Kotlin is a modern, statically typed programming language that runs on the Java Virtual Machine (JVM). One of the language’s powerful features is reflection, which allows you to examine and manipulate the structure of your code at runtime. Kotlin reflection provides a set of APIs that enable introspection, dynamic loading, and modification of classes, objects, properties, and functions. In this blog post, we will delve into the world of Kotlin reflection, exploring its various aspects and providing examples to help you understand its capabilities.
Basics of Reflection in Kotlin
Reflection in Kotlin allows you to access properties and methods of objects dynamically at runtime, without knowing them in advance. Normally, when you access a method or property, your program’s source code references a specific declaration, and the compiler ensures that the declaration exists. However, there are situations where you need to work with objects of any type or where the names of methods and properties are only known at runtime. This is where reflection comes in handy.
In Kotlin, there are two reflection APIs you can work with. The first one is the standard Java reflection API, which is defined in java.lang.reflect package. Since Kotlin classes are compiled to regular Java bytecode, the Java reflection API works perfectly with Kotlin. This means that Java libraries that use reflection are fully compatible with Kotlin code.
The second reflection API is the Kotlin reflection API, defined in the kotlin.reflect package. This API provides access to concepts that don’t exist in the Java world, such as properties and nullable types. However, it doesn’t fully replace the Java reflection API, and there may be cases where you need to use Java reflection instead. It’s important to note that the Kotlin reflection API is not restricted to Kotlin classes alone; you can use it to access classes written in any JVM language.
Let’s take an example to understand how reflection can be useful. Suppose you have a JSON serialization library that needs to serialize any object to JSON. Since the library can’t reference specific classes and properties, it relies on reflection to dynamically access and serialize the object’s properties at runtime. This allows the library to handle objects of different types without having prior knowledge about their structure.
Here’s a simplified example of using reflection in Kotlin to access the properties of an object dynamically:
Kotlin
dataclassPerson(val name: String, val age: Int)funmain() {val person = Person("Alice", 25)val properties = person.javaClass.declaredFieldsfor (property in properties) { property.isAccessible = truevalvalue = property.get(person)println("${property.name}: $value") }}
In this example, we have a Person class with two properties: name and age. Using reflection, we retrieve the declared fields (name and age) of the person object dynamically. By setting the isAccessible property to true, we ensure that we can access private fields as well. Finally, we get the values of the properties using the get() method and print them.
JVM dependency
To add the Kotlin reflection library as a dependency in your JVM project, you’ll need to include the kotlin-reflect artifact. Here’s how you can do it in Gradle and Maven:
Make sure to replace the version (1.9.0 in this example) with the version you desire to use or the latest updated one.
If you’re not using Gradle or Maven, ensure that the kotlin-reflect.jar is present in the classpath of your project. For IntelliJ IDEA projects that use the command-line compiler or Ant, the reflection library is automatically added by default. However, in the command-line compiler and Ant, you can use the -no-reflect compiler option to exclude kotlin-reflect.jar from the classpath if you don’t need it.
Note that, to reduce the runtime library size on platforms where it matters, such as Android, the Kotlin reflection API is packaged into a separate .jar file, kotlin-reflect.jar, which isn’t added to the dependencies of new projects by default. If you’re using the Kotlin reflection API, you need to make sure the library is added as a dependency. IntelliJ IDEA can detect the missing dependency and assist you with adding it.
Kotlin reflection API
The Kotlin reflection API provides a set of classes and interfaces that allow you to inspect and manipulate the structure and behavior of Kotlin classes at runtime. Here are the main components of the Kotlin reflection API that you mentioned:
KClass
In Kotlin, the main entry point for the reflection API is the KClass interface, which represents a class. It serves as a counterpart to Java’s java.lang.Class and allows you to perform various reflection operations.
To obtain an instance of KClass, you can use the ::class syntax on a class name. For example, if you have a class named MyClass, you can get its KClass instance like this:
Kotlin
val myClassKClass: KClass<MyClass> = MyClass::class
Once you have a KClass instance, you can use it to enumerate and access the declarations contained within the class, its superclasses, interfaces, and so on. The KClass interface provides functions and properties to perform these operations.
To get the KClass of an object at runtime, you can use the javaClass property, which returns the corresponding Java class. Then, you can access the .kotlin extension property on the Java class to obtain the Kotlin reflection counterpart (KClass). Here’s an example:
Kotlin
val obj = MyClass()val objKClass: KClass<outMyClass> = obj.javaClass.kotlin
In the above example, obj.javaClass returns the Java class of the obj instance, and .kotlin is used to obtain the corresponding KClass instance.
Once you have a KClass instance, you can use it to perform reflection operations such as accessing properties, functions, constructors, annotations, and more.
Kotlin
classPerson(val name: String, val age: Int)val person = Person("Alice", 29)val kClass = person.javaClass.kotlinprintln(kClass.simpleName)kClass.memberProperties.forEach { println(it.name) }
Output:
Person age name
Explanation:
The Person class is defined with two properties: name of type String and age of type Int.
An instance of Person named person is created with the values “Alice” and 29 for the name and age properties, respectively.
person.javaClass returns the corresponding Java class object for the person instance.
kotlin is used as an extension property on the Java class object to obtain the KClass instance representing the Person class, which is assigned to the kClass variable.
kClass.simpleName prints the simple name of the class, which in this case is “Person”.
kClass.memberProperties returns a collection of KProperty objects representing the non-extension properties of the class, including properties defined in its superclasses.
The forEach function is used to iterate over each KProperty object in the collection and print its name. In this example, it prints “age” and “name”, which are the names of the properties defined in the Person class.
By using kClass.memberProperties, you can retrieve all the non-extension properties defined in a class, including those inherited from its superclasses. This is a useful feature of Kotlin reflection for dynamically inspecting and working with class properties at runtime.
If you browse the declaration of KClass, you’ll see that, KClass interface in Kotlin reflection provides several useful methods and properties for accessing the contents of a class. Here are some additional features of KClass:
simpleName:Returns the simple name of the class as a String. This property is useful for getting the name of the class without any package information.
qualifiedName: Returns the fully qualified name of the class as a String, including the package name. This property is useful when you need the complete name of the class, including the package.
members:Returns a collection of KCallable objects representing all members (properties, functions, etc.) of the class. This includes both declared members and inherited members from superclasses.
constructors:Returns a collection of KFunction objects representing all constructors of the class. This allows you to retrieve information about the constructors and their parameters.
nestedClasses:Returns a collection of KClass objects representing all nested classes declared within the class. This is useful if you want to access and work with nested classes.
These are just a few examples of the additional features provided by the KClass interface. You can find more details and explore other methods and properties available in the Kotlin standard library reference (http://mng.bz/em4i).
KCallable
In Kotlin, when you want to access and call functions or properties dynamically through reflection, you can use the KCallable interface. KCallable is a super interface for functions and properties, and it declares the call method, which allows you to invoke the corresponding function or property getter.
Here’s an example of using the call method to call a function through reflection:
In this example, the ::foo syntax refers to the function foo and returns an instance of the KFunction class from the reflection API. By using the call method on kFunction, you can invoke the referenced function. In this case, we provide a single argument, 42.
If you try to call the function with an incorrect number of arguments, such as function.call(), it will throw a runtime exception with the message “IllegalArgumentException: Callable expects 1 arguments, but 0 were provided.”
To provide better type safety and enforce the correct number of arguments, you can use a more specific method called invoke. The type of the ::foo expression, in this case, is KFunction1<Int, Unit>, which contains information about parameter and return types. The 1 denotes that this function takes one parameter of type Int. You can then use the invoke method to call the function with a fixed number of arguments:
In the above example, kFunction.invoke(1, 2) calls the sum function with arguments 1 and 2, and kFunction(3, 4) is a shorthand notation for invoking the function. Since kFunction is of type KFunction2<Int, Int, Int>, it only accepts two arguments of type Int. If you try to call it with an incorrect number of arguments, the code won’t compile.
So, if you have a KFunction of a specific type with known parameters and return type, it’s recommended to use its invoke method for type safety. The call method is a more generic approach that can work with any type of function but doesn’t provide the same level of type safety.
BTW, How, and where are KFunctionN interfaces defined?
The KFunctionN interfaces, such as KFunction1, KFunction2, and so on, represent functions with different numbers of parameters. Each of these types extends the KFunction interface and adds an invoke member with the corresponding number of parameters.
For example, the KFunction2 interface declares the invoke method as follows:
Kotlin
operatorfuninvoke(p1: P1, p2: P2): R
Here, P1 and P2 represent the parameter types of the function, and R represents the return type.
It’s important to note that these function types, represented by KFunctionN, are synthetic compiler-generated types. You won’t find their explicit declarations in the kotlin.reflect package or any other standard Kotlin library.
The synthetic types approach allows for flexibility in the number of parameters a function type can have. Generating these types dynamically reduces the size of the kotlin-runtime.jar and avoids imposing artificial restrictions on the maximum number of function-type parameters.
So, the KFunctionN interfaces are synthetic types created by the compiler to represent functions with different numbers of parameters. They are not explicitly defined in the kotlin.reflect package, and their generation is based on the function’s parameter and return types.
KProperty
In Kotlin, you can also use reflection to access and retrieve property values dynamically. The KProperty interface provides methods to achieve this, with different interfaces for top-level properties and member properties.
For top-level properties, you can use instances of the KProperty0 interface, which has a no-argument get method. Here’s an example:
Kotlin
var counter = 0val kProperty = ::counterkProperty.setter.call(21)println(kProperty.get()) // Output: 21
In this example, kProperty refers to the top-level property counter. By calling kProperty.get(), you retrieve the current value of the property.
For member properties, you need to use the appropriate interface based on the number of arguments the get method requires. For example, KProperty1 is used for member properties with a single argument. You also need to provide the object instance on which the property is accessed. Here’s an example:
Kotlin
classPerson(val name: String, val age: Int)val person = Person("Alice", 29)val memberProperty = Person::ageprintln(memberProperty.get(person)) // Output: 29
In this case, memberProperty refers to the age property of the Person class. By calling memberProperty.get(person), you retrieve the value of the age property for the specific person instance.
It’s important to note that KProperty1 is a generic class. The type of memberProperty in the above example is KProperty<Person, Int>, where the first type parameter represents the type of the receiver (in this case, Person) and the second type parameter represents the property type (Int). This ensures that you can only call the get method with a receiver of the correct type. Attempting to call memberProperty.get("Alice") would result in a compilation error.
Please keep in mind that reflection can only be used to access properties defined at the top level or within a class, and not local variables within a function. If you try to obtain a reference to a local variable using::x, you will encounter a compilation error stating that “References to variables aren’t supported yet”.
Hierarchy of interfaces in Kotlin Reflection API
In Kotlin, there is a hierarchy of interfaces that allows you to access source code elements at runtime. These interfaces are designed to represent declarations and provide access to various information about them.
Here’s a breakdown of the key interfaces mentioned:
KAnnotatedElement: This interface serves as the base for all other interfaces that represent declarations at runtime, such as KClass, KFunction, and KParameter. Since all declarations can be annotated, they inherit this interface to provide access to annotations.
KClass:This interface is used to represent both classes and objects at runtime. It provides access to information about the class or object, such as its name, superclass, interfaces, properties, and functions.
KProperty: This interface represents any property at runtime, regardless of whether it’s mutable (var) or read-only (val). It allows you to access information about the property, such as its name, type, annotations, and the getter function.
KMutableProperty: This is a subclass of KProperty and specifically represents a mutable property, which is declared using var. It provides additional functionality to modify the property’s value, in addition to the information available through KProperty.
Getter and Setter: These are special interfaces defined in Property and KMutableProperty to work with property accessors as functions. They extend the KFunction interface, allowing you to access information about the getter and setter functions associated with the property. For example, you can retrieve annotations applied to the getter or setter.
Note that the figure mentions the omission of specific interfaces like KProperty0 for simplicity. KProperty0 represents a property without any parameters (zero-argument property). Similarly, there are other specific interfaces in the hierarchy that cater to different scenarios and numbers of parameters.
Overall, these interfaces in the hierarchy provide a flexible and comprehensive way to access and manipulate source code elements at runtime, allowing you to retrieve information, modify values, and work with annotations.
Best Practices and Use Cases for Kotlin Reflection:
Serialization and Deserialization
Reflection is commonly used in serialization and deserialization libraries. It allows these libraries to introspect object structures dynamically, enabling automatic conversion between objects and their serialized forms. Reflection facilitates the inspection of object properties and their values, making it easier to transform objects into a serialized format and vice versa.
In this example, reflection is used to serialize a Person object by obtaining its properties using memberProperties and creating a map of property names and values. The same reflection-based approach is used to deserialize the serialized data back into a Person object.
Dependency Injection
Frameworks like Spring heavily rely on reflection for implementing dependency injection. Reflection enables these frameworks to scan classes, examine annotations, and wire dependencies at runtime. By leveraging reflection, frameworks can automatically discover and instantiate the required dependencies, reducing the need for manual configuration. Reflection helps in achieving loose coupling and makes the dependency injection process more flexible and adaptable.
Kotlin
classServiceA {fundoSomething() {println("Service A is doing something.") }}classServiceB {fundoSomethingElse() {println("Service B is doing something else.") }}classClient(privateval serviceA: ServiceA, privateval serviceB: ServiceB) {funperformActions() { serviceA.doSomething() serviceB.doSomethingElse() }}funmain() {val clientClass = Client::classval constructors = clientClass.constructorsif (constructors.isNotEmpty()) {val constructor = constructors.first()val parameters = constructor.parameters// Dependency injection using reflectionval serviceA = ServiceA()val serviceB = ServiceB()val client = constructor.callBy(mapOf(parameters[0] to serviceA, parameters[1] to serviceB)) client.performActions() }}
In this example, reflection is used for dependency injection. The Client class has dependencies on ServiceA and ServiceB. Using reflection, the constructor of Client is obtained, and the dependencies are instantiated (ServiceA and ServiceB). The dependencies are then injected into the Client object using callBy and a map of parameter-to-argument pairs.
Testing and Mocking
Reflection is valuable in testing and mocking scenarios. It allows developers to examine and modify internal state, invoke private methods, and create mock objects dynamically. Reflection provides the ability to access and modify otherwise inaccessible members, which is particularly useful for unit testing private methods or creating mock objects with dynamically generated behavior. It simplifies the process of writing test cases and enables comprehensive testing of code components.
Kotlin
classMathUtils {privatefunmultiply(a: Int, b: Int): Int {return a * b }funsquare(n: Int): Int {returnmultiply(n, n) }}funmain() {val mathUtils = MathUtils()val multiplyMethod = mathUtils::class.declaredFunctions .firstOrNull { it.name == "multiply" } multiplyMethod?.let { it.isAccessible = trueval result = it.call(mathUtils, 4, 5)println(result) // Output: 20 }}
In this example, reflection is used for testing and mocking. The private multiply method of the MathUtils class is accessed using reflection by setting its accessibility to true. The call method is then used to invoke the method with arguments. This allows us to test the private method’s functionality or mock its behavior in a controlled testing environment.
By following these best practices and leveraging Kotlin reflection in appropriate use cases such as serialization, dependency injection, and testing, developers can harness the full potential of reflection to enhance the flexibility, extensibility, and maintainability of their Kotlin applications.
Limitations of Kotlin Reflection
Reflection has some limitations, such as being unable to access private members by default, requiring additional permissions in security-restricted environments, and being less type-safe than static typing.
The limitations of Kotlin reflection include:
Performance Overhead: Reflection operations incur performance overhead compared to statically typed code, as they involve runtime introspection and dynamic dispatch.
Limited Access to Private Members: By default, Kotlin reflection does not provide direct access to private members of classes. Access to private members requires setting accessibility, which may have security implications.
Security and Permissions: In security-restricted environments, using reflection may require additional permissions or explicit configuration to prevent unauthorized access.
Type Safety: Reflection is inherently less type-safe compared to static typing. Type errors that would normally be caught by the compiler can only be detected at runtime when using reflection.
Limited Compile-Time Checks: Reflection bypasses many compile-time checks provided by the Kotlin compiler. Renaming elements accessed via reflection may lead to runtime errors that are not caught during compilation.
Platform Dependencies: Kotlin reflection relies on platform-specific features and APIs, which may introduce variations in behavior and capabilities across different platforms.
Conclusion
Kotlin reflection provides a powerful set of APIs that enable you to examine and manipulate your code dynamically at runtime. From accessing class information to modifying properties and invoking functions dynamically, reflection opens up a whole new range of possibilities in your Kotlin projects. However, it’s essential to use reflection judiciously and be aware of its performance implications. By understanding the concepts and best practices outlined in this blog post, you’ll be well-equipped to leverage the full potential of Kotlin reflection in your applications.
Annotations are a powerful feature in the Kotlin programming language that allows you to add metadata and additional information to your code. They provide a way to decorate classes, functions, properties, and other program elements with custom markers, which can be processed at compile time or runtime. In this blog post, we will dive deep into Kotlin annotations, exploring their syntax, usage, and various aspects, along with practical examples to illustrate their capabilities.
Kotlin Annotations Basics
In programming, metadata refers to additional information about a piece of code or data. It provides context, instructions, or descriptive details that can be used by tools, frameworks, or other parts of the system.
Annotations in Kotlin are a way to attach metadata to declarations in your code. They allow you to add information or instructions to classes, functions, properties, or other elements. Annotations are defined using special syntax and are prefixed with the @ symbol.
Annotations can have parameters that allow you to provide specific values or arguments. These parameters can be of different types, such as strings, numbers, classes, or even other annotations.
When you apply an annotation to a declaration, you associate that metadata with the declaration. The metadata can then be processed or accessed by various tools, frameworks, or libraries during compilation, runtime, or reflection.
Let’s look at a simple example to understand annotations better:
Kotlin
// Define a custom annotationannotationclassMyAnnotation(val message: String)// Apply the annotation to a function@MyAnnotation("This is my function")funmyFunction() {// Function implementation}
In this example, we define a custom annotation(don’t worry! We discuss it later) called MyAnnotation with a parameter message. We then apply this annotation to the myFunction function.
The annotation @MyAnnotation("This is my function") serves as metadata attached to the function declaration. It provides additional information or instructions about the function.
The metadata provided by the annotation can be used by various tools or frameworks. For instance, a documentation generation tool may use annotation to include the message in the generated documentation. A code analyzer or linter may use the annotation to perform specific checks or enforce coding standards related to the function.
Annotations can also be processed at runtime using reflection. Reflection allows you to inspect and manipulate code and data during program execution. You can use reflection to access annotations attached to declarations and perform actions based on the metadata they provide.
Declaring and applying annotations
In Kotlin, annotations are used to associate additional metadata with declarations, like functions or classes. This metadata can be accessed by various tools that work with source code, compiled class files, or at runtime, depending on how the annotation is configured.
Applying annotations
To apply an annotation in Kotlin, you use the @ symbol followed by the annotation’s name at the beginning of the declaration you want to annotate. You can apply annotations to functions, classes, and other code elements. Let’s see some examples:
Here’s an example using the JUnit framework, where a test method is marked with the @Test annotation:
In Kotlin, annotations can have parameters. Let’s take a look at the @Deprecated annotation as a more interesting example. It has a replaceWith parameter, which allows you to provide a replacement pattern to facilitate a smooth transition to a new version of the API. The following code demonstrates the usage of annotation arguments, including a deprecation message and a replacement pattern:
In this case, when someone uses the remove function in their code, the IDE will not only show a suggestion to use removeAt instead, but it will also offer a quick fix to automatically replace the remove function with removeAt. This makes it easier to update your code and follow the recommended practices.
Annotations in Kotlin can have arguments of specific types, such as primitive types, strings, enums, class references, other annotation classes, and arrays of these types. The syntax for specifying annotation arguments is slightly different from Java:
To specify a class as an annotation argument, use the ::class syntax:
When you want to specify a class as an argument for an annotation, you can use the ::class syntax.
Kotlin
@MyAnnotation(MyClass::class)
In this case, let’s say you have a custom annotation called @MyAnnotation, and you want to pass a class called MyClass as an argument to that annotation. In this case, you can use the ::class syntax like this: @MyAnnotation(MyClass::class).
By using ::class, you are referring to the class itself as an object. It allows you to pass the class reference as an argument to the annotation, indicating which class the annotation is associated with.
To specify another annotation as an argument, don’t use the @ character before the annotation name:
when specifying an annotation as an argument for another annotation, you don’t need to use the “@” symbol before the annotation name.
In the above example, the @Deprecated annotation. It allows you to provide a replacement pattern using the ReplaceWith annotation. In this case, you simply specify the ReplaceWith annotation without the “@” symbol when using it as an argument for @Deprecated .
By omitting the “@” symbol, you indicate that the argument is another annotation.
To specify an array as an argument, use the arrayOf function:
if you want to specify an array as an argument for an annotation, you can use the arrayOf function.
For example, let’s say you have an annotation called @RequestMapping with a parameter called path, and you want to pass an array of strings ["/foo", "/bar"] as the value for that parameter. In this case, you can use the arrayOf function like this:
Kotlin
@RequestMapping(path = arrayOf("/foo", "/bar"))
However, if the annotation class is declared in Java, you don’t need to use the arrayOf function. In Java, the parameter named value in the annotation is automatically converted to a vararg parameter if necessary. This means you can directly provide the values without using the arrayOf function.
To use a property as an annotation argument, you need to mark it with a const modifier:
In Kotlin, annotation arguments need to be known at compile time, which means you cannot refer to arbitrary properties as arguments. However, you can use the const modifier to mark a property as a compile-time constant, allowing you to use it as an annotation argument.
To use a property as an annotation argument, follow these steps:
Declare the property using the const modifier at the top level of a file or inside an object.
Initialize the property with a value of a primitive type or a String.
Here’s an example using JUnit’s @Test annotation that specifies a timeout for a test:
In this example, TEST_TIMEOUT is declared as a const property with a value of 100L. The timeout parameter of the @Test annotation is then set to the value of TEST_TIMEOUT. This allows you to specify the timeout value as a constant that can be reused and easily changed if needed.
Remember that properties marked with const need to be declared at the top level of a file or inside an object, and they must be initialized with values of primitive types or String. Using regular properties without the const modifier will result in a compilation error with the message “Only ‘const val’ can be used in constant expressions.”
Annotation targets
In Kotlin, a single declaration can correspond to multiple Java declarations, each of which can have annotations. For example, a Kotlin property can correspond to a Java field, a getter, a setter, and a constructor parameter (in the case of a property declared in the primary constructor). In such cases, it’s important to specify which element should be annotated.
To specify the element to be annotated, you use a “use-site target” declaration. The use-site target is placed between the “@” sign and the annotation name, separated by a colon. For instance, if you want to apply the @Rule annotation to the property getter, you would write @get:Rule.
Let’s take an example using JUnit. In JUnit, you can specify a rule to be executed before each test method. The standard TemporaryFolder rule is used to create files and folders that are automatically deleted when the test method finishes.
In Java, you would declare a public field or method annotated with @Rule to specify the rule. However, if you annotate the folder property in your Kotlin test class with @Rule, you’ll encounter a JUnit exception saying “The @Rule ‘folder’ must be public.” This happens because @Rule is applied to the field, which is private by default. To apply it to the getter, you need to write @get:Rule explicitly.
When you annotate a property with an annotation declared in Java, it is applied to the corresponding field by default. However, Kotlin also allows you to declare annotations that can be directly applied to properties.
The following is a list of supported use-site targets in Kotlin:
property: Java annotations cannot be applied with this use-site target.
field: Field generated for the property.
get: Property getter.
set: Property setter.
receiver: Receiver parameter of an extension function or property.
param: Constructor parameter.
setparam: Property setter parameter.
delegate: Field storing the delegate instance for a delegated property.
file: Class containing top-level functions and properties declared in the file.
If you want to annotate a file with the file target, the annotation needs to be placed at the top level of the file, before the package directive. For example, @file:JvmName("StringFunctions") changes the name of the corresponding class.
Unlike Java, Kotlin allows you to apply annotations to arbitrary expressions, not just class and function declarations or types. A common example is the @Suppress annotation, which can be used to suppress a specific compiler warning within the annotated expression. Here’s an example that suppresses an unchecked cast warning for a local variable declaration:
Kotlin
funtest(list: List<*>) {@Suppress("UNCHECKED_CAST")val strings = list as List<String>// ...}
Note that IntelliJ IDEA can automatically insert this annotation for you when you encounter a compiler warning by pressing Alt-Enter and selecting Suppress from the intention options menu.
Controlling the Java API with annotations
Kotlin provides several annotations that allow you to control how Kotlin declarations are compiled to Java bytecode and interact with Java callers. These annotations serve various purposes, such as replacing Java keywords, changing method or field names, exposing methods as static Java methods, generating function overloads, or exposing properties as Java fields without getters or setters. Let’s go through them:
@Volatile and @Strictfp
@Volatile is used as a replacement for the Java keyword volatile, indicating that a property should be treated as volatile in Java.
@Strictfp is used as a replacement for the Java keyword strictfp, ensuring that a method or class adheres to strict floating-point arithmetic rules in Java.
@JvmName
@JvmName allows you to change the name of a method or field that is generated from a Kotlin declaration when it is accessed from Java.
By default, Kotlin uses its own naming conventions, and @JvmName provides compatibility with existing Java code that expects different names.
@JvmStatic
@JvmStatic is applied to methods within an object declaration or a companion object in Kotlin.
It exposes those methods as static Java methods, meaning they can be called directly on the class without needing an instance of the enclosing object or companion object.
@JvmOverloads
When a Kotlin function has default parameter values, @JvmOverloads instructs the Kotlin compiler to generate additional overloaded versions of the function in the bytecode.
These generated overloaded versions provide options to Java callers to omit some or all of the optional parameters, making it easier to call the function from Java code.
@JvmField
@JvmField is used to expose a property as a public Java field without generating the default getters and setters.
When applied to a property, Kotlin will generate a public Java field instead, allowing direct access to the field from Java code.
These annotations enhance the interoperability between Kotlin and Java by providing fine-grained control over how Kotlin declarations are compiled and accessed from Java. They help ensure seamless integration between the two languages and facilitate working with existing Java codebases in Kotlin projects.
Declaring annotations (Custom annotation)
In Kotlin, we can declare our own annotations using the annotation modifier. Annotations allow you to associate metadata with declarations such as functions, classes, properties, or parameters. This metadata can be accessed by tools or frameworks that work with your code, enabling additional functionality or behavior. Here’s the general syntax for declaring an annotation in Kotlin:
Kotlin
annotationclassMyAnnotation
Let’s take the example of the @JsonExclude annotation, which is a simple annotation without any parameters:
Kotlin
annotationclassJsonExclude
The syntax resembles a regular class declaration but with the annotation modifier. Annotation classes are used to define metadata associated with declarations and expressions and cannot contain any code, so they don’t have a body.
For annotations that require parameters, you can declare the parameters in the primary constructor of the annotation class. Let’s consider the @JsonName annotation as an example, which takes a name parameter:
Kotlin
annotationclassJsonName(val name: String)
Here, we use the regular primary constructor declaration syntax. It’s important to note that the val keyword is mandatory for all parameters of an annotation class.
Now, let’s compare how the same annotation would be declared in Java:
In Java, the annotation has a method called value(), whereas in Kotlin, the annotation has a property called name. In Java, when applying an annotation, you need to explicitly specify names for all attributes except value. In Kotlin, applying an annotation is similar to a regular constructor call. You can use named arguments to make the argument names explicit, or you can omit them. For example, @JsonName(name = "first_name") is equivalent to @JsonName("first_name"), as name is the first parameter of the JsonName constructor.
If you need to apply a Java annotation to a Kotlin element, you must use the named-argument syntax for all arguments except value, which Kotlin also recognizes as a special case.
Annotation Parameters:
Annotations can have constructors that take parameters, as we saw in the above example, allowing you to customize their behavior for different use cases. Parameters can have default values, making them optional when applying the annotation. Parameters can be of the following types:
Primitive types (e.g., Int, String, Boolean)
Enum classes
Class references
Other annotation classes
Arrays of the above types
Here’s an example of an annotation with parameters:
Kotlin
annotationclassMyAnnotation(valvalue: String, val priority: Int = 1)
In the above example, MyAnnotation takes two parameters: value of type String and priority of type Int. The priority parameter has a default value of 1, making it optional when applying the annotation, which means it is optional to provide a value for priority when applying the annotation. If a value is not explicitly provided, the default value of 1 will be used.
Instantiation
In Java, an annotation type is a form of an interface, so you can implement it and use an instance. For example, the following code defines an annotation type called InfoMarker and then implements it in a class called MyClass:
Kotlin
@interfaceInfoMarker { String info() default "default";}classMyClass implements InfoMarker {@Overridepublic String info() {return"This is my info"; }}
While, in Kotlin, you can call the constructor of an annotation class in arbitrary code( This means that you can create an instance of an annotation class anywhere in your code, not just in a class that implements the annotation.)and similarly use the resulting instance. For example, the following code defines an annotation class called InfoMarker and then creates an instance of the annotation class in the main function:
Kotlin
annotationclassInfoMarker(val info: String)funmain(args: Array<String>) {val marker = InfoMarker("This is my info")println(marker.info) // This is my info}
The InfoMarker annotation class has a single property called info, which is of type String. The main function creates an instance of the InfoMarker annotation class by calling the constructor with the value “This is my info”. The println function then prints the value of the info property.
As you can see, Kotlin’s approach to annotation instantiation is much simpler than Java’s. In Kotlin, you don’t need to implement an annotation interface; you can simply call the constructor of the annotation class and use the resulting instance. This makes it much easier to create and use annotations in Kotlin code.
Meta-annotations: controlling how an annotation is processed
Let’s discuss how to control the usage of annotations and how you can apply annotations to other annotations.
In Kotlin, just like in Java, you can annotate an annotation class itself. These annotations, which can be applied to annotation classes, are called meta-annotations. Meta-annotations control how the compiler processes annotations. Various frameworks, including dependency injection libraries, also use meta-annotations to mark annotations for different purposes.
One commonly used meta-annotation in the Kotlin standard library is @Target. It specifies the valid targets for the annotated annotation. For example, in the declarations of JsonExclude and JsonName in JKid(simple JSON serialization/deserialization library for Kotlin), @Target is used as follows:
@Target(AnnotationTarget.PROPERTY) annotation class JsonExclude
The @Target meta-annotation indicates the types of elements to which the annotation can be applied. If @Target is not used, the annotation will be applicable to all declarations, which might not make sense in specific contexts. The AnnotationTarget enum provides a range of possible targets for annotations, including classes, files, functions, properties, property accessors, types, expressions, and more. If needed, you can specify multiple targets like this: @Target(AnnotationTarget.CLASS, AnnotationTarget.METHOD).
The commonly used targets include:
CLASS: Annotation can be applied to classes and interfaces.
FUNCTION: Annotation can be applied to functions and methods.
PROPERTY: Annotation can be applied to properties.
FIELD: Annotation can be applied to fields (backing fields of properties).
ANNOTATION_CLASS: Annotation can be applied to other annotations.
PARAMETER: Annotation can be applied to function parameters.
CONSTRUCTOR: Annotation can be applied to constructors.
Custom Meta-Annotations
In Kotlin, you can define your own meta-annotation by using the AnnotationTarget.ANNOTATION_CLASS target. This allows you to create an annotation that can be used to annotate other annotations. Here’s an example:
In this example, we define a meta-annotation called BindingAnnotation using the AnnotationTarget.ANNOTATION_CLASS target. This means that BindingAnnotation can be used to annotate other annotations.
However, if you want to use your annotation from Java code, annotations with the PROPERTY target cannot be used directly. To make such annotations usable from Java, you can add an additional target called AnnotationTarget.FIELD. This will allow the annotation to be applied to properties in Kotlin and to fields in Java. Here’s an example:
In this updated example, we added AnnotationTarget.FIELD as an additional target for the BindingAnnotation annotation. This enables the annotation to be used on properties in Kotlin and fields in Java, making it usable in both languages.
The @Retention annotation
Another important meta-annotation you might be familiar with from Java is @Retention. It determines whether the annotation will be stored in the .class file and whether it will be accessible at runtime through reflection. In Kotlin, the default retention is RUNTIME, which means annotations are retained in .class files and accessible at runtime. Therefore, the JKid annotations do not explicitly specify retention.
If you want to declare your own annotations with different retention policies, you can use @Retention and specify the desired AnnotationRetention value, such as SOURCE, BINARY, or RUNTIME.
Here’s an example of specifying retention explicitly:
SOURCE: This retention policy indicates that the annotation will only be retained in the source code and will not be included in the compiled .class files. It will not be accessible at runtime through reflection.
BINARY: This retention policy specifies that the annotation will be stored in the .class files, but it won’t be accessible at runtime through reflection.
RUNTIME: This is the default retention policy in Kotlin. It means that the annotation will be stored in the .class files and will be accessible at runtime through reflection.
By explicitly specifying the desired AnnotationRetention value using @Retention, you can control how your annotations are retained and accessed in Kotlin.
Repeatable annotations
In Kotlin, just like in Java, you can use repeatable annotations, which allow you to apply an annotation multiple times to a single code element. To make an annotation repeatable in Kotlin, you need to mark its declaration with the @kotlin.annotation.Repeatable meta-annotation. This ensures that the annotation can be repeated both in Kotlin and Java.
The key difference between Kotlin and Java in terms of repeatable annotations is the absence of a containing annotation in Kotlin. In Java, a containing annotation is automatically generated by the compiler with a predefined name to hold the repeated annotations. However, in Kotlin, the compiler generates this containing annotation automatically with the name @Tag.Container.
Here’s an example that demonstrates the usage of repeatable annotations in Kotlin:
Kotlin
@RepeatableannotationclassTag(val name: String)// The compiler generates the @Tag.Container containing annotation
In the example above, the @Tag annotation is marked as repeatable. This means you can apply it multiple times to the same code element. The Kotlin compiler automatically generates the containing annotation @Tag.Container to hold the repeated annotations.
If you want to specify a custom name for the containing annotation, you can use the @kotlin.jvm.JvmRepeatable meta-annotation. Here’s an example:
In this case, the @Tag annotation is marked as repeatable using @JvmRepeatable and the explicitly declared containing annotation class Tags.
To extract repeatable annotations in Kotlin or Java using reflection, you can use the KAnnotatedElement.findAnnotations() function. This function allows you to retrieve all the instances of a specific repeatable annotation applied to an element.
Overall, Kotlin supports repeatable annotations similarly to Java, but with a slight difference in how the containing annotation is handled.
Here’s a real-time example in Kotlin, it demonstrates the usage of repeatable annotations:
In this example, we have a Book class that represents a book. We want to annotate the class with the names of the authors. The Author annotation is marked as repeatable using @Repeatable.
In the main function, we retrieve the annotations applied to the Book class using reflection. We iterate over the annotations and check if they are instances of the Author annotation. If they are, we print out the name of the author.
When you run this code, it will output:
Author: John Author: Jane
As you can see, the Author annotation is repeated twice on the Book class, allowing us to specify multiple authors for a single book.
This example showcases how you can use repeatable annotations in Kotlin to add multiple instances of the same annotation to a code element, and then access those annotations at runtime using reflection.
Lambdas
Annotations, which are used to provide metadata or additional information about elements in your code, can also be applied to lambdas in Kotlin. When an annotation is used on a lambda, it is applied to the invoke() method that represents the body of the lambda.
One example of using annotations on lambdas is in frameworks like Quasar, which utilize annotations for concurrency control. In the provided code snippet, an annotation called Suspendable is used on a lambda expression.
Kotlin
annotationclassSuspendableval f = @Suspendable { Fiber.sleep(10) }
In this example, the Suspendable annotation is applied to the lambda expression. It indicates that the code inside the lambda is suspendable, meaning it can be paused and resumed later. The specific behavior and implementation details of the Suspendable annotation would be determined by the framework or library you’re using.
Please note that the Fiber.sleep(10) inside the lambda is just a fictional example and may not reflect actual usage. It’s meant to demonstrate that the lambda contains some code that could be annotated with Suspendable for concurrency control purposes.
Conclusion
Kotlin annotations are a powerful tool for adding metadata and controlling the behavior of code. Whether it’s using built-in annotations or creating custom ones, annotations enable developers to express additional information and automate tasks. By understanding the syntax, usage, and advanced techniques of Kotlin annotations, you can enhance your codebase, improve documentation, and streamline development processes.
Remember, annotations are not just decorations; they are a valuable asset in your Kotlin programming arsenal. By mastering annotations, you can take full advantage of their capabilities and write more robust, maintainable, and expressive code.
Kotlin is a modern programming language that offers powerful features for building robust and type-safe applications. One such feature is generics, which allows developers to write reusable code that can work with different types. In this blog post, we will delve into the concept of Kotlin generics and explore the intricacies of variance, providing clear explanations and practical examples along the way.
In Kotlin, there are some advanced concepts related to generics that we will explore. These concepts include reified type parameters and declaration-site variance. While they may sound unfamiliar, don’t worry! This blog post will cover them in detail.
Generic type parameters
In Kotlin Generics, you can define types that have type parameters, allowing for more flexible and reusable code. When you create an instance of such a type, you substitute the type parameters with specific types, known as type arguments. This allows you to specify the kind of data that will be stored or processed by the type.
For example, let’s consider the List type. In Kotlin, you can declare a variable of type List<String>, which means it holds a list of strings. Similarly, the Map type has type parameters for the key type (K) and the value type (V). You can instantiate a Map with specific arguments, such as Map<String, Person>, which represents a map with string keys and Person values.
In many cases, the Kotlin compiler can infer the type arguments based on the context. For example:
Kotlin
val authors = listOf("Stan Lee", "J.K Rowling")
Here, since both values passed to the listOf function are strings, the compiler infers that you’re creating a List<String>. However, when creating an empty list, there is nothing to infer the type argument from, so you need to specify it explicitly. You have two options for specifying the type argument:
Kotlin
// here created empty list, so nothing to infer, so specified type argument(i.e String) explicitly.val readers: MutableList<String> = mutableListOf()val readers = mutableListOf<String>()
Both of these declarations are equivalent and create an empty mutable list of strings.
It’s important to note that Kotlin always requires type arguments to be either specified explicitly or inferred by the compiler. In contrast, Java allows the use of raw types, where you can declare a variable of a generic type without specifying the type argument. However, Kotlin does not support raw types, and type arguments must always be defined.
So, in Kotlin, you won’t encounter situations where you can use a generic type without providing the type arguments explicitly or inferring them through type inference.
Generic functions and properties
In Kotlin generics, You can write generic functions and properties to work with any type, providing flexibility and reusability. Generic functions have their own type parameters, which are replaced with specific type arguments when invoking the function. Similarly, generic properties allow you to define properties that are parameterized by a type.
Let’s explore examples of generic functions and properties to understand how they work.
Generic Functions
To illustrate, let’s consider the slice function, which returns a list containing elements at specified indices. Its declaration looks like this:
Kotlin
fun <T> List<T>.slice(indices: IntRange): List<T> {// Implementation goes here}
In this example, <T> is the type parameter, which represents the unknown type that will be specified when calling the function. The List<T> is the receiver type, indicating that this function is an extension function for lists. The return type List<T> ensures that the resulting list has the same type as the input list.
When invoking the slice function, you can either explicitly specify the type argument or rely on type inference:
Kotlin
val letters = ('a'..'z').toList()println(letters.slice<Char>(0..2)) // Specifies the type argument explicitly, o/p - [a, b, c]println(letters.slice(10..13)) // The compiler infers that T is Char here, o/p - [k, l, m, n]
The compiler can often infer the type argument based on the context, so you don’t need to explicitly provide it. In both cases, the result type will be List<Char>.
Generic type parameter used in parameter Function Type
When a generic type parameter is used in a parameter function type like (T) -> Boolean, it allows the function to accept a lambda or function that operates on elements of the generic type. The compiler infers the type based on the context and ensures type safety throughout the filtering process. Let’s see it in detail.
Consider the filter function, which filters a list based on a provided predicate. Its declaration looks like this:
In this example, <T> is the type parameter, representing the unknown type that will be specified when calling the function. The List<T> is the receiver type, indicating that this function is an extension function for lists. The predicate parameter has the function type (T) -> Boolean, which means it accepts a function that takes a parameter of type T and returns a Boolean value.
When using the filter function, you can provide a lambda expression as the predicate. The compiler infers the type based on the function’s declaration and the type of the list being filtered. Here’s an example:
Kotlin
val authors = listOf("Stan Lee", "J.K.Rowling")val readers = mutableListOf<String>(/* ... */)val filteredReaders = readers.filter { it !in authors }
In this case, the lambda expression { it !in authors } is used as the predicate. The compiler determines that the lambda parameter it has the type T, which is inferred to be String because the filter function is called on List<String> (readers).
By utilizing the generic type parameter T in the function declaration, the filter function can work with any type of list and apply the provided predicate accordingly.
Generic Extension Properties
Similar to generic functions, you can declare generic extension properties using the same syntax. For example, let’s define an extension property penultimate that returns the element before the last one in a list:
Kotlin
val <T> List<T>.penultimate: T// This generic extension property can be called on a list of any kindget() = this[size - 2]
In this case, <T> is the type parameter, and it is part of the receiver type List<T>. The get() function provides the implementation for retrieving the penultimate element.
You can then use the penultimate property on a list:
Kotlin
println(listOf(1, 2, 3, 4).penultimate) //The type parameter T is inferred to be Int in this invocation// Output: 3
Note that generic non-extension properties are not allowed in Kotlin. Regular (non-extension) properties cannot have type parameters because they are associated with a specific class or obect and cannot store multiple values of different types.
If you attempt to declare a generic non-extension property, the compiler will report an error similar to the following:
Kotlin
val <T> x: T = TODO()// ERROR: type parameter of a property must be used in its receiver type
The error message indicates that the type parameter T used in the property declaration should be associated with its receiver type, but since regular properties are specific to a class or object, it doesn’t make sense to have a generic property that can accommodate multiple types.
Declaring generic classes
Just like in Java, in Kotlin also, you can declare generic classes and interfaces by using angle brackets after the class name and specifying the type parameters within the angle brackets. These type parameters can then be used within the body of the class, just like any other types.
For example, let’s consider the declaration of the standard Java interface List in Kotlin:
In this example, List is declared as a generic interface with a type parameter T. You can use this type parameter in the interface’s methods and other declarations.
When a class extends a generic class or implements a generic interface, you need to provide a type argument for the generic parameter of the base type. This type argument can be a specific type or another type parameter.
In the first example, the StringList class extends List<String>, which means it specifically contains String elements. The get function in StringList will have the signature fun get(index: Int): String instead of fun get(index: Int): T.
In the second example, the ArrayList class defines its own type parameter T and specifies it as the type argument for the superclass List<T>.
Note that T in ArrayList is not the same as in List — it’s a new type parameter, and it doesn’t need to have the same name.
Additionally, a class can even refer to itself as a type argument. Classes implementing the Comparable interface are the classical example of this pattern. Any comparable element must define how to compare it with objects of the same type:
In this example, the String class implements the generic Comparable interface by specifying String as the type argument for the type parameter T.
Type parameter constraints
Type parameter constraints in Kotlin allow you to limit the types that can be used as arguments for a class or function. This ensures that only specific types or their subtypes can be used.
When you specify a type as an upper bound constraint for a type parameter of a generic type, the corresponding type arguments in specific instantiations of the generic type must be either the specified type or its subtypes(For now, you can think of subtype as a synonym for subclass).
To specify a constraint, you put a colon after the type parameter name, followed by the type that’s the upper bound for the type parameter. In Java, you use the keyword extends to express the same concept: T sum(List list)
Let’s start with an example. Suppose we have a function called sum that calculates the sum of elements in a list. We want this function to work with List<Int> or List<Double>, but not with List<String>. To achieve this, we can define a type parameter constraint that specifies the type parameter of sum must be a number.
Kotlin
fun <T : Number> sum(list: List<T>): T {var result = 0.0for (element in list) { result += element.toDouble() }return result as T}
In this example, we specify <T : Number> as the type parameter constraint, indicating that T must be a subclass of Number. Now, when we invoke the function with a list of integers, it works correctly:
Kotlin
println(sum(listOf(1, 2, 3))) // Output: 6
The type argument Int extends Number, so it satisfies the type parameter constraint.
You can also use methods defined in the class used as the bound for the type parameter constraint. Here’s an example:
In this case, T is constrained to be a subclass of Number, so we can use methods defined in the Number class, such as toDouble().
Now let’s consider another example where we want to find the maximum of two items. Since it’s only possible to compare items that are comparable to each other, we need to specify that requirement in the function signature using the Comparable interface:
Kotlin
fun <T : Comparable<T>> max(first: T, second: T): T {returnif (first > second) first else second}println(max("kotlin", "java")) // Output: kotlin
In this case, we specify <T : Comparable<T>> as the type parameter constraint. It ensures that T can only be a type that implements the Comparable interface. Hence, we can compare first and second using the > operator.
If you try to call max with incomparable items, such as a string and an integer, it won’t compile:
Kotlin
println(max("kotlin", 42)) // ERROR: Type parameter bound for T is not satisfied
The error occurs because the type argument Any inferred for T is not a subtype of Comparable<Any>, which violates the type parameter constraint.
In some cases, you may need to specify multiple constraints on a type parameter. You can use a slightly different syntax for that. Here’s an example where we ensure that the given CharSequence has a period at the end and can be appended:
Kotlin
fun <T> ensureTrailingPeriod(seq: T)where T : CharSequence, T : Appendable {if (!seq.endsWith('.')) { seq.append('.') }}val helloWorld = StringBuilder("Hello World")ensureTrailingPeriod(helloWorld)println(helloWorld) // Output: Hello World.
In this case, we specify the constraints T : CharSequence and T : Appendable using the where clause. This ensures that the type argument must implement both the CharSequence and Appendable interfaces, allowing us to use operations like endsWith and append on values of that type
Type parameter constraints are also commonly used when you want to declare a non-null type parameter. This helps enforce that the type argument cannot be nullable, ensuring that you always have a non-null value. Let’s see it in more detail.
Making type parameters non-null
In Kotlin, when you declare a generic class or function, you can substitute any type argument, including nullable types, for its type parameters. By default, a type parameter without an upper bound specified will have the upper bound of Any? which means it can accept both nullable and non-nullable types.
Let’s take an example to understand this. Consider the Processor class defined as follows:
Kotlin
classProcessor<T> {funprocess(value: T) {value?.hashCode() // value” is nullable, so you have to use a safe call }}
In the process function of this class, the parameter value is nullable, even though T itself is not marked with a question mark. This is because specific instantiations of the Processor class can use nullable types for T. For example, you can create an instance of Processor<String?> which allows nullable strings as its type argument:
Kotlin
val nullableStringProcessor = Processor<String?>() // String?, which is a nullable type, is substituted for TnullableStringProcessor.process(null) // This code compiles fine, having “null” as the “value” argument
If you want to ensure that only non-null types can be substituted for the type parameter, you can specify a constraint or an upper bound. If the only restriction you have is nullability, you can use Any as the upper bound instead of the default Any?. Here’s an example:
Kotlin
classProcessor<T : Any> { // Specifying a non-“null” upper boundfunprocess(value: T) {value.hashCode() // “value” of type T is now non-“null” }}
In this case, the <T : Any> constraint ensures that the type T will always be a non-nullable type. If you try to use a nullable type as the type argument, like Processor<String?>(), the compiler will produce an error. The reason is that String? is not a subtype of Any (it’s a subtype of Any?, which is a less specific type):
Kotlin
val nullableStringProcesor = Processor<String?>()// Error: Type argument is not within its bounds: should be subtype of 'Any'
It’s worth noting that you can make a type parameter non-null by specifying any non-null type as an upper bound, not only Any. This allows you to enforce stricter constraints based on your specific needs.
Underscore operator ( _ ) for type arguments
The underscore operator _ in Kotlin is a type inference placeholder that allows the Kotlin compiler to automatically infer the type of an argument based on the context and other explicitly specified types.
Kotlin
abstractclassSomeClass<T> {abstractfunexecute() : T}classSomeImplementation : SomeClass<String>() {overridefunexecute(): String = "Test"}classOtherImplementation : SomeClass<Int>() {overridefunexecute(): Int = 42}objectRunner {inlinefun <reifiedS: SomeClass<T>, T> run() : T {return S::class.java.getDeclaredConstructor().newInstance().execute() }}funmain() {// T is inferred as String because SomeImplementation derives from SomeClass<String>val s = Runner.run<SomeImplementation, _>()assert(s == "Test")// T is inferred as Int because OtherImplementation derives from SomeClass<Int>val n = Runner.run<OtherImplementation, _>()assert(n == 42)}
Don’t worry! Let’s break down the code step by step:
In this code, we have an abstract class called SomeClass with a generic type T. It declares an abstract function execute() that returns an object of type T.
We have a class called SomeImplementation which extends SomeClass and specifies the generic type as String. It overrides the execute() function and returns the string value "Test".
Similarly, we have another class called OtherImplementation which extends SomeClass and specifies the generic type as Int. It overrides the execute() function and returns the integer value 42.
Below that, we have an object called Runner with a function run(). This function is generic and has two type parameters S and T. It uses the reified keyword to access the type information at runtime. Inside the function, it creates an instance of the specified class S using reflection (getDeclaredConstructor().newInstance()) and calls the execute() function on it, returning the result of type T.
In the above code, the underscore operator is used in the main() function when calling the Runner.run() function. Let’s take a closer look:
Kotlin
val s = Runner.run<SomeImplementation, _>()
In this line, the type parameter T is explicitly specified as _ for the Runner.run() function. Here, _ acts as a placeholder for the type to be inferred by the compiler. Since SomeImplementation derives from SomeClass<String>, the compiler infers T as String for this invocation. Therefore, the variable s is inferred to be of type String, and the Runner.run() function returns the result of executing SomeImplementation, which is the string "Test".
Kotlin
val n = Runner.run<OtherImplementation, _>()
Similarly, in this line, the type parameter T is specified as _ for the Runner.run() function. Since OtherImplementation derives from SomeClass<Int>, the compiler infers T as Int for this invocation. Consequently, the variable n is inferred to be of type Int, and the Runner.run() function returns the result of executing OtherImplementation, which is the integer 42.
By using the underscore operator _ as a type argument, the compiler can automatically infer the appropriate type based on the context and the explicitly specified types.
BTW, how do generics work at runtime?
In Kotlin, generics are a compile-time feature rather than a runtime feature. This means that type information is erased at runtime and not available for inspection or manipulation by the program.
When you use generics in Kotlin, the compiler performs type checking and ensures type safety at compile time. It enforces that the correct types are used in generic functions or classes based on the type parameters specified.
At runtime, Kotlin uses type erasure to remove the generic type information. This is done for compatibility with Java, as both languages share a common runtime environment known as the Java Virtual Machine (JVM). The JVM does not natively support reified generics, which would allow for preserving type information at runtime.
Due to type erasure, you cannot directly access the type parameters of a generic class or function at runtime. For example, if you define a List<String> and a List<Int>, they both become List<Any> at runtime.
However, there are cases where Kotlin provides a workaround for working with generics at runtime using reified types. The reified keyword can be used in inline functions to retain type information within the body of the function. This allows you to perform type checks or access the class instance of the type parameter within the function.
Here’s an example of an inline function that utilizes reified types to perform type checks at runtime:
Kotlin
inlinefun <reifiedT> getType(obj: T) {if (obj is T) {println("Object is of type ${T::class.simpleName}") } else {println("Object is not of type ${T::class.simpleName}") }}
In this example, the reified T declaration allows you to access the class instance of the type parameter T using T::class. This wouldn’t be possible without the reified keyword.
Please note that although reified types enable limited runtime access to type information, they only work within the scope of inline functions. Outside of inline functions, the type information is still erased at runtime.
Variance: generics and subtyping
The concept of variance describes how types with the same base type and different type arguments relate to each other: for example, List<String> and List<Any>. It’s important to understand variance when working with generic classes or functions because it helps ensure the safety and consistency of your code.
Why variance exists: passing an argument to a function
To illustrate why variance is important, let’s consider passing arguments to functions. Suppose we have a function that takes a List<Any> as an argument. Is it safe to pass a variable of type List<String> to this function?
In the case of a function that prints the contents of the list, such as:
You can safely pass a list of strings (List<String>) to this function. Each element in the list is treated as an Any, and since String is a subtype of Any, it is considered safe.
However, let’s consider another function that modifies the list:
If you attempt to pass a list of strings (MutableList<String>) to this function, like so:
Kotlin
val strings = mutableListOf("abc", "bac")addAnswer(strings)println(strings.maxBy { it.length })
You will encounter a ClassCastException at runtime. This occurs because the function addAnswer tries to add an integer (42) to a list of strings. If the compiler allowed this, it would lead to a type inconsistency when accessing the contents of the list as strings. To prevent such issues, the Kotlin compiler correctly forbids passing a MutableList<String> as an argument when a MutableList<Any> is expected.
So, the answer to whether it’s safe to pass a list of strings to a function expecting a list of Any objects depends on whether the function modifies the list. If the function only reads the list, it is safe to pass a List with a more specific element type. However, if the list is mutable and the function adds or replaces elements, it is not safe.
Kotlin provides different interfaces, such as List and MutableList, to control safety based on mutability. If a function accepts a read-only list, you can pass a List with a more specific element type. However, if the list is mutable, you cannot do that.
In the upcoming sections, we’ll explore these concepts in the context of generic classes. We’ll also examine why List and MutableList differ regarding their type arguments. But before that, let’s discuss the concepts of type and subtype.
Difference between Classes, types, and subtypes
In Kotlin, the type of a variable specifies the possible values it can hold. The terms “type” and “class” are sometimes used interchangeably, but they have distinct meanings. In the case of a non-generic class, the class name can be used directly as a type. For example, var x: String declares a variable that can hold instances of the String class. However, the same class name can also be used to declare a nullable type, such as var x: String?which indicates that the variable can hold either a String or null. So each Kotlin class can be used to construct at least two types.
When it comes to generic classes, things get more complex. To form a valid type, you need to substitute a specific type as a type argument for the class’s type parameter. For example, List is a class, not a type itself, but the following substitutions are valid types: List<Int>, List<String?>, List<List<String>>, and so on. Each generic class can generate an infinite number of potential types.
To discuss the relationship between types, it’s important to understand the concept of subtyping. Type B is considered a subtype of type A if you can use a value of type B wherever a value of type A is expected. For example, Int is a subtype of Number, but Int is not a subtype of String. Note that a type is considered a subtype of itself. The term “supertype” is the opposite of subtype: if A is a subtype of B, then B is a supertype of A.
Understanding subtype relationships is crucial because the compiler performs checks whenever you assign a value to a variable or pass an argument to a function. For example:
Storing a value in a variable is only allowed if the value’s type is a subtype of the variable’s type. In this case, since Int is a subtype of Number, the declaration val n: Number = i is valid. Similarly, passing an expression to a function is only allowed if the expression’s type is a subtype of the function’s parameter type. In the example, the type Int of the argument i is not a subtype of the function parameter type String, so the invocation of the f function does not compile.
In simpler cases, subtype is essentially the same as subclass. For example, Int is a subclass of Number, so the Int type is a subtype of the Number type. If a class implements an interface, its type is a subtype of the interface type. For instance, String is a subtype of CharSequence.
Nullable types introduce a scenario where subtype and subclass differ. A non-null type is a subtype of its corresponding nullable type, but they both correspond to the same class.
You can store the value of a non-null type in a variable of a nullable type, but not vice versa. For example:
Kotlin
val s: String = "abc"val t: String? = s
In this case, the value of the non-null type String can be stored in a variable of the nullable type String?. However, you cannot assign a nullable type to a non-null type because null is not an acceptable value for a non-null type.
The distinction between subclasses and subtypes becomes particularly important when dealing with generic types. This brings us back to the question from the previous section: is it safe to pass a variable of type List<String> to a function expecting List<Any>? We’ve already seen that treating MutableList<String> as a subtype of MutableList<Any> is not safe. Similarly, MutableList<Any> is not a subtype of MutableList<String> either.
A generic class, such as MutableList, is called invariant on the type parameter if, for any two different types A and B, MutableList<A> is neither a subtype nor a supertype of MutableList<B>. In Java, all classes are invariant, although specific uses of those classes can be marked as non-invariant (as you’ll see soon).
In the previous section, we encountered a class, List, where the subtyping rules are different. The List interface in Kotlin represents a read-only collection. If type A is a subtype of type B, then List<A> is a subtype of List<B>. Such classes or interfaces are called covariant. In the upcoming sections, we’ll explore the concept of covariance in more detail and explain when it’s possible to declare a class or interface as covariant.
Covariance: preserved subtyping relation
Covariance refers to preserving the subtyping relation between generic classes. In Kotlin, you can declare a class to be covariant on a specific type parameter by using the out keyword before the type parameter’s name.
A covariant class is a generic class (we’ll use Producer as an example) for which the following holds: Producer<A> is a subtype of Producer<B> if A is a subtype of B. We say that the subtyping is preserved. For example, Producer<Cat> is a subtype of Producer<Animal> because Cat is a subtype of Animal.
Here’s an example of the Producer interface using the out keyword:
Kotlin
interfaceProducer<outT> {funproduce(): T}
Flexible Function Argument and Return Value Passing
Covariance in Kotlin allows you to pass values of a class as function arguments and return values, even when the type arguments don’t exactly match the function definition. This means that you can use a more specific type as a substitute for a more generic type.
Suppose we have a hierarchy of classes involving Animal, where Cat is a subclass of Animal. We also have a generic interface called Producer, which represents a producer of objects of type T. We’ll make the Producer interface covariant by using the out keyword on the type parameter.
Kotlin
interfaceProducer<outT> {funproduce(): T}
Now, let’s define a class AnimalProducer that implements the Producer interface for the Animal type:
Since Cat is a subtype of Animal, we can also use CatProducer wherever a Producer<Animal> is expected. This is possible because we declared the Producer interface as covariant.
Now, let’s see how covariance allows us to pass these producers as function arguments and return values:
funmain() {val animalProducer = AnimalProducer()val catProducer = CatProducer()feedAnimal(animalProducer) // Passes an AnimalProducer, which is a Producer<Animal>feedAnimal(catProducer) // Passes a CatProducer, which is also a Producer<Animal>}
In the feedAnimal function, we expect a Producer<Animal> as an argument. With covariance, we can pass both AnimalProducer and CatProducer instances because Producer<Cat> is a subtype of Producer<Animal> due to the covariance declaration.
This demonstrates how covariance allows you to treat more specific types (Producer<Cat>) as if they were more generic types (Producer<Animal>) when it comes to function arguments and return values.
BTW, How covariance guarantees type safety?
Suppose we have a class hierarchy involving Animal, where Cat is a subclass of Animal. We also have a Herd class that represents a group of animals.
Kotlin
openclassAnimal {funfeed() { /* feeding logic */ }}classHerd<T : Animal> { // The type parameter isn’t declared as covariantval size: Intget() = /* calculate the size of the herd */operatorfunget(i: Int): T { /* get the animal at index i */ }}funfeedAll(animals: Herd<Animal>) {for (i in0 until animals.size) { animals[i].feed() }}
Now, suppose you have a function called takeCareOfCats, which takes a Herd<Cat> as a parameter and performs some operations specific to cats.
Kotlin
classCat : Animal() {funcleanLitter() { /* clean litter logic */ }}funtakeCareOfCats(cats: Herd<Cat>) {for (i in0 until cats.size) { cats[i].cleanLitter()// feedAll(cats) // This line would cause a type-mismatch error, Error: inferred type is Herd<Cat>, but Herd<Animal> was expected }}
In this case, if you try to pass the cats herd to the feedAll function, you’ll get a type-mismatch error during compilation. This happens because you didn’t use any variance modifier on the type parameter T in the Herd class, making the Herd<Cat> incompatible with Herd<Animal>. Although you could use an explicit cast to overcome this issue, it is not a recommended approach.
To make it work correctly, you can make the Herd class covariant by using the out keyword on the type parameter:
Kotlin
classHerd<outT : Animal> { // The T parameter is now covariant.// ...}funtakeCareOfCats(cats: Herd<Cat>) {for (i in0 until cats.size) { cats[i].cleanLitter() }feedAll(cats) // Now this line works because of covariance, You don’t need a cast.
By marking the type parameter as covariant, you ensure that the subtyping relation is preserved, and T can only be used in \”out\” positions. This guarantees type safety and allows you to pass a Herd<Cat> where a Herd<Animal> is expected.
Usage of covariance
Covariance in Kotlin allows you to make a class covariant on a type parameter, but it also imposes certain constraints to ensure type safety. The type parameter can only be used in “out” positions, which means it can produce values of that type but not consume them.
You can’t make any class covariant: it would be unsafe. Making the class covariant on a certain type parameter constrains the possible uses of this type parameter in the class. To guarantee type safety, it can be used only in so-called out positions, meaning the class can produce values of type T but not consume them. Uses of a type parameter in declarations of class members can be divided into “in” and “out” positions.
Let’s consider a class that declares a type parameter T and contains a function that uses T. We say that if T is used as the return type of a function, it’s in the out position. In this case, the function produces values of type T. If T is used as the type of a function parameter, it’s in the in position. Such a function consumes values of type T.
The out keyword on a type parameter of the class requires that all methods using T have T only in “out” positions and not in “in” positions. This keyword constrains possible use of T, which guarantees safety of the corresponding subtype relation.
Let’s understand this with some examples. Consider the Herd class, which is declared as Herd<out T : Animal>. The type parameter T is used only in the return value of the get method. This is an “out” position, and it is safe to declare the class as covariant. For instance, Herd<Cat> is considered a subtype of Herd<Animal> because Cat is a subtype of Animal.
Kotlin
classHerd<outT : Animal> {val size: Int = ...operatorfunget(i: Int): T { ... } // Uses T as the return type}
Similarly, the List<T> interface in Kotlin is covariant because it only defines a get method that returns an element of type T. Since there are no methods that store values of type T, it is safe to declare the class as covariant.
Kotlin
interfaceList<outT> : Collection<T> {operatorfunget(index: Int): T// Read-only interface that defines only methods that return T (so T is in the “out” position)// ...}
You can also use the type parameter T as a type argument in another type. For example, the subList method in the List interface returns a List<T>, and T is used in the “out” position.
Kotlin
interfaceList<outT> : Collection<T> {funsubList(fromIndex: Int, toIndex: Int): List<T> // Here T is in the “out” position as well.// ...}
However, you cannot declare MutableList<T> as covariant on its type parameter because it contains methods that both consume and produce values of type T. Therefore, T appears in both “in” and “out” positions, and making it covariant would be unsafe.
Kotlin
interfaceMutableList<T> : List<T>, MutableCollection<T> { //MutableList can’t be declared as covariant on T …overridefunadd(element: T): Boolean// … because T is used in the “in” position.}
The compiler enforces this restriction. It would report an error if the class was declared as covariant: Type parameter T is declared as ‘out’ but occurs in ‘in’ position.
Constructor Parameters and Variance
In Kotlin, constructor parameters are not considered to be in the “in” or “out” position when it comes to variance. This means that even if a type parameter is declared as “out,” you can still use it in a constructor parameter declaration without any restrictions.
The type parameter T is declared as “out” but it can still be used in the constructor parameter vararg animals: T without any issues. The variance protection is not applicable to the constructor because it is not a method that can be called later, so there are no potentially dangerous method calls that need to be restricted.
However, if you use the val or var keyword with a constructor parameter, it declares a property with a getter and setter (if the property is mutable). In this case, the type parameter T is used in the “out” position for a read-only property and in both “out” and “in” positions for a mutable property.
Here, the type parameter T cannot be marked as “out” because the class contains a setter for the leadAnimal property, which uses T in the “in” position. The presence of a setter makes it necessary to consider both “out” and “in” positions for the type parameter.
It’s important to note that the position rules for variance in Kotlin only apply to the externally visible API of a class, such as public, protected, and internal members. Parameters of private methods are not subject to the “in” or “out” position rules. The variance rules are in place to protect a class from misuse by external clients and do not affect the implementation of the class itself.
In this case, the Herd class can safely be made covariant on T because the leadAnimal property has been made private. The private visibility means that the property is not accessible from external clients, so the variance rules for the public API do not apply.
Contravariance: reversed subtyping relation
Contravariance is the opposite of covariance and it can be understood as a mirror image of covariance. When a class is contravariant, the subtyping relationship between its type arguments is the reverse of the subtyping relationship between the classes themselves.
To illustrate this concept, let’s consider the example of the Comparator interface. This interface has a single method called compare, which takes two objects and compares them:
Kotlin
interfaceComparator<inT> {funcompare(e1: T, e2: T): Int { ... }}
In this case, you’ll notice that the compare method only consumes values of type T. This means that the type parameter T is used in “in” positions only, indicating that it is a contravariant type. To indicate contravariance, the “in” keyword is placed before the declaration of T.
A comparator defined for values of a certain type can, of course, compare the values of any subtype of that type. For example, if you have a Comparator, you can use it to compare values of any specific type.
Kotlin
val anyComparator = Comparator<Any> { e1, e2 -> e1.hashCode() - e2.hashCode() }val strings: List<String> = listOf("abc","xyz")strings.sortedWith(anyComparator) // You can use the comparator for any objects to compare specific objects, such as strings.
Here, the sortedWith function expects a Comparator (a comparator that can compare strings), and it’s safe to pass one that can compare more general types. If you need to perform comparisons on objects of a certain type, you can use a comparator that handles either that type or any of its supertypes. This means Comparator<Any> is a subtype of Comparator<String>, where Any is a supertype of String. The subtyping relation between comparators for two different types goes in the opposite direction of the subtyping relation between those types.
What is contravariance?
A class that is contravariant on the type parameter is a generic class (let’s consider Consumer<T> as an example) for which the following holds: Consumer<A> is a subtype of Consumer<B> if B is a subtype of A. The type arguments A and B changed places, so we say the subtyping is reversed. For example, Consumer<Animal> is a subtype of Consumer<Cat>.
In simple words, contravariance in Kotlin means that the subtyping relationship between two generic types is reversed compared to the normal inheritance hierarchy. If B is a subtype of A, then a generic class or interface that is contravariant on its type parameter T will have the relationship ClassName<A> is a subtype of ClassName<B>.
Here, we see the difference between the subtyping relation for classes that are covariant and contravariant on a type parameter. You can see that for the Producer class, the subtyping relation replicates the subtyping relation for its type arguments, whereas for the Consumer class, the relation is reversed.
The “in” keyword means values of the corresponding type are passed in to methods of this class and consumed by those methods. Similar to the covariant case, constraining use of the type parameter leads to the specific subtyping relation. The “in” keyword on the type parameter T means the subtyping is reversed and T can be used only in “in” positions.
Covariance and Contravariance in Kotlin’s Function Types
In Kotlin, a class or interface can be covariant on one type parameter and contravariant on another. One of the classic examples of this is the Function interface. Let’s take a look at the declaration of the Function1 interface, which represents a one-parameter function:
To make the notation more readable, Kotlin provides an alternative syntax (P) -> R to represent Function1<P, R>. In this syntax, you’ll notice that P (the parameter type) is used only in the in position and is marked with the in keyword, while R (the return type) is used only in the out position and is marked with the out keyword.
This means that the subtyping relationship for the function type is reversed for the first type argument (P) and preserved for the second type argument (R).
For example, let’s say you have a higher-order function called enumerateCats that accepts a lambda function taking a Cat parameter and returning a Number:
Kotlin
funenumerateCats(f: (Cat) -> Number) { ... }
Now, suppose you have a function called getIndex defined in the Animal class that returns an Int. You can pass Animal::getIndex as an argument to enumerateCats:
Kotlin
funAnimal.getIndex(): Int = ...enumerateCats(Animal::getIndex) // This code is legal in Kotlin. Animal is a supertype of Cat, and Int is a subtype of Number
In this case, the Animal::getIndex function is accepted because Animal is a supertype of Cat, and Int is a subtype of Number, the function type’s subtyping relationship allows it.
This illustration demonstrates how subtyping works for function types. The arrows indicate the subtyping relationship.
Use-site variance: specifying variance for type occurrences
To understand use-site variance better, you first need to understand declaration-site variance. In Kotlin, the ability to specify variance modifiers on class declarations provides convenience and consistency because these modifiers apply to all places where the class is used. This concept is known as a declaration-site variance.
Declaration-site variance in Kotlin is achieved by using variance modifiers on type parameters when defining a class. As you already knows there are two main variance modifiers:
out (covariant): Denoted by the out keyword, it allows the type parameter to be used as a return type or read-only property. It specifies that the type parameter can only occur in the “out” position, meaning it can only be returned from functions or accessed in a read-only manner.
in (contravariant): Denoted by the in keyword, it allows the type parameter to be used as a parameter type. It specifies that the type parameter can only occur in the “in” position, meaning it can only be passed as a parameter to functions.
By specifying these variance modifiers on type parameters, you define the variance behavior of the class, and it remains consistent across all usages of the class.
On the other hand, Java handles variance differently through use-site variance. In Java, each usage of a type with a type parameter can specify whether the type parameter can be replaced with its subtypes or supertypes using wildcard types (? extends and ? super). This means that at each usage point of the type, you can decide the variance behavior.
It’s important to note that while Kotlin supports declaration-site variance with the out and in modifiers, it also provides a certain level of use-site variance through the out and in projection syntax (out T and in T). These projections allow you to control the variance behavior in specific usage points within the code.
Declaration-site variance in Kotlin Vs. Java wildcards
In Kotlin, declaration-site variance allows for more concise code because variance modifiers are specified once on the declaration of a class or interface. This means that clients of the class or interface don’t have to think about the variance modifiers. The convenience of declaration-site variance is that the variance behavior is determined at the point of declaration and remains consistent throughout the codebase.
On the other hand, in Java, wildcards are used to handle variance at the use site. To create APIs that behave according to users’ expectations, the library writer has to use wildcards extensively. For example, in the Java 8 standard library, wildcards are used on every use of the Function interface. This can lead to code like Function<? super T, ? extends R> in method signatures.
To illustrate the declaration of the map method in the Stream interface in Java :
In the Java code, wildcards are used in the declaration of the map method to handle the variance of the function argument. This can make the code less readable and more cumbersome, especially when dealing with complex type hierarchies.
In contrast, the Kotlin code uses declaration-site variance, specifying the variance once on the declaration makes the code much more concise and elegant.
BTW, How does use-site variance work in Kotlin?
Kotlin supports use-site variance, you can specify variance at the use site, which means you can indicate the variance for a specific occurrence of a type parameter, even if it can’t be declared as covariant or contravariant in the class declaration. Let’s break down the concepts and see how use-site works.
In Kotlin, many interfaces, like MutableList, are not covariant or contravariant by default because they can both produce and consume values of the types specified by their type parameters. However, in certain situations, a variable of that type may be used only as a producer or only as a consumer.
Consider the function copyData that copies elements from one collection to another:
Kotlin
fun <T> copyData(source: MutableList<T>, destination: MutableList<T>) {for (item in source) { destination.add(item) }}
In this function, both the source and destination collections have an invariant type. However, the source collection is only used for reading, and the destination collection is only used for writing. In this case, the element types of the collections don’t need to match exactly.
To make this function work with lists of different types, you can introduce a second generic parameter:
Kotlin
fun <T : R, R> copyData(source: MutableList<T>, destination: MutableList<R>) {for (item in source) { destination.add(item) }}
In this modified version, you declare two generic parameters representing the element types in the source and destination lists. The source element type (T) should be a subtype of the elements in the destination list (R).
However, Kotlin provides a more elegant way to express this using use-site variance. If the implementation of a function only calls methods that have the type parameter in the “out” position (as a producer) or only in the “in” position (as a consumer), you can add variance modifiers to the particular usages of the type parameter in the function definition.
For example, you can modify the copyData function as follows:
Kotlin
fun <T> copyData(source: MutableList<outT>, destination: MutableList<T>) {for (item in source) { destination.add(item) }}
In this version, you specify the out modifier for the source parameter, which means it’s a projected (restricted) MutableList. You can only call methods that return the generic type parameter (T) or use it in the “out” position. The compiler prohibits calling methods where the type parameter is used as an argument (“in” position).
When using use-site variance in Kotlin, there are limitations on the methods that can be called on a projected type. If you are using a projected type, you may not be able to call certain methods that require the type parameter to be used as an argument (“in” position) :
Kotlin
val list: MutableList<outNumber> = ..list.add(42) // Error: Out-projected type 'MutableList<out Number>' prohibits the use of 'fun add(element: E): Boolean'
Here, list is declared as a MutableList<out Number>, which is an out-projected type. The out projection restricts the type parameter Number to only be used in the “out” position, meaning it can only be used as a return type or read from. You cannot call the add method because it requires the type parameter to be used as an argument (“in” position).
If you need to call methods that are prohibited by the projection, you should use a regular type instead of a projection. In this case, you can use MutableList<Number> instead of MutableList<out Number>. By using the regular type, you can access all the methods available for that type.
Regarding the concept of using the in modifier, it indicates that in a particular location, the corresponding value acts as a consumer, and the type parameter can be substituted with any of its supertypes. This is similar to the contravariant position in Java’s bounded wildcards.
For example, the copyData function can be rewritten using an in-projection:
Kotlin
fun <T> copyData(source: MutableList<T>, destination: MutableList<inT>) {for (item in source) { destination.add(item) }}
In this version, the destination parameter is projected with the in modifier, indicating that it can consume elements of type T or any of its supertypes. This allows you to copy elements from the source list to a destination list with a broader type.
It’s important to note that use-site variance declarations in Kotlin correspond directly to Java’s bounded wildcards. MutableList<out T> in Kotlin is equivalent to MutableList<? extends T> in Java, while the in-projected MutableList<in T> corresponds to Java’s MutableList<? super T>.
Use-site projections in Kotlin can help widen the range of acceptable types and provide more flexibility when working with generic types, without the need for separate covariant or contravariant interfaces.
Star projection: using * instead of a type argument
In Kotlin, star projection is a syntax that allows you to indicate that you have no information about a generic argument. It is represented by the asterisk (*) symbol. Let’s explore the semantics of star projections in more detail.
When you use star projection, such as List<*>, it means you have a list of elements of an unknown type. It’s important to note that MutableList<*> is not the same as MutableList<Any?>. The former represents a list that contains elements of a specific type, but you don’t know what type it is. You can’t put any values into the list because it may violate the expectations of the calling code. However, you can retrieve elements from the list because you know they will match the type Any?, which is the supertype of all Kotlin types.
Here’s an example to illustrate this:
Kotlin
val list: MutableList<Any?> = mutableListOf('a', 1, "qwe")val chars = mutableListOf('a', 'b', 'c')val unknownElements: MutableList<*> = if (Random().nextBoolean()) list else charsunknownElements.add(42) // Error: Adding elements to a MutableList<*> is not allowedprintln(unknownElements.first()) // You can retrieve elements from unknownElements
In this example, unknownElements can be either list or chars based on a random condition. You can’t add any values to unknownElements because its type is unknown, but you can retrieve elements from it using the first() function.
Kotlin
unknownElements.add(42)// Error: Out-projected type 'MutableList<*>' prohibits//the use of 'fun add(element: E): Boolean'
The term “out-projected type” refers to the fact that MutableList<*> is projected to act as MutableList<out Any?>. It means you can safely get elements of type Any? from the list but cannot put elements into it.
For contravariant type parameters, like Consumer<in T>, a star projection is equivalent to <in Nothing>. In this case, you can’t call any methods that have T in the signature on a star projection because you don’t know exactly what it can consume. This is similar to Java’s wildcards (MyType<?> in Java corresponds to MyType<*> in Kotlin).
You can use star projections when the specific information about type arguments is not important. For example, if you only need to read the data from a list or use methods that produce values without caring about their specific types. Here’s an example of a function that takes List<*> as a parameter:
In this case, the printFirst function only reads the first element of the list and doesn’t care about its specific type. Alternatively, you can introduce a generic type parameter if you need more control over the type:
Kotlin
fun <T> printFirst(list: List<T>) {if (list.isNotEmpty()) {println(list.first()) }}
The syntax with star projection is more concise, but it works only when you don’t need to access the exact value of the generic type parameter.
Now let’s consider an example using a type with a star projection and common traps that you may encounter. Suppose you want to validate user input using an interface called FieldValidator. It has a type parameter declared as contravariant (in T). You also have two validators for String and Int inputs.
If you want to store all validators in the same container and retrieve the right validator based on the input type, you might try using a map. However, using FieldValidator<*> as the value type in the map can lead to difficulties. You won’t be able to validate a string with a validator of type FieldValidator<*> because the compiler doesn’t know the specific type of the validator.
Kotlin
val validators = mutableMapOf<KClass<*>, FieldValidator<*>>()validators[String::class] = DefaultStringValidatorvalidators[Int::class] = DefaultIntValidatorvalidators[String::class]!!.validate("") // Error: Cannot call validate() on FieldValidator<*>
In this case, you will encounter a similar error as before, indicating that it’s unsafe to call a method with the type parameter on a star projection. One way to work around this is by explicitly casting the validator to the desired type, but this is not recommended as it is not type-safe.
Kotlin
val stringValidator = validators[String::class] as FieldValidator<String>println(stringValidator.validate("")) // Output: false
This code compiles, but it’s not safe because the cast is unchecked and may fail at runtime if the generic type information is erased.
A safer approach is to encapsulate the access to the map and provide type-safe methods for registration and retrieval. This ensures that only the correct validators can be registered and retrieved. Here’s an example using an object called Validators:
In this example, the Validators object controls all access to the map, ensuring that only correct validators can be registered and retrieved. The code emits a warning about the unchecked cast, but the guarantees provided by the Validators object make sure that no incorrect use can occur.
This pattern of encapsulating unsafe code in a separate place helps prevent misuse and makes the usage of a container safe. It’s worth noting that this pattern is not specific to Kotlin and can be applied in Java as well.
Conclusion
Kotlin generics and variance are powerful tools that enhance type safety and code reusability. Understanding these concepts enables developers to write generic code that can be adapted to different types and relationships between them. By mastering generics and variance, you can build more flexible and robust applications.
In this blog post, we covered the basics of Kotlin generics, explained variance with examples, explored variance annotations, wildcards, type projections, and discussed additional topics such as reified type parameters and generic constraints. With this knowledge, you are well-equipped to utilize generics effectively in your Kotlin projects.
In object-oriented programming, inheritance is a fundamental concept that allows a class to inherit properties and behaviors from its parent class. However, inheritance has its limitations, and sometimes an alternative approach is needed. Kotlin provides native support for the delegation pattern, which is a powerful alternative to implementation inheritance. In this article, we will explore the delegation pattern in Kotlin and its various aspects.
Overview of the Delegation Pattern
The delegation pattern is a design pattern where an object delegates some or all of its responsibilities to another object. Instead of inheriting behavior, an object maintains a reference to another object and forwards method calls to it. This promotes composition over inheritance and provides greater flexibility in reusing and combining behaviors from different objects.
In Kotlin, the delegation pattern is built into the language, making it easy and convenient to implement. With the by keyword, Kotlin allows a class to implement an interface by delegating all of its public members to a specified object. Let’s dive into the details and see how it works.
Basic Usage of Delegation in Kotlin
To understand the basic usage of delegation in Kotlin, let’s consider a simple example. Assume we have an interface called Base with a single function print():
Kotlin
interfaceBase {funprint()}
Next, we define a class BaseImpl that implements the Base interface. It has a constructor parameter x of type Int and provides an implementation for the print() function:
Kotlin
classBaseImpl(val x: Int) : Base {overridefunprint() {println(x) }}
Now, we want to create a class called Derived that also implements the Base interface. Instead of implementing the print() function directly, we can delegate it to an instance of the Base interface. We achieve this by using the by keyword followed by the object reference in the class declaration:
Kotlin
classDerived(b: Base) : Basebyb
In this example, the by clause in the class declaration indicates that b will be stored internally in objects of Derived, and the compiler will generate all the methods of Base that forward to b. This means that the print() function in Derived will be automatically delegated to the print() function of the b object.
To see the delegation in action, let’s create an instance of BaseImpl with a value of 10 and pass it to the Derived class. Then, we can call the print() function on the Derived object:
When we execute the print() function on the Derived object, it internally delegates the call to the BaseImpl object (b), and thus it prints the value 10.
Overriding Methods in DelegationPattern
In Kotlin, when a class implements an interface by delegation, it can also override methods provided by the delegate object. This allows for customization and adding additional behavior specific to the implementing class.
Let’s extend our previous example to understand method overriding in the delegation. Assume we have an interface Base with two functions: printMessage() and printMessageLine():
In this example, the printMessage() function in the Derived class overrides the implementation provided by the delegate object b. When we call printMessage() on an instance of Derived, it will print “softAai Apps” instead of the original implementation.
To test the overridden behavior, we can modify the main() function as follows:
When we call the printMessage() function on the Derived object, it invokes the overridden implementation in the Derived class, and it prints “softAai Apps” instead of 10. However, the printMessageLine() function is not overridden in the Derived class, so it delegates the call to the BaseImpl object, which prints the original value 10 followed by a new line.
Property Delegationin Delegation Pattern
In addition to method delegation, Kotlin also supports property delegation. This allows a class to delegate the implementation of properties to another object. Let’s understand how it works.
Assume we have an interface Base with a read-only property message:
Kotlin
interfaceBase {val message: String}
We modify the BaseImpl class to implement the Base interface with the message property:
Kotlin
classBaseImpl(val x: Int) : Base {overrideval message: String = "BaseImpl: x = $x"}
Now, let’s update the Derived class to delegate the Base interface and override the message property:
In this example, the Derived class delegates the implementation of the Base interface to the b object. However, it overrides the message property and provides its own implementation.
To see the property delegation in action, we can modify the main() function as follows:
Kotlin
funmain() {val b = BaseImpl(10)val derived = Derived(b)println(derived.message) // Output: Message of Derived}
When we access the message property of the Derived object, it returns the overridden value “Message of Derived” instead of the one in the delegate object b.
Advantages of the Delegation Pattern in Kotlin
Code Reusability: Delegation allows for code reuse by delegating responsibilities to another object. This promotes composition over inheritance and allows for the flexible reuse of behavior.
Separation of Concerns: Delegation helps in separating different concerns by assigning specific responsibilities to different objects. This leads to a more modular and maintainable codebase.
Flexibility: Delegation allows for dynamic behavior modification at runtime. By delegating to different objects, you can easily switch or modify behavior as needed without changing the implementing class.
Easy Composition: Delegation makes it straightforward to combine and compose multiple behaviors. Objects can be combined by delegating to multiple objects, allowing for flexible composition of functionalities.
Code Readability: Delegation improves code readability by clearly specifying which object is responsible for which behavior. It enhances code understanding and reduces complexity.
Disadvantages of the Delegation Pattern in Kotlin
Performance Overhead: Delegation adds a level of indirection, which can introduce a slight performance overhead. Each method call needs to be forwarded to the delegate object, which can impact performance in performance-critical scenarios.
Increased Complexity: Delegation can introduce additional complexity, especially when multiple levels of delegation are involved. Understanding the flow of method calls and responsibilities might require careful analysis.
Potential Code Duplication: If multiple classes implement the same interface using delegation, there is a possibility of code duplication. Each class might need to provide its own implementation, even if the behavior is similar across implementations.
Limited Access to Internal State: When using delegation, accessing the internal state or members of the delegate object might become more complex. If the delegate object exposes limited or no access to its internal state, it can limit the flexibility of the implementing class.
Learning Curve: Understanding and utilizing the delegation pattern might require some learning and understanding of the concept. Developers who are not familiar with delegation might require additional effort to grasp the concept and its best practices.
Conclusion
The delegation pattern in Kotlin is a powerful alternative to implementation inheritance. It allows a class to implement an interface by delegating the responsibilities to another object. Kotlin’s by keyword makes it easy to implement delegation without boilerplate code.
In this article, we covered the basics of delegation pattern, including how to delegate methods and properties, and how to override them in the implementing class. We also discussed the limitation of overridden methods not being called from within the delegate object.
By leveraging the delegation pattern, you can achieve code reuse, composition, and flexibility in your Kotlin applications. Understanding and utilizing this pattern can lead to cleaner and more maintainable code.
Remember to consider the delegation pattern when designing your classes and to evaluate whether it provides a better solution compared to traditional implementation inheritance.
Performance is an important element in constructing solid and proficient software applications. Kotlin, a cutting-edge and multifunctional programming language, offers multiple language structures for developers to take advantage of, each with its own performance-related properties. In this blog post, we will go in-depth into the execution efficiency impact of regular classes, sealed classes, and sealed interfaces in Kotlin.
Regular Classes
Regular classes in Kotlin provide the most basic form of class definition. They are open by default, meaning that they can be inherited and extended by other classes. Regular classes allow for polymorphism, encapsulation, and inheritance. In terms of performance, regular classes generally have a minimal impact on runtime efficiency. When instantiating regular classes, there is a slight overhead associated with memory allocation and object initialization. However, this overhead is typically negligible and doesn’t significantly impact performance unless you are creating an excessive number of instances. The method dispatch mechanism used in regular classes is dynamic, which incurs a small runtime cost when invoking methods. Nevertheless, modern virtual machine optimizations often mitigate this performance impact, making regular classes a reliable choice for most scenarios.
Sealed Classes
Sealed classes in Kotlin are used to represent restricted class hierarchies. They provide a way to define a limited set of subclasses that can inherit from them. Sealed classes are declared with the sealed modifier and are typically used in scenarios where you have a predefined set of possible types.
In terms of performance, sealed classes offer a slight trade-off compared to regular classes. The restricted class hierarchy allows the compiler to perform exhaustive when expression checks, which results in more efficient code generation. The when expression, when used with sealed classes, can ensure that all possible subclasses are handled, eliminating the need for a default case. This static analysis leads to improved performance since the compiler can optimize the code based on the exhaustive knowledge of the subclasses. Consequently, sealed classes can offer better runtime efficiency compared to regular classes when used appropriately.
Sealed Interfaces
Sealed interfaces, introduced in Kotlin 1.5, extend the concept of sealed classes to interfaces. They allow developers to define a sealed set of possible implementations for an interface. Sealed interfaces are declared using the sealed modifier and provide a way to restrict the types that can implement them.
From a performance standpoint, sealed interfaces share similar characteristics with sealed classes. The restricted set of implementations allows for exhaustive checks, enabling the compiler to optimize the code by eliminating unnecessary branching and providing improved runtime efficiency. The usage of sealed interfaces can lead to more predictable performance compared to regular interfaces, especially in scenarios where you need to handle a limited number of implementations.
Conclusion
When constructing applications in Kotlin, regular classes, sealed classes, and sealed interfaces provide varied performance benefits depending on the specific utilization. Regular classes are strong, providing minimal performance effects. Sealed classes and sealed interfaces, conversely, introduce a bound class hierarchy or implementation group, permitting more competent code generation and enhanced runtime productivity.
To determine the best construction for the job, it is critical to take into consideration the design prerequisites and balance between agility and performance. Regular classes are a suitable solution for a wide range of circumstances, whereas sealed classes and sealed interfaces are of greater use when there is a minimal number of known subclasses or executions.
It is imperative to understand that optimizing performance must be carried out with actual profiling and testing to determine issues with accuracy. By understanding the performance properties of regular classes, sealed classes, and sealed interfaces in Kotlin, you can make sound decisions to generate efficient and high-performing applications.
In today’s mobile app development landscape, implementing a smooth and secure authentication process is essential for user engagement and retention. One popular authentication method is Google Sign-In, which allows users to sign in to your app using their Google credentials. In this blog, we will explore how to integrate Google Sign-In seamlessly into your Jetpack Compose UI for Android projects. By following the steps outlined below, you’ll be able to enhance the user experience and streamline the authentication process.
Prerequisites
Before diving into the implementation, ensure that you have a basic understanding of Jetpack Compose and Android development. Familiarity with Kotlin is also beneficial. Additionally, make sure you have set up a project in the Google Cloud Platform (GCP) and obtained the necessary credentials and permissions.
Step 1: Google Sign-In APIAdding the Dependency
The first step is to add the required dependency to your project’s build.gradle file. By including the ‘play-services-auth’ library, you gain access to the Google Sign-In API. Make sure to sync the project after adding the dependency to ensure it is correctly imported.
The version number, in this case, is 19.2.0, which specifies the specific version of the play-services-auth library you want to include.
Step 2: Creating the GoogleUserModel
To handle the user data obtained from the Google Sign-In process, we need to create a data class called ‘GoogleUserModel’. This class will store the relevant user information, such as their name and email address. By encapsulating this data in a model class, we can easily pass it between different components of our app.
Kotlin
dataclassGoogleUserModel(val name: String?, val email: String?)
Step 3: Implementing the AuthScreen
The ‘AuthScreen’ composable function serves as the entry point for our authentication flow. It interacts with the ‘GoogleSignInViewModel’ and handles the UI components required for the sign-in process. We will create a smooth navigation flow that allows users to initiate the Google Sign-In procedure.
In the ‘AuthView’ composable function, we will define the visual layout of our authentication screen. This includes displaying a loading indicator, the Google Sign-In button, and handling potential error messages. By providing a user-friendly interface, we enhance the overall user experience.
The AuthScreen function is a Composable function that represents the authentication screen. It takes a NavController as a parameter, which will be used for navigating to different screens.
Inside the AuthScreen function, an instance of GoogleSignInViewModel is created using the viewModel function. This ViewModel is responsible for managing the authentication state related to Google Sign-In.
The userState variable collects the state of the googleUser property from the GoogleSignInViewModel as a Composable state. This allows the UI to update reactively whenever the user state changes.
The AuthView composable function is called to display the UI components of the authentication screen. It takes a lambda function onClick and the mGoogleSignInViewModel as parameters.
After calling the AuthView composable, there is a check to see if the user’s name is not empty. If it’s not empty, a LaunchedEffect is used to perform an action. It hides the loading state, converts the user object to JSON using MoshiUtils, and navigates to the settings screen, passing the user data along.
The AuthView composable function is responsible for rendering the UI of the authentication screen. It uses the Scaffold composable to set up the basic layout structure.
Inside the AuthView composable, there’s a Column that contains various UI components of the authentication screen, such as an Image, a sign-in button (SignInGoogleButton), and a Text displaying the app’s slogan.
The when expression is used to handle different states. In this case, when the loading state of the mGoogleSignInViewModel is true, it displays an error message (AUTH_ERROR_MSG) using the Text composable.
Finally, there are @Preview annotations for previewing the AuthView composable in different UI modes (day mode and night mode).
Step 5: Managing Authentication with GoogleSignInViewModel
The ‘GoogleSignInViewModel’ class plays a crucial role in managing the authentication state and communicating with the Google Sign-In API. Depending on your preference, you can choose to use LiveData or StateFlow to update the user’s sign-in status and handle loading and error states. This ViewModel acts as a bridge between the UI and the underlying authentication logic.
Kotlin
/* * It contains commented code I think it will helpful when implement logout functionality * in future thats why kept as it is here. * */classGoogleSignInViewModel : ViewModel() {privatevar _userState = MutableStateFlow(GoogleUserModel("", ""))val googleUser = _userState.asStateFlow()privatevar _loadingState = MutableStateFlow(false)val loading = _loadingState.asStateFlow()privateval _errorStateFlow = MutableStateFlow(false)val errorStateFlow = _errorStateFlow.asStateFlow()/* init { checkSignedInUser(application.applicationContext) }*/funfetchSignInUser(email: String?, name: String?) { _loadingState.value = true viewModelScope.launch { _userState.value =GoogleUserModel( email = email, name = name, ) } _loadingState.value = false }/* private fun checkSignedInUser(applicationContext: Context) { _loadingState.value = true val gsa = GoogleSignIn.getLastSignedInAccount(applicationContext) if (gsa != null) { _userState.value = GoogleUserModel( email = gsa.email, name = gsa.displayName, ) } _loadingState.value = false }*/funhideLoading() { _loadingState.value = false }funshowLoading() { _loadingState.value = true }funisError(isError: Boolean) { _errorStateFlow.value = isError }}/*class GoogleSignInViewModelFactory( private val application: Application) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") if (modelClass.isAssignableFrom(GoogleSignInViewModel::class.java)) { return GoogleSignInViewModel(application) as T } throw IllegalArgumentException("Unknown ViewModel class") }}*/
Conclusion
By following this tutorial, you have learned how to seamlessly integrate Google Sign-In into your Jetpack Compose UI for Android. The integration allows users to sign in to your app using their Google credentials, enhancing the user experience and streamlining the authentication process. By leveraging the power of Jetpack Compose and the Google Sign-In API, you can build secure and user-friendly apps that cater to the modern authentication needs of your users.
Generics in Kotlin provide a powerful way to write reusable and type-safe code. However, on the Java Virtual Machine (JVM), generics are subject to type erasure, meaning that the specific type arguments used for instances of a generic class are not preserved at runtime. This limitation has implications for runtime type checks and casts. But fear not! Kotlin provides a solution: reified type parameters. In this blog post, we’ll delve into the world of reified type parameters and explore how they enable us to access and manipulate type information at runtime.
Understanding Type Erasure in Kotlin Generics
Generics in Kotlin are implemented using type erasure on the JVM. This means that the specific type arguments used for instances of a generic class are not preserved at runtime. In this section, we’ll explore the practical consequences of type erasure in Kotlin and learn how you can overcome its limitations by declaring a function as inline.
By declaring a function as inline, you can prevent the erasure of its type arguments. In Kotlin, this is achieved by using reified type parameters. Reified type parameters allow you to access and manipulate the actual type information of the generic arguments at runtime.
In simpler terms, when you mark a function as inline with a reified type parameter, you can retrieve and work with the specific types used as arguments when calling that function.
Now, let’s look at some examples to better understand the concept of reified type parameters and their usefulness.
Generics at runtime: type checks and casts
Generics in Kotlin, similar to Java, are erased at runtime. This means that the type arguments used to create an instance of a generic class are not preserved at runtime. For example, if you create a List<String> and put strings into it, at runtime, you will only see it as a List. You won’t be able to identify the specific type of elements the list was intended to contain. However, the compiler ensures that only elements of the correct type are stored in the list based on the type arguments provided during compilation.
Even though the compiler recognizes list1 and list2 as distinct types, at execution time, they appear the same. However, you can generally rely on List<String> to contain only strings and List<Int> to contain only integers because the compiler knows the type arguments and enforces type safety. It is possible to deceive the compiler using type casts or Java raw types, but it requires a deliberate effort.
When it comes to checking the type information at runtime, the erased type information poses some limitations. You cannot directly check if a value is an instance of a specific erased type with type arguments. For example, the following code won’t compile:
Kotlin
if (valueis List<String>) { ... } // Error: Cannot check for instance of erased type
Even though you can determine at runtime that value is a List, you cannot determine whether it’s a list of strings, persons, or some other type. That information is erased.
Note that erasing generic type information has its benefits: the overall amount of memory used by your application is smaller; because less type information needs to be saved in memory.
As we stated earlier, Kotlin doesn’t let you use a generic type without specifying type arguments. Thus you may wonder how to check that the value is a list, rather than a set or another object
To check if a value is a List without specifying its type argument, you can use the star projection syntax:
Kotlin
if (valueis List<*>) { ... }
By using List<*>, you’re essentially treating it as a type with unknown type arguments, similar to Java’s List<?>. In this case, you can determine that the value is a List, but you won’t have any information about its element type.
Note that you can still use normal generic types in as and as? casts. However, these casts won’t fail if the class has the correct base type but a wrong type argument because the type argument is not known at runtime. The compiler will emit an “unchecked cast” warning for such casts. It’s important to understand that it’s only a warning, and you can still use the value as if it had the necessary type.
Here’s an example of using as? cast with a warning:
Kotlin
funprintSum(c: Collection<*>) {val intList = c as? List<Int> // Warning here. Unchecked cast: List<*> to List<Int> ?: throwIllegalArgumentException("List is expected")println(intList.sum())}
This code defines a function called printSum that takes a collection (c) as a parameter. Within the function, a cast is performed using the as? operator, attempting to cast c as a List<Int>. If the cast succeeds, the resulting value is assigned to the variable intList. However, if the cast fails (i.e., c is not a List<Int>), the as? operator returns null, and the code throws an IllegalArgumentException with the message “List is expected”. Finally, the sum of the integers in intList is printed.
Let’s see how this function behaves when called with different inputs:
Kotlin
printSum(listOf(1, 2, 3)) // o/p - 6
When called with a list of integers, the function works as expected. The sum of the integers is calculated and printed.
Now let’s change the input to a set:
Kotlin
printSum(setOf(1, 2, 3)) // o/p - IllegalArgumentException: List is expected
When called with a set of integers, the function throws an IllegalArgumentException because the input is not a List. The as? cast fails, resulting in a null value, and the IllegalArgumentException is thrown.
Now we pass String as input:
Kotlin
printSum(listOf("a", "b", "c")) // o/p - ClassCastException: String cannot be cast to Number
When called with a list of strings, the function successfully casts the list to a List<Int>, despite the wrong type argument. However, during the execution of intList.sum(), a ClassCastException occurs. This happens because the function tries to treat the strings as numbers, resulting in a runtime error.
The code examples above demonstrate that type casts (as and as?) in Kotlin may lead to runtime exceptions if the casted type and the actual type are incompatible. The compiler emits an “unchecked cast” warning to notify you about this potential risk. It’s important to understand the meaning of these warnings and be cautious when using type casts.
The code snippet below shows an alternative approach using an is check:
Kotlin
funprintSum(c: Collection<*>) {val intList = c as? List<Int> // Warning here. Unchecked cast: List<*> to List<Int> ?: throwIllegalArgumentException("List is expected")println(intList.sum())}
In this example, the printSum function takes a Collection<Int> as a parameter. Using the is operator, it checks if c is a List<Int>. If the check succeeds, the sum of the integers in the list is printed. This approach is possible because the compiler knows at compile time that c is a collection of integers.
So, Kotlin’s compiler helps you identify potentially dangerous type checks (forbidding is checks) and emits warnings for type casts (as and as?) that may cause issues at runtime. Understanding these warnings and knowing which operations are safe is essential when working with type casts in Kotlin.
Power of Reified Type Parameters in Inline Functions
In Kotlin, generics are typically erased at runtime, which means that you can’t determine the type arguments used when an instance of a generic class is created or when a generic function is called. However, there is an exception to this limitation when it comes to inline functions. By marking a function as inline, you can make its type parameters reified, which allows you to refer to the actual type arguments at runtime.
Let’s take a look at an example to illustrate this. Suppose we have a generic function called isA that checks if a given value is an instance of a specific type T:
Kotlin
fun <T> isA(value: Any) = valueis T
If we try to call this function with a specific type argument, like isA<String>("abc"), we would encounter an error because the type argument T is erased at runtime.
However, if we modify the function to beinline and mark the type parameter as reified, like this:
Kotlin
inlinefun <reifiedT> isA(value: Any) = valueis T
Now we can call isA<String>("abc") and isA<String>(123) without any errors. The reified type parameter allows us to check whether the value is an instance of T at runtime. In the first example, the output will be true because "abc" is indeed a String, while in the second example, the output will be false because 123 is not a String.
Another practical use of reified type parameters is demonstrated by the filterIsInstance function from the Kotlin standard library. This function takes a collection and selects instances of a specified class, returning only those instances. For example:
Kotlin
val items = listOf("one", 2, "three")println(items.filterIsInstance<String>())
In this case, we specify <String> as the type argument for filterIsInstance, indicating that we are interested in selecting only strings from the items list. The function’s return type is automatically inferred as List<String>, and the output will be [one, three].
Here’s a simplified version of the filterIsInstance function’s declaration from the Kotlin standard library:
Before coming to this code explanation, have you ever thought, Why reification works for inline functions only? How does this work? Why are you allowed to write element is T in an inline function but not in a regular class or function? Let’s see the answers to all these questions:
Reification works for inline functions because the compiler inserts the bytecode implementing the inline function directly at every place where it is called. This means that the compiler knows the exact type used as the type argument in each specific call to the inline function.
When you call an inline function with a reified type parameter, the compiler can generate a bytecode that references the specific class used as the type argument for that particular call. For example, in the case of the filterIsInstance<String>() call, the generated code would be equivalent to:
Kotlin
for (element inthis) {if (element is String) { destination.add(element) }}
The generated bytecode references the specific String class, not a type parameter, so it is not affected by the type-argument erasure that occurs at runtime. This allows the reified type parameter to be used for type checks and other operations at runtime.
It’s important to note that inline functions with reified type parameters cannot be called from Java code. Regular inline functions are accessible to Java as regular functions, meaning they can be called but are not inlined. However, functions with reified type parameters require additional processing to substitute the type argument values into the bytecode, and therefore they must always be inlined. This makes it impossible to call them in a regular way, as Java code does not support this mechanism.
Also, one more thing to note is that an inline function can have multiple reified type parameters and can also have non-reified type parameters alongside the reified ones. It’s important to keep in mind that marking a function as inline does not necessarily provide performance benefits in all cases. If the function becomes large, it’s recommended to extract the code that doesn’t depend on reified type parameters into separate non-inline functions for better performance.
Practical use cases of reified type parameters
Reified type parameters can be especially useful when working with APIs that expect parameters of type java.lang.Class. Let\’s explore two examples to demonstrate how reified type parameters simplify such scenarios.
Example 1
ServiceLoader The ServiceLoader API from the JDK is an example of an API that takes a java.lang.Class representing an interface or abstract class and returns an instance of a service class implementing that interface. Traditionally, in Kotlin, you would use the following syntax to load a service:
Kotlin
val serviceImpl = ServiceLoader.load(Service::class.java)
However, using a function with a reified type parameter, we can make this code shorter and more readable:
Kotlin
val serviceImpl = loadService<Service>()
To define the loadService function, we use the inline modifier and a reified type parameter:
Kotlin
inlinefun <reifiedT> loadService(): T {return ServiceLoader.load(T::class.java)}
Here, T::class.java retrieves the java.lang.Class corresponding to the class specified as the type parameter, allowing us to use it as needed. This approach simplifies the code by specifying the class as a type argument, which is shorter and easier to read compared to ::class.java syntax.
Example 2
Simplifying startActivity in Android In Android development, when launching activities, instead of passing the class of the activity as a java.lang.Class, you can use a reified type parameter to make the code more concise. For instance:
With this inline function, you can start an activity by specifying the activity class as a type argument:
Kotlin
startActivity<DetailActivity>()
This simplifies the code by eliminating the need to pass the activity class as a java.lang.Class instance explicitly.
Reified type parameters allow us to work with class references directly, making the code more readable and concise. They are particularly useful in scenarios where APIs expect java.lang.Class parameters, such as ServiceLoader in Java or starting activities in Android.
Restrictions on Reified Type Parameters
Reified Type parameters in Kotlin have certain restrictions that you need to be aware of. Some of these restrictions are inherent to the concept itself, while others are determined by the implementation of Kotlin and may change in future Kotlin versions. Here’s a summary of how you can use reified type parameters and what you cannot do:
You can use a reified type parameter in the following ways:
Type checks and casts (is, !is, as, as?)
Reified type parameters can be used in type checks and casts. You can check if an object is of a specific type or perform a type cast using the reified type parameter. Here’s an example:
Kotlin
inlinefun <reifiedT> checkType(obj: Any) {if (obj is T) {println("Object is of type T") } else {println("Object is not of type T") }val castedObj = obj as? T// Perform operations with the casted object}
Kotlin reflection APIs (::class)
Reified type parameters can be used with Kotlin reflection APIs, such as ::class, to access runtime information about the type. It allows you to retrieve the KClass object representing the type parameter. Here’s an example:
Getting the corresponding java.lang.Class (::class.java)
Reified type parameters can also be used to obtain the corresponding java.lang.Class object of the type using the ::class.java syntax. This can be useful when interoperating with Java APIs that require Class objects. Here’s an example:
Using reified type parameter as a type argument when calling other functions
Reified type parameters can be used as type arguments when calling other functions. This allows you to propagate the type information to other functions without losing it due to type erasure. Here’s an example:
Kotlin
inlinefun <reifiedT> processList(list: List<T>) {// Process the list of type Tfor (item in list) {// ... }}funmain() {val myList = listOf("Hello", "World")processList<String>(myList)}
These examples demonstrate the various ways in which reified type parameters can be utilized in Kotlin, including type checks, reflection APIs, obtaining java.lang.Class, and passing the type information to other functions as type arguments.
However, there are certain things you cannot do with reified type parameters:
Creating new instances of the class specified as a type parameter
Reified type parameters cannot be used to create new instances of the class directly. You can only access the type information using reified type parameters. To create new instances, you would need to use other means such as reflection or factory methods. Here’s an example:
Kotlin
inlinefun <reifiedT> createInstance(): T {// Error: Cannot create an instance of the type parameter TreturnT()}
Calling methods on the companion object of the type parameter class
Reified type parameters cannot directly access the companion object of the type parameter class. However, you can access the class itself using T::class syntax. To call methods on the companion object, you would need to access it through the class reference. Here’s an example:
Kotlin
inlinefun <reifiedT> callCompanionMethod(): String {// Error: Cannot access the companion object of the type parameter Treturn T.Companion.someMethod()}
Using a non-reified type parameter as a type argument
When calling a function with a reified type parameter, you cannot use a non-reified type parameter as a type argument. Reified type parameters can only be used as type arguments themselves. Here’s an example:
Kotlin
inlinefun <reifiedT> reifiedFunction() {// Error: Non-reified type parameter cannot be used as a type argumentanotherFunction<T>()}fun <T> anotherFunction() {// ...}
Marking type parameters of classes, properties, or non-inline functions as reified
Reified type parameters can only be used in inline functions. You cannot mark type parameters of classes, properties, or non-inline functions as reified. Reified type parameters are limited to inline functions. Here’s an example:
Kotlin
classMyClass<T> { // Error: Type parameter cannot be marked as reified// ...}val <T> List<T>.property: T// Error: Type parameter cannot be marked as reifiedget() = TODO()fun <T> nonInlineFunction() { // Error: Type parameter cannot be marked as reified// ...}
These examples illustrate the restrictions on reified type parameters in Kotlin. By understanding these limitations, you can use reified type parameters effectively in inline functions while keeping in mind their specific usage scenarios.
Conclusion
Reified type parameters in Kotlin offer a powerful tool for overcoming the limitations of type erasure at runtime. By utilizing reified type parameters in inline functions, developers can access and manipulate precise type information, enabling type checks, casts, and interaction with reflection APIs. Understanding the benefits and restrictions of reified type parameters empowers Kotlin developers to write more expressive, type-safe, and concise code.
By embracing reified type parameters, Kotlin programmers can unleash the full potential of generics and enhance their runtime type-related operations. Start utilizing reified type parameters today and unlock a world of type-aware programming in Kotlin!
Kotlin, a modern and versatile programming language, offers various features to enhance developer productivity. One such powerful feature is function types, which enable you to treat functions as first-class citizens in your code. In this blog post, we will delve into function types in Kotlin, understand their types, explore their usage, and provide examples to solidify our understanding. So let’s dive in!
Recap: Higher-Order Functions
In Kotlin, a higher-order function is a function that can accept a lambda expression or a function reference as an argument or can return a lambda expression or a function reference. It allows functions to be treated as values and enables flexible and concise coding.
Let’s take the example of the filter function from the standard library, which takes a predicate function as an argument and is, therefore, a higher-order function:
Kotlin
list.filter { x > 0 }
Function types
In Kotlin, you can declare variables with function types. This means that the variables can hold references to functions. Let’s take a look at an example:
Kotlin
val sum: (Int, Int) -> Int = { x, y -> x + y } // Function that takes two Int parameters and returns an Int valueval action: () -> Unit = { println(42) } // Function that takes no arguments and doesn’t return a value
In this code, we have two variables: sum and action. The type of sum is a function that takes two Int parameters and returns an Int. The type of action is a function that takes no parameters and returns Unit, which represents a lack of meaningful value.
What are Function Types?
Function types allow you to treat functions as values. Just like any other variable, you can assign functions to variables, pass them as parameters to other functions, and even return them from functions. This feature provides flexibility and enables you to write more concise and expressive code.
Syntax
To declare a function type, you put the function parameter types in parentheses, followed by an arrow, and the return type of the function. In the above diagram, (Int, String) -> Unit specifies a function that takes Int and String parameters and returns a Unit.
The Unit type is used to indicate that a function doesn’t return a meaningful value. In regular function declarations, you can omit the Unit return type, but in function type declarations, it is always required. So, you can’t omit a Unit in this context.
Kotlin
val sum: (Int, Int) -> Int = { x, y -> x + y }
In the lambda expression { x, y -> x + y }, you might notice that the types of the parameters x and y are omitted. This is because the types are already specified in the function type declaration, so there’s no need to repeat them in the lambda itself.
You can also make the return type of a function type nullable by using the? symbol. For example:
Kotlin
var canReturnNull: (Int, Int) -> Int? = { null }
In this case, the function type (Int, Int) -> Int? represents a function that takes two Int parameters and returns an Int that can be nullable. This means the function can return either an Int value or null.
Furthermore, you can declare a nullable variable of a function type by enclosing the entire function type definition in parentheses and placing the question mark after the parentheses. For example:
Kotlin
var funOrNull: ((Int, Int) -> Int)? = null
Here, ((Int, Int) -> Int)? represents a nullable variable of a function type. The entire function type definition is enclosed in parentheses, and the question mark indicates that the variable itself is nullable.
It’s important to note the distinction between a function type with a nullable return type ((Int, Int) -> Int?) and a nullable variable of a function type (((Int, Int) -> Int)?). Omitting the parentheses will result in different meanings, so be cautious when specifying nullable function types.
Parameter names of function types
In Kotlin, you have the option to specify names for the parameters of a function type. This can improve the readability of your code and can be helpful for code completion in the IDE.
Here’s an example that demonstrates specifying parameter names in a function type:
In this code, the performRequest function takes two parameters: url of type String and callback of type (code: Int, content: String) -> Unit. The callback parameter is a function type that expects two parameters named code and content, both of type Int and String respectively. The function type represents a callback function that will be invoked when the request is completed.
When you call the performRequest function, you can provide a lambda expression as the argument for the callback parameter. The lambda can use any parameter names you prefer, regardless of the names specified in the function type declaration. For example:
Kotlin
val url = "https://blog.softaai.com"performRequest(url) { code, content ->// Code that uses the parameters 'code' and 'content'}performRequest(url) { code, page ->// Code that uses the parameters 'code' and 'page'}
In the above examples, we pass a lambda expression to the performRequest function. Inside the lambda, we can choose different names for the parameters (code and content in the first example, and code and page in the second example). These parameter names in the lambda expression do not need to match the names specified in the function type declaration.
Although the parameter names don’t affect type matching, using descriptive names can make your code more readable and understandable. Additionally, modern IDEs can utilize these parameter names for code completion, making it easier for you to write your code accurately.
Calling functions passed as arguments
In Kotlin, you can call functions that are passed as arguments to other functions. Let’s explore a couple of examples to understand how this works.
First, let’s consider the twoAndThree function, which takes another function as an argument and performs an arbitrary operation on the numbers 2 and 3:
Kotlin
funtwoAndThree(operation: (Int, Int) -> Int) {val result = operation(2, 3)println("The result is $result")}
In this example, the twoAndThree function accepts a function called operation, which has a function type (Int, Int) -> Int. This means the operation function takes two Int parameters and returns an Int. Inside the twoAndThree function, the operation function is called with arguments 2 and 3, and the result is printed.
To call the twoAndThree function and pass a function as an argument, you can use a lambda expression. For example:
Kotlin
twoAndThree { a, b -> a + b }
In this case, we pass a lambda expression that adds two numbers (a + b). The lambda matches the function type (Int, Int) -> Int because it takes two Int parameters and returns an Int. The result of the addition, 5, is printed by the twoAndThree function.
Similarly, you can pass a different lambda expression to achieve a different operation:
Kotlin
twoAndThree { a, b -> a * b }
Here, the lambda multiplies the two numbers (a * b), and the result, 6, is printed.
The syntax for calling a function passed as an argument is the same as calling a regular function. You use parentheses after the function name and provide the necessary arguments inside the parentheses.
Now, let’s consider another example: reimplementing the filter function from the standard library. The filter function takes a predicate as a parameter. The predicate is a function that takes a character and returns a Boolean result. The implementation checks whether each character satisfies the predicate and adds it to a StringBuilder if it does.
To reimplement the filter function for strings, let’s consider the following implementation:
In this implementation, the filter function is an extension function on the String class. It takes a predicate parameter, which is a function that takes a Char parameter and returns a Boolean.
Inside the function, a StringBuilder is created to store the filtered characters. The function iterates over each character of the string using the for loop and checks if the character satisfies the given predicate. If the predicate returns true for a character, it is appended to the StringBuilder.
Finally, the StringBuilder is converted to a string using the toString() function and returned as the result.
Here’s an example of how you can use the filter function:
Kotlin
println("softAai Apps".filter { it in'A'..'Z' })
In this case, the input string is "softAai Apps", and the predicate checks if each character is within the range from 'A' to 'Z'. The filtered result, which only contains the uppercase alphabetic characters, is printed as "AA".
The filter function implementation is straightforward. It enables you to filter characters from a string based on a given predicate, providing a more convenient and readable way to perform such operations.
By using higher-order functions and passing functions as arguments, you can create flexible and reusable code that can perform different operations based on the provided functions.
Default and null values for parameters with function types
When declaring a parameter of a function type, you can specify a default value for it. This can be useful when you want to provide a default behavior for the function if the caller doesn’t provide a specific implementation. Here’s an example:
Kotlin
fun <T> printCollection(collection: Collection<T>, transform: (T) -> String = { it.toString() }) {for (element in collection) {println(transform(element)) }}
In this example, the printCollection function takes a collection parameter of type Collection<T> and a transform parameter of function type (T) -> String. The transform parameter has a default value defined as a lambda expression { it.toString() }, which uses the toString() method to convert each element of the collection to a string.
You can call the printCollection function in different ways:
Kotlin
val numbers = listOf(1, 2, 3, 4, 5)// Omitting the transform parameter to use the default behaviorprintCollection(numbers)// Passing a lambda as the transform parameterprintCollection(numbers) { "Number: $it" }// Passing the transform parameter as a named argumentprintCollection(numbers, transform = { "Value: $it" })
In the first example, we omit the transform parameter, so the default behavior using toString() will be used to convert each element.
In the second example, we pass a lambda expression { "Number: $it" } as the transform parameter. This lambda defines a custom behavior to transform each element of the collection.
In the third example, we explicitly pass the transform parameter as a named argument, providing a different lambda expression { "Value: $it" } to customize the transformation.
Another option is to declare a parameter of a nullable function type. However, directly calling a function passed in such a parameter is not allowed because it could potentially lead to null pointer exceptions. To handle this, you can check for null explicitly or use the safe-call syntax callback?.invoke(). Here’s an example:
In this example, the performAction function takes a nullable function type parameter callback of type (() -> Unit)?. Inside the function, we can use the safe-call syntax callback?.invoke() to invoke the function only if it is not null.
Function Types with Generic Parameters
Kotlin allows you to define function types with generic parameters. This provides flexibility when working with functions that can operate on different types.
Kotlin
fun <T> processList(list: List<T>, operation: (T) -> Unit) {for (item in list) {operation(item) }}val numbers = listOf(1, 2, 3, 4, 5)processList(numbers) { println(it) } // Prints each number in the list
In the above example, the processList function takes a list of generic type T and a function type (T) -> Unit as parameters. The operation function is called for each item in the list and can perform any desired operation.
Now you know how to write functions that take functions as arguments, including how to provide default values for function parameters and handle nullable function types. This allows you to create more flexible and customizable functions in Kotlin.
Returning functions from functions
Returning functions from functions allows you to dynamically choose and provide different logic based on certain conditions or states. This can be useful in scenarios where the behavior of a program needs to adapt to different situations.
For example, let’s consider a shipping cost calculation scenario. The shipping cost may vary depending on the chosen delivery method. We can define a function called getShippingCostCalculator that takes the delivery parameter of type Delivery (an enum class representing different delivery options) and returns a function of type (Order) -> Double. The returned function calculates the shipping cost based on the selected delivery method.
Here’s an example implementation:
Kotlin
enumclassDelivery { STANDARD, EXPEDITED }classOrder(val itemCount: Int)fungetShippingCostCalculator(delivery: Delivery): (Order) -> Double { // Declares a function that returns a functionif (delivery == Delivery.EXPEDITED) {return { order ->6 + 2.1 * order.itemCount } // Returns lambdas from the function }return { order ->1.2 * order.itemCount } // Returns lambdas from the function}
In this example, when getShippingCostCalculator is called with Delivery.EXPEDITED, it returns a function that takes an Order parameter and calculates the shipping cost as 6 + 2.1 * order.itemCount. For any other delivery option, it returns a different function that calculates the shipping cost as 1.2 * order.itemCount.
You can use the returned function to calculate the shipping cost for a specific order. Here’s an example of usage:
Kotlin
val calculator = getShippingCostCalculator(Delivery.EXPEDITED) // Stores the returned function in a variableprintln("Shipping costs ${calculator(Order(3))}") // Invokes the returned function
In this code, we obtain the calculator function by calling getShippingCostCalculator with Delivery.EXPEDITED. Then, we pass an Order object with itemCount as 3 to the calculator function, which calculates and returns the shipping cost as 12.3. Finally, we print the shipping cost.
Let’s consider one more scenario where returning functions from functions is useful.
Suppose you’re working on a GUI contact-management application, and you need to determine which contacts should be displayed based on the state of the user interface (UI). You can define a function called getContactFilter that takes a UI state as a parameter and returns a function of type (Contact) -> Boolean. The returned function will determine whether a contact should be displayed or not based on the UI state.
Here’s an example implementation:
Kotlin
dataclassContact(val name: String, val isFavorite: Boolean)enumclassUIState { ALL, FAVORITES }fungetContactFilter(uiState: UIState): (Contact) -> Boolean { // Declares a function that returns a functionreturnwhen (uiState) { UIState.ALL -> { true } // Display all contacts UIState.FAVORITES -> { contact -> contact.isFavorite } // Display only favorite contacts }}
In this example, getContactFilter takes a UIState parameter and returns a function of type (Contact) -> Boolean. The returned function determines whether a contact should be displayed or not based on the UI state. If the UI state is UIState.ALL, the returned function always returns true, indicating that all contacts should be displayed. If the UI state is UIState.FAVORITES, the returned function checks the isFavorite property of the contact and returns its value, indicating whether the contact is a favorite or not.
You can use the returned function to filter the contacts based on the UI state. Here’s an example usage:
In this code, we obtain the filter function by calling getContactFilter with UIState.FAVORITES. Then, we have a list of contacts, and we use the filter function to filter the contacts based on the UI state. The filtered contacts, in this case, are the contacts that are marked as favorites. Finally, we print the names of the filtered contacts.
Returning functions from functions allows you to dynamically select and apply different logic based on conditions or states, providing flexibility and customization in your code.
Using function types from Java
Under the hood, function types are declared as regular interfaces: a variable of a function type is an implementation of a FunctionN interface. The Kotlin standard library defines a series of interfaces, corresponding to different numbers of function arguments: Function0 (this function takes no arguments), Function1 (this function takes one argument), and so on. Each interface defines a single invoke method, and calling it will execute the function. A variable of a function type is an instance of a class implementing the corresponding FunctionN interface, with the invoke method containing the body of the lambda.
Kotlin functions that use function types can be called easily from Java. Java 8 lambdas are automatically converted to values of function types:
// JavaprocessTheAnswer(number -> number + 1); // output : 43
In this case, the processTheAnswer function in Kotlin takes a function type (Int) -> Int as a parameter. In Java, you can pass a lambda expression number -> number + 1 as an argument, and it will be automatically converted to the corresponding function type.
What about older Java versions?
If you are using an older version of Java that doesn’t support lambdas, you can pass an instance of an anonymous class that implements the invoke method from the corresponding function interface. Here’s an example:
Kotlin
// JavaprocessTheAnswer(new Function1<Integer, Integer>() { // Uses the Kotlin function type from Java code (prior to Java 8)@Overridepublic Integer invoke(Integer number) { System.out.println(number);return number + 1; }});
In this Java example, we create an anonymous class that implements the Function1 interface. We override the invoke method, which corresponds to the function body in Kotlin, and provide the desired implementation.
What about Extention Functions?
In Java, you can easily use extension functions from the Kotlin standard library that expect lambdas as arguments. Note, however, that they don’t look as nice as in Kotlin — you have to pass a receiver object as a first argument explicitly. Here’s an example:
Kotlin
// JavaList<String> strings = new ArrayList<>();strings.add("42");CollectionsKt.forEach(strings, s -> { System.out.println(s);return Unit.INSTANCE;});
In this Java example, we use the CollectionsKt.forEach extension function from the Kotlin standard library. We pass a lambda expression s -> { ... } as an argument. Inside the lambda, we can perform the desired operations. Note that in Java, you need to explicitly return Unit.INSTANCE to match the Kotlin requirement of returning Unit.
It’s important to remember that in Java, functions or lambdas can return Unit. However, since Unit has a value in Kotlin, you need to explicitly return it. Additionally, you cannot directly pass a lambda that returns void as an argument of a function type that expects Unit as the return type.
Overall, while using function types and lambdas from Kotlin in Java may require some adjustments in syntax, it is still possible to utilize Kotlin’s higher-order functions and achieve the desired functionality.
Removing duplication through lambdas
In Kotlin, lambdas are anonymous functions that can be treated as values. They allow you to define blocks of code that can be passed around and executed later. This flexibility enables us to write more concise and reusable code.
Suppose you have a scenario where you need to perform similar operations on different elements of a collection. Without lambdas, you might end up writing repetitive code for each element, resulting in duplication. However, by utilizing lambdas, you can extract the common behavior and eliminate duplication.
To illustrate this concept, let’s consider an example involving website visit data. We have a class called SiteVisit that represents a visit to a website. Each visit has properties like path (the visited URL), duration (time spent on the page), and os (operating system used by the visitor). The os property is an enum called OS, representing different operating systems.
Our goal is to calculate the average duration of visits from Windows machines. We can achieve this by filtering the visits based on the operating system, mapping the durations, and then calculating the average using the average function.
In this example, we used the lambda expression { it.os == OS.WINDOWS } as a filter to select only the visits with the Windows operating system. Then, we used the map function to extract the durations of those visits. Finally, the average function calculated the average duration.
Now, let’s say we want to calculate the average duration for visits from Mac users as well. Without using lambdas, we would need to write similar code again, resulting in duplication. However, we can avoid this duplication by extracting the common behavior into a function and parameterizing it.
We define an extension function called averageDurationFor on the List<SiteVisit> class, which takes an os parameter representing the operating system. Inside the function, we filter the visits based on the given operating system, map the durations, and calculate the average.
Kotlin
funList<SiteVisit>.averageDurationFor(os: OS) =filter { it.os == os } .map(SiteVisit::duration) .average()
With this function in place, we can now calculate the average duration for both Windows and Mac visits without duplicating the code.
By parameterizing the behavior that varies (in this case, the operating system), we eliminated duplication and made the code more reusable and maintainable.
However, there are situations where a simple parameter is not sufficient to capture the complexity of the condition we want to apply. For example, if we want to calculate the average duration for visits from mobile platforms (iOS and Android), a single parameter representing the operating system won’t be enough. In such cases, lambdas provide a powerful solution.
We can modify our averageDurationFor function to take a lambda expression as a parameter. This lambda expression represents a condition that needs to be fulfilled for a visit to be included in the calculation.
Now, we can use this enhanced averageDurationFor function to calculate the average duration based on more complex conditions. For example, finding the average duration for visits from Android and iOS users:
In this case, we passed a lambda expression { it.os in setOf(OS.ANDROID, OS.IOS) } to the averageDurationFor function. This lambda expression represents the condition that checks if the operating system is either Android or iOS.
Similarly, we can use Lambdas to perform more intricate queries, such as finding the average duration of visits to the signup page from iOS users:
Here, we provided a lambda expression { it.os == OS.IOS && it.path == "/signup" } to specify the condition that includes only the visits from iOS users to the “/signup” page.
By using lambdas and function types, we can eliminate code duplication and extract both the repeated data and behavior into reusable functions. Lambdas allow us to write more expressive and concise code, making our programs easier to understand and maintain.
Function types and lambdas not only help eliminate code duplication but also provide a flexible and concise way to define different strategies or behaviors within your code. Instead of creating multiple classes or interfaces for each strategy, you can directly pass lambda expressions as different strategies, simplifying your code and making it more expressive.
Conclusion
Function types in Kotlin provide a powerful way to work with functions as first-class citizens. They enable you to pass functions as parameters, return them from functions, and even assign them to variables. This flexibility allows for concise and expressive code, making Kotlin a great language for functional programming paradigms. By understanding the various aspects of function types and exploring practical examples, you can leverage this feature to write more efficient and maintainable code. So go ahead, harness the power of function types in Kotlin, and take your programming skills to the next level!
In the world of modern programming languages, Kotlin has gained popularity for its flexibility and concise coding style, largely thanks to lambdas or anonymous functions. However, the use of lambdas can introduce overhead due to function calls and memory allocations. To address this concern, Kotlin offers inline functions as a means to optimize code execution. In this blog post, we will delve into inline functions in Kotlin, understanding how they work, their limitations, and the advantages they offer over lambdas. Additionally, we will explore advanced concepts such as noinline, crossinline, and reified types that further enhance the capabilities of inline functions.
Understanding Inline Functions in Kotlin
In Kotlin, inline functions can help remove the overhead associated with lambdas and improve performance. When you use a lambda expression, it is typically compiled into an anonymous class. This means that each time you use a lambda, an additional class is created. Moreover, if the lambda captures variables, a new object is created for each invocation. As a result, using Lambdas can introduce runtime overhead and make the implementation less efficient compared to directly executing the code.
To mitigate this performance impact, Kotlin provides the inline modifier for functions. When you mark a function with inline, the compiler replaces every call to that function with the actual code implementation, instead of generating a function call. This way, the overhead of creating additional classes and objects is avoided.
Let’s see a simple example to illustrate this:
Kotlin
inlinefunmultiply(a: Int, b: Int): Int {return a * b}funmain() {val result = multiply(2, 3)println(result)}
In this example, the multiply function is marked as inline. When you call multiply(2, 3), the compiler replaces the function call with the actual code of the multiply function:
Kotlin
funmain() {val result = 2 * 3// only for illustrating purposes, later we will see how it actually works println(result)}
This allows the code to execute the multiplication directly without the overhead of a function call.
Let’s see one more example to illustrate this:
Kotlin
inlinefunperformOperation(a: Int, b: Int, operation: (Int, Int) -> Int): Int {returnoperation(a, b)}funmain() {val result = performOperation(5, 3) { x, y -> x + y }println(result)}
In this example, the performOperation function is marked as inline. It takes two integers, a and b, and a lambda expression representing an operation to be performed on a and b. When performOperation is called, instead of generating a function call, the compiler directly replaces the code inside the function with the code from the lambda expression.
So, in the main function, the call to performOperation(5, 3) will be replaced with the actual code 5 + 3. This eliminates the overhead of creating an anonymous class and improves performance.
BTW, How inlining works actually?
When you declare a function as inline in Kotlin, its body is substituted directly into the places where the function is called, instead of being invoked as a separate function. This substitution process is known as inlining.
Let’s take a look at an example to understand it more:
In this example, the synchronized function is declared as inline. It takes a Lock object and a lambda action as parameters. The function locks the Lock object, executes the provided action lambda, and then releases the lock.
When you use the synchronized function, the code generated for every call to it is similar to a synchronized statement in Java.
In this case, the lambda expression passed to synchronized is substituted directly into the code of the calling function. The bytecode generated from the lambda becomes part of the definition of the calling function and is not wrapped in an anonymous class implementing a function interface.
Not inlined Case (passing lambda as a parameter)
It’s worth noting that if you call an inline function and pass a parameter of a function type from a variable, rather than a lambda directly, the body of the inline function is not inlined.
Here’s an example:
Kotlin
classLockOwner(val lock: Lock) {funrunUnderLock(body: () -> Unit) {synchronized(lock, body) // A variable of a function type is passed as an argument, not a lambda. }}
In this case, the lambda’s code is not available at the site where the inline function is called, so it cannot be inlined. The body of the runUnderLock function is not inlined because there’s no lambda at the invocation. Only the body of the synchronized function is inlined; the lambda is called as usual. The runUnderLock function will be compiled to bytecode similar to the following function:
Kotlin
classLockOwner(val lock: Lock) {fun__runUnderLock__(body: () -> Unit) { // This function is similar to the bytecode the real runUnderLock is compiled to lock.lock()try {body() // The body isn’t inlined, because there’s no lambda at the invocation. } finally { lock.unlock() } }}
Here, the body of the runUnderLock function cannot be inlined because the lambda is passed as a parameter from a variable (body) rather than directly providing a lambda expression.
Suppose when you pass a lambda as a parameter directly, like this:
Kotlin
lockOwner.runUnderLock {// code block A}
The body of the inline function runUnderLock can be inlined, as the compiler knows the exact code to replace at the call site.
However, when you pass a lambda from a variable, like this:
Kotlin
val myLambda = {// code block A}lockOwner.runUnderLock(myLambda)
The body of the inline function cannot be inlined because the compiler doesn’t have access to the code inside the lambda (myLambda) at the call site. It would require the compiler to know the contents of the lambda in order to inline it.
In such cases, the function call behaves like a regular function call, and the body of the function is not copied to the call site. Instead, the lambda is passed as an argument to the function and executed within the function’s context.
So, suppose even though the runUnderLock function is marked as inline, the body of the function won’t be inlined because the lambda is passed as a parameter from a variable.
What about multiple inlining?
If you have two uses of an inline function in different locations with different lambdas, each call site will be inlined independently. The code of the inline function will be copied to both locations where you use it, with different lambdas substituted into it.
If you have multiple calls to the inline function with different lambdas, like this:
Each call site will be inlined independently. The code of the inline function will be copied to both locations where you use it, with different lambdas substituted into it. This allows the compiler to inline the code at each call site separately.
Restrictions on inline functions
When a function is declared as inline in Kotlin, the body of the lambda expression passed as an argument is substituted directly into the resulting code. However, this substitution imposes certain restrictions on how the corresponding parameter can be used in the function body.
If the parameter is called directly within the function body, the code can be easily inlined. But if the parameter is stored for later use, the code of the lambda expression cannot be inlined because there must be an object that contains this code.
In general, the parameter can be inlined if it’s called directly or passed as an argument to another inline function. If it’s used in a way that prevents inlining, such as storing it for later use, the compiler will prohibit the inlining and show an error message stating “Illegal usage of inline-parameter.”
Let’s consider an example with the Sequence.map function:
Kotlin
fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {returnTransformingSequence(this, transform)}
The map function doesn’t call the transform function directly. Instead, it passes the transform function as a constructor parameter to a class (TransformingSequence) that stores it in a property. To support this, the lambda passed as the transform argument needs to be compiled into the standard non-inline representation, which is an anonymous class implementing a function interface.
“noinline” Modifier
In situations where a function expects multiple lambda arguments, you can choose to inline only some of them. This can be useful when one of the lambdas contains a lot of code or is used in a way that doesn’t allow inlining. To mark parameters that accept non-inlineable lambdas, you can use the noinline modifier:
By using noinline, you indicate that the notInlined parameter should not be inlined.
Note that the compiler almost fully supports inlining functions across modules, or functions defined in third-party libraries(we will discuss more at the end of this blog). You can also call most inline functions from Java; such calls will not be inlined but will be compiled as regular function calls.
Inlining collection operations
In Kotlin, the standard library provides a set of collection functions that accept lambda expressions as arguments. These functions, such as filter, map, and others, are declared as inline, which means that the bytecode of both the function and the lambda will be inlined at the call site.
Let’s compare the performance of filtering a list of people using the filter function with a lambda expression versus manually filtering the list using a loop:
Kotlin
///////////// manually //////////////////dataclassPerson(val name: String, val age: Int)val result = mutableListOf<Person>()for (person in people) {if (person.age < 30) result.add(person)}println(result) // [Person(name=Alice, age=29)]
Kotlin
////////////// with lambda /////////////////val people = listOf(Person("Alice", 29), Person("Bob", 31))println(people.filter { it.age < 30 }) // [Person(name=Alice, age=29)]
This code uses the filter function to filter the list based on the condition specified in the lambda expression { it.age < 30 }. The resulting code will be roughly the same as manually filtering the list using a loop.
The reason for this is that the filter function is declared as inline, and its bytecode, along with the bytecode of the lambda, will be substituted directly into the calling code. This eliminates the overhead of function calls and lambda object creation, resulting in efficient code execution.
Now, let’s consider a chain of operations where both filter and map are applied:
In this example, both filter and map functions are declared as inline. However, there is an intermediate collection created to store the result of filtering before applying the mapping operation. The code generated from the filter function adds elements to this intermediate collection, and the code generated from map reads from it.
If the number of elements to process is large and the overhead of the intermediate collection becomes a concern, you can use a Sequence instead by adding an asSequence call to the chain. However, it’s important to note that lambdas used to process a Sequence are not inlined. Each intermediate sequence is represented as an object storing a lambda in its field, and the terminal operation involves a chain of calls through each intermediate sequence. Therefore, adding asSequence calls to every chain of collection operations may not provide performance benefits for smaller collections and is more suitable for larger collections.
So, you can safely use idiomatic collection operations in Kotlin’s standard library functions, as they are declared as inline and their bytecode, along with the lambda expressions, will be inlined at the call site. If performance becomes a concern for larger collections, you can consider using Sequence and asSequence calls, but it’s not necessary for smaller collections, as regular collection operations perform well.
Deciding when to declare functions as inline
When deciding whether to declare a function as inline, it’s important to consider the specific circumstances and the type of function being used.
For regular function calls, it’s generally not necessary to use the inline keyword. The JVM already has powerful inlining support and automatically analyzes the code to inline calls when it provides the most benefit. The JVM performs this optimization while translating bytecode to machine code. Additionally, calling functions directly without inlining can provide clearer stack traces.
On the other hand, declaring functions as inline is beneficial when working with functions that take lambdas as arguments. In these cases, the overhead of inlining is more significant. By using the inline keyword, you can save on the function call overhead as well as the creation of additional classes and objects for lambda instances. The JVM currently may not always perform inlining effectively with calls and lambdas, so using the inline keyword can ensure efficient execution.
Furthermore, inlining allows you to use features that are not possible with regular lambdas, such as non-local returns(we will look later here). This can provide additional flexibility and functionality in your code.
However, it’s essential to consider the code size when deciding whether to use the inline modifier. If the function you want to inline is large, copying its bytecode into every call site can result in a significant increase in bytecode size. In such cases, it’s recommended to extract the code that is not related to the lambda arguments into a separate non-inline function. This approach helps manage code size and optimize performance.
It’s worth noting that the inline functions in the Kotlin standard library are typically small, as the developers have taken care to extract non-lambda-related code into separate functions.
So, you should carefully consider whether to use the inline keyword based on the specific circumstances and the type of function being used. Regular function calls can rely on the JVM’s inlining support, while functions with lambda arguments can benefit from the inline modifier to reduce overhead and enable additional features. Pay attention to code size and consider extracting unrelated code into separate non-inline functions if necessary.
Using inlined lambdas for resource management
Lambdas can be useful for simplifying code duplication when it comes to resource management. Resource management involves acquiring a resource before performing an operation and releasing it afterward. Resources can include files, locks, database transactions, and more.
Traditionally, the try/finally statement is used to implement this pattern. The resource is acquired before the try block and released in the finally block. However, in Kotlin, you can encapsulate the logic of the try/finally statement in a function and pass the code that uses the resource as a lambda to that function.
For example, the Kotlin standard library provides the withLock function, which offers a more idiomatic API for working with locks:
Kotlin
val l: Lock = ...l.withLock {// Access the resource protected by this lock}
The withLock function is an extension function defined in the Kotlin library. It takes a lambda as an argument and performs the necessary lock operations:
Kotlin
fun <T> Lock.withLock(action: () -> T): T {lock()try {returnaction() } finally {unlock() }}
Files are another type of resource commonly used with this pattern. In Java, the try-with-resources statement was introduced to simplify working with resources. In Kotlin, a similar effect can be achieved using the use function, which is an extension function in the Kotlin standard library.
Here’s an example of rewriting a Java method that reads the first line from a file using the use function in Kotlin:
The use function is called on a closable resource and receives a lambda as an argument. It ensures that the resource is closed properly, regardless of whether the lambda completes normally or throws an exception. The use function is inlined, meaning it doesn’t introduce any performance overhead.
Note that in the body of the lambda, a non-local return is used to return a value from the readFirstLineFromFile function.
Control flow in higher-order functions
When using higher-order functions like filter or forEach in Kotlin, the behavior of return statements changes. If you use a return statement inside a loop, it’s straightforward to understand that it will exit the loop. However, when you convert the loop into a higher-order function, such as filter, the return statement works differently.
Return statements in lambdas: return from an enclosing function
If you use a return statement inside a lambda passed to a higher-order function like forEach, it will not only exit the lambda but also return from the function that called the lambda. This type of return statement is called a non-local return because it affects a larger block of code than just the lambda itself.
To illustrate this, let’s consider an example. Suppose we have a list of Person objects and we want to find if there is a person named “Alice”:
Kotlin
dataclassPerson(val name: String, val age: Int)val people = listOf(Person("Alice", 29), Person("Bob", 31))funlookForAlice(people: List<Person>) { people.forEach {if (it.name == "Alice") {println("Found!")return } }println("Alice is not found")}
In this example, if the lambda inside forEach encounters a person with the name “Alice,” it will print “Found!” and immediately return from the lookForAlice function. However, if no person named “Alice” is found, it will execute the last line and print “Alice is not found.”
Non-local returns
If you use the return keyword in a lambda, it returns from the function in which you called the lambda, not just from the lambda itself. Such a return statement is called a non-local return because it returns from a larger block than the block containing the return statement. To understand the logic behind the rule, think about using a return keyword in a for loop or a synchronized block in a Java method. It’s obvious that it returns from the function and not from the loop or block
Note that the return from the outer function is possible only if the function that takes the lambda as an argument is inlined. In the example above, the forEach function is inlined, and the body of the forEach function is inlined together with the body of the lambda, so it’s easy to compile the return expression so that it returns from the enclosing function.
Using the return expression in lambdas passed to non-inline functions isn’t allowed. A non-inline function can save the lambda passed to it in a variable and execute it later, when the function has already returned, so it’s too late for the lambda to affect when the surrounding function returns.
Returning from lambdas: return with a label
You can indeed use a local return within a lambda expression in Kotlin. This type of return is similar to a break statement in a for loop. It allows you to terminate the execution of the lambda and continue executing the code from where the lambda was invoked. To differentiate a local return from a non-local return, you use labels.
To use a label with a lambda expression, you place the label name followed by the @ character before the opening curly brace of the lambda. Then, to perform a local return, you use the return@label syntax, where label represents the name of the label.
Here’s an example to demonstrate the use of labels and local returns:
In this code, the lambda expression inside the forEach function is labeled with label@. When the condition it.name == "Alice" is true, the return@label statement is executed, causing the lambda to exit. The program then proceeds with the line println("Alice might be somewhere").
It’s worth noting that you can also use the function name as the label for the lambda expression. Here’s an alternative version of the same code using the function name as the label:
In this case, return@forEach is used to explicitly specify the return label as the function name itself.
Note that if you specify the label of the lambda expression explicitly, labeling using the function name doesn’t work. A lambda expression can’t have more than one label.
Labeled “this” expression
The same rules and concepts of labels also apply to lambdas with receivers. In Kotlin, lambdas with receivers are lambdas that have an implicit context object accessible via the this reference within the lambda. When you specify a label for a lambda with a receiver, you can use the corresponding labeled this expression to access its implicit receiver.
Here’s an example that demonstrates this concept:
Kotlin
println(StringBuilder().apply sb@{ // This lambda’s implicit receiver is accessed by this@sb.listOf(1, 2, 3).apply { // “this” refers to the closest implicit receiver in the scopethis@sb.append(this.toString()) // All implicit receivers can be accessed,the outer ones via explicit labels. }})
Here’s a breakdown of the code:
StringBuilder().apply sb@{...}: This line creates a new StringBuilder instance using the constructor StringBuilder(). The apply function is then called on the StringBuilder instance. The sb@ label is used to explicitly label the lambda expression.
listOf(1, 2, 3).apply {...}: Inside the lambda expression, the apply function is called on a List created using listOf(1, 2, 3). This apply function is invoked on the List itself and not on the StringBuilder instance.
[email protected](this.toString()): The code within the lambda expression appends the string representation of the List (obtained through this.toString()) to the StringBuilder instance. The this@sb syntax refers to the implicit receiver of the outer lambda expression (StringBuilder instance).
It’s important to note that when using labels for lambdas with receivers, you can explicitly specify the label for the lambda expression, or you can use the function name as a label.
Anonymous functions: local returns by default
The non-local return syntax is fairly verbose and becomes cumbersome if a lambda contains multiple return expressions. Multiple return expressions mean lambda expressions that have more than one return statement within their body. This means that the lambda code can have multiple points where it can exit and return a value or terminate the execution.
Here’s an example of a lambda with multiple return statements:
To address this, you can use anonymous functions as an alternative syntax to pass around blocks of code. Anonymous functions provide a concise way to handle multiple returns within a block of code without the need for labels.
Here’s an example that demonstrates the use of anonymous functions to handle multiple returns:
val numbers = listOf(1, 2, 3, 4, 5): This line creates a list of integers containing the numbers 1, 2, 3, 4, and 5.
val result = numbers.map(fun(number): String {...}): The map function is called on the numbers list. It takes an anonymous function as an argument. The anonymous function accepts a single parameter number and returns a String. The map function applies this anonymous function to each element of the numbers list and creates a new list with the transformed values.
if (number % 2 == 0) { return "Even" } else { return "Odd" }: Within the anonymous function, this code block checks if the number is even or odd. If it’s even (when number % 2 == 0), the string “Even” is returned. Otherwise, if it’s odd, the string “Odd” is returned.
The returned string from each iteration of the anonymous function is collected by the map function, resulting in a new list result that contains the strings “Odd”, “Even”, “Odd”, “Even”, and “Odd” in this case.
Note that using anonymous functions can simplify the handling of multiple returns within a lambda expression.
Let’s see, how can this be possible?
An anonymous function is another way to write a block of code passed to a function. It is similar to a regular function, but its name and parameter types are omitted. Here’s an example:
Kotlin
funlookForAlice(people: List<Person>) { people.forEach(fun(person) {if (person.name == "Alice") returnprintln("${person.name} is not Alice") })}
In this example, the anonymous function is used inside the forEach function. It follows the same rules as regular functions for specifying the return type. Anonymous functions with a block body require the return type to be explicitly specified. However, if an expression body is used, the return type can be omitted.
Inside an anonymous function, a return expression without a label will return from the anonymous function itself, not from the enclosing function. The rule is that return returns from the closest function declared using the fun keyword. In contrast, lambda expressions do not use the fun keyword, so a return in a lambda expression returns from the outer function. The difference is illustrated here:
Here’s a comparison between an anonymous function return and a lambda expression return:
Kotlin
funlookForAlice(people: List<Person>) { people.forEach(fun(person) {if (person.name == "Alice") return// Returns from the anonymous function })}funlookForAlice(people: List<Person>) { people.forEach {if (it.name == "Alice") return// Returns from the enclosing function }}
Note that despite the similar appearance to regular function declarations, anonymous functions are another syntactic form of lambda expressions. The implementation details and inlining behavior for lambda expressions also apply to anonymous functions.
“crossinline” Modifier
the crossinline modifier in Kotlin is used to restrict non-local returns from lambdas. It helps ensure that lambdas passed to certain functions cannot use the return keyword to perform a non-local return.
But why do we need this “crossinline”?
Well, sometimes we pass lambdas to functions that are not inlined, such as higher-order functions, local objects, or nested functions. In these cases, the lambda can be executed in a different context from where it was defined. If the lambda could perform a non-local return, it might cause unexpected behavior or make the code harder to understand.
In Kotlin, when we pass a lambda to a higher-order function or a non-inlined function, the lambda can be executed in a different context from where it was defined. This means that the lambda may be called outside its original scope. By default, a lambda can have a non-local return, which means it can exit not only from itself but also from the surrounding function. However, in some cases, allowing non-local returns can lead to unexpected behavior or make the code harder to understand.
Here are a few simplified real-time use cases where crossinline can be useful:
Asynchronous Callbacks: Imagine a scenario where you have an asynchronous operation that takes a callback function. The callback function is executed when the operation completes. If the callback could perform a non-local return, it might prematurely exit the surrounding function, causing unexpected behavior. By marking the callback as crossinline, you ensure that it executes within the proper context and doesn’t exit the surrounding function prematurely.
Resource Management:Consider a function that manages the acquisition and release of resources, such as opening and closing a file. The function takes a lambda as a parameter to perform some operations on the resource. If the lambda could perform a non-local return, it might skip the resource release step, leading to resource leaks. By marking the lambda as crossinline, you ensure that the resource release step is always executed before the function returns.
Error Handling: In error handling scenarios, you might have a function that takes a lambda to handle the error case. If the lambda could perform a non-local return, it might bypass the necessary error handling steps defined within the surrounding function. By using crossinline, you ensure that the error-handling logic remains intact and is always executed within the proper context.
In these use cases, using crossinline helps maintain the expected flow and behavior of the code, preventing unexpected returns or skipping important steps within the surrounding function.
The purpose of crossinline is to enforce that such lambdas cannot perform non-local returns. By marking a lambda parameter as crossinline, we explicitly state that it should not use the return keyword to return from outside the lambda’s scope.
Let’s see an example to illustrate this concept:
Kotlin
inlinefunhigherOrderFunction(crossinline aLambda: () -> Unit) {normalFunction {aLambda() // Using aLambda inside normalFunction }}funnormalFunction(aLambda: () -> Unit) {return// Normal return from normalFunction}funmain() {higherOrderFunction {return// Error: Cannot perform non-local return from a crossinline lambda }}
In this example, we have a higher-order function called higherOrderFunction that takes a lambda parameter aLambda marked as crossinline. Inside higherOrderFunction, we invoke normalFunction and pass aLambda to it. Since aLambda is marked as crossinline, it cannot perform a non-local return.
Now, in the main function, we call higherOrderFunction and provide a lambda that tries to perform a non-local return using return. However, since the lambda is marked as crossinline, this will result in a compilation error.
The use of crossinline in this example ensures that the lambda passed to higherOrderFunction cannot perform non-local returns. This restriction makes the code easier to reason about and prevents potential issues that might arise from non-local returns in certain contexts.
“reified” Type
In Kotlin, the reified modifier is used in combination with the inline keyword to enable type information to be available at runtime for certain generic functions. It allows us to access and manipulate the type parameter of a generic function inside the function body.
By default, type parameters in Kotlin are erased at runtime due to type erasure.
Type erasure refers to the process by which type information is removed or erased at runtime in languages that employ generics. It is a mechanism used by the Java Virtual Machine (JVM) and other platforms to ensure compatibility with code compiled without generics.
In Kotlin, type parameters are erased at runtime due to type erasure, which means that the actual type arguments used when invoking a generic function or creating a generic class are not retained at runtime. Instead, the compiler replaces type parameters with their upper bounds or with the Any type if no upper bound is specified.
For example, consider the following generic function in Kotlin:
Kotlin
fun <T> printType(item: T) {println("Type of item: ${item::class.simpleName}")}
In this case, when the function is invoked with different type arguments, such as Int or String, the compiled bytecode does not retain information about the specific type argument. The type parameter T is erased, and the compiled code behaves as if it were using the Any type.
The consequence of type erasure is that, at runtime, generic code cannot differentiate between different type arguments. It means that within a generic function, you can’t directly access the specific type information of the type parameter T. For example, you can’t invoke functions or access properties specific to T without additional mechanisms.
However, with the reified modifier, we can retain type information within an inline function. This enables us to perform operations that require type-specific information, such as checking the type, accessing properties, or invoking functions specific to that type.
Here’s an example to illustrate the usage of reified:
Kotlin
inlinefun <reifiedT> printType(item: T) {println("Type of item: ${T::class.simpleName}")}funmain() {val number = 42val text = "softAai"printType(number) // Output: Type of item: IntprintType(text) // Output: Type of item: String}
In this example, we have an inline function called printType that takes a parameter named item of type T. The T type parameter is marked with the reified modifier. Inside the function, we use T::class to access the runtime class of T and retrieve its simple name.
In the main function, we call printType twice with different types: Int and String. When the function is inlined, the reified modifier allows us to access the type information of T at runtime. As a result, the type name of the item parameter is printed correctly.
The reified modifier simplifies certain operations that require runtime type information and eliminates the need for workarounds or reflection-based approaches. It improves type safety and enables more expressive and concise code.
It’s important to note that the reified modifier can only be used with inline functions, and it’s applicable only to type parameters of the function itself, not the class. Additionally, reified can be used in combination with other language features, such as is checks, when expressions, and function calls specific to the type T.
Overall, the reified modifier in Kotlin is a powerful tool that allows us to work with type information at runtime within inline functions.
Inline properties
Inline properties in Kotlin provide a way to mark property accessors as inline, allowing them to be inlined as regular functions at the call site. This can lead to improved performance and reduced overhead.
The inline modifier can be applied to the getter and setter accessors of properties that don’t have backing fields. You have the flexibility to annotate individual accessors or the entire property.
Here’s an example to illustrate the usage of inline properties:
In this example, we have an inline property foo with an inline getter. The getter returns an instance of Foo inline, meaning that the code inside the getter will be copied to the call site during compilation.
Similarly, we have an inline property bar with both an inline getter and setter. The getter and setter accessors can contain custom logic, and marking them as inline allows that logic to be inlined at the call site.
At the call site, accessing an inline property is no different from invoking a regular inline function. The property accessors are expanded and copied into the calling code, eliminating the overhead of function calls and providing potential performance benefits.
Let’s dive into a detailed example to explain how marking the setter or getter as inline can eliminate function call overhead.
Consider the following code:
Kotlin
dataclassPerson(val age: Int) {val currentAge: Intinlineget() = age}funmain() {val person = Person(25)println("Current age of the person: ${person.currentAge}")}
When you access the currentAge property using person.currentAge, the getter for the property is invoked. However, since the getter is marked as inline, its code is expanded and copied directly into the calling code (in this case, the println statement). This behavior is similar to invoking a regular inline function.
In this specific example, the getter’s code is quite simple: it returns the value of the age property. This code is directly inserted where the property is accessed,eliminating the overhead of a separate function call. This inlining results in potential performance benefits by avoiding the function call overhead and making the code more efficient.
The benefit of inlining the getter and setter is that the property accessors’ code is copied directly into the calling code during compilation. This eliminates the need for function calls and reduces the overhead associated with them. The result is improved performance, as the property access becomes as efficient as accessing a regular variable.
By using inline properties, we can achieve better performance when working with simple properties that don’t require complex logic in their accessors. However, it’s worth noting that inlining larger or more complex accessors can lead to increased code size, which might impact maintainability and readability.
So, marking the getter or setter as inline in Kotlin allows the code inside the accessors to be copied directly at the call site, eliminating the function call overhead. This results in improved performance when accessing or modifying inline properties.
Restrictions for public API inline functions
In Kotlin, when you have an inline function that is public or protected, it is considered part of a module’s public API. This means that other modules can call that function, and the function itself can be inlined at the call sites in those modules.
However, there are certain risks of binary incompatibility that can arise when changes are made to the module that declares the inline function, especially if the calling module is not re-compiled after the change.
To mitigate these risks, there are restrictions placed on public API inline functions. These functions are not allowed to usenon-public-API declarations, which include private and internal declarations and their parts, within their function bodies.
Using Private Declarations
Kotlin
privatefunprivateFunction() {// Implementation of private function}inlinefunpublicAPIInlineFunction() {privateFunction() // Error: Private declaration cannot be used in a public API inline function// Rest of the code}
In this scenario, we have a private function privateFunction(). When attempting to use this private function within the public API inline function publicAPIInlineFunction(), a compilation error will occur. The restriction prevents the usage of private declarations within public API inline functions.
Using Internal Declarations
Kotlin
internalfuninternalFunction() {// Implementation of internal function}inlinefunpublicAPIInlineFunction() {internalFunction() // Error: Internal declaration cannot be used in a public API inline function// Rest of the code}
In this scenario, we have an internal function internalFunction(). When trying to use this internal function within the public API inline function publicAPIInlineFunction(), a compilation error will arise. The restriction prohibits the usage of internal declarations within public API inline functions.
To eliminate this restriction and allow the use of internal declarations in public API inline functions, you can annotate the internal declaration with @PublishedApi. This annotation signifies that the internal declaration can be used in public API inline functions. When an internal inline function is marked with @PublishedApi, its body is checked as if it were a public function.
Using Internal Declarations with @PublishedApi
Kotlin
@PublishedApiinternalfuninternalFunction() {// Implementation of internal function}inlinefunpublicAPIInlineFunction() {internalFunction() // Allowed because internalFunction is annotated with @PublishedApi// Rest of the code}
In this scenario, we have an internal function internalFunction() that is annotated with @PublishedApi. This annotation indicates that the internal function can be used in public API inline functions. Therefore, using internalFunction() within the public API inline function publicAPIInlineFunction() is allowed.
By applying @PublishedApi to the internal declaration, we explicitly allow its usage in public API inline functions, ensuring that the function remains compatible and can be safely used in other modules.
So, the restrictions for public API inline functions in Kotlin prevent them from using non-public-API declarations. However, by annotating internal declarations with @PublishedApi, we can exempt them from this restriction and use them within public API inline functions, thereby maintaining compatibility and enabling safe usage across modules.
Conclusion
Inline functions in Kotlin offer an effective means of optimizing code efficiency by eliminating the overhead of lambdas and function calls. By replacing function calls with the actual function body, inline functions enhance performance and reduce memory usage. Additionally, advanced concepts like noinline, crossinline, and reified types provide further flexibility and control over inline function behavior. By understanding and leveraging these concepts effectively, developers can optimize their code and enhance application performance in Kotlin projects.
In the ever-evolving world of Android, each version brings its own set of enhancements and improvements. The past couple of Android versions brought some of the major upgrades Android has gotten since its inception. Android 12 introduced Material You, which brought much-needed UI changes, and Android 13 added quality-of-life improvements over Android 12, making it a more polished experience. Much like Android 13, Android 14 may seem like an incremental upgrade, but you would be surprised by just how many internal changes it brings to improve the overall Android experience.
Throughout this blog post, we will delve into the Android 14 new things you need to know as an Android developer in this fast-paced world. Here, we’ll explore how Android 14 empowers you to create exceptional experiences for your users effortlessly.
So, buckle up and get ready to embark on a thrilling adventure into the future of Android development, as we unravel the wonders of Google I/O 2023 and unveil the exciting world of Android 14!
Android 14 New Features
Android 14 is the latest version of Google’s mobile operating system, and it’s packed with new features for both users and developers. Here’s a look at some of the highlights:
Photo Picker
Say goodbye to privacy concerns when it comes to granting access to your photo library! In the past, apps would request access to your entire photo collection even if you just wanted to upload a single picture. This raised legitimate privacy worries since handing over access to all your photos wasn’t the safest option.
Luckily, Android 14 introduces a game-changing solution known as the Photo Picker feature. With this new interface, you have full control over which photos an app can access. Instead of granting unrestricted access, you can now select and share specific photos without compromising your privacy. This means that apps only get access to the photos you choose, ensuring that your entire photo library remains secure.
Thanks to Android 14’s Photo Picker, you can confidently enjoy the convenience of sharing photos while maintaining control over your privacy. It’s a small but significant step towards a safer and more personalized app experience.
Notification Flashes
Android 14 introduces a handy feature called “Notification Flashes” that proves invaluable in noisy environments or for individuals with hearing difficulties. If you often find yourself in situations where you can’t hear your phone’s notifications, this feature has got you covered.
To enable or disable Notification Flashes, follow these simple steps:
Open your phone’s Settings.
Look for the “Display” option and tap on it.
Scroll down and find “Flash notifications.”
You’ll see two toggle options: “Camera Flash” and “Screen Flash.” Toggle them on or off based on your preference.
If you choose to use Screen Flashes, you can even customize the color of the flash. Here’s how:
Within the “Flash notifications” menu, tap on “Screen Flash.”
You’ll be presented with a selection of colors to choose from.
Tap on a color to preview how it will look.
Once you’re satisfied with your choice, simply close the prompt.
With Notification Flashes, you can stay informed about incoming notifications, even in noisy environments or if you have difficulty hearing. It’s a simple yet powerful feature that enhances accessibility and ensures you never miss an important update.
Camera and Battery Life Improvements
Android 14 doesn’t just bring exciting new features but also focuses on enhancing the overall user experience. Google has made significant quality-of-life improvements to ensure a smoother and optimized performance.
One area of improvement is battery consumption. Android 14 is designed to be more efficient, helping to prolong your device’s battery life. This means you can enjoy using your phone for longer periods without worrying about running out of power.
Moreover, both the user interface (UI) and internal workings of Android 14 have been refined to provide a seamless experience. You can expect a smoother and more responsive interface, making navigation and app usage more enjoyable.
In addition to the general improvements, Android 14 introduces new camera extensions. These extensions optimize the post-processing time and enhance the quality of the images captured. If you have a Pixel device powered by the Tensor G2 chip, you’ll notice an even greater improvement in the camera department. The Tensor G2 chip brings significant advancements that further enhance the camera capabilities, resulting in stunning photos with reduced processing time.
With Android 14, you can look forward to a more efficient and polished experience, along with impressive camera enhancements, especially on Pixel devices powered by the Tensor G2 chip. Get ready to enjoy a smoother and more captivating Android journey!
Upcoming Features
As Android 14 is still in the development stage(currently in beta), the upcoming stable version may include or discard these proposed upcoming features.
LockScreen Customizations
One of the exciting features coming to Android 14 is the ability to customize your lock screen. This means you can personalize how your lock screen appears, including changing the clock style and customizing the app shortcuts located at the lower corners. This feature draws some inspiration from iOS 16.
These lock screen customizations are expected to be available in the stable Android 14 release, which is scheduled to launch next month if everything goes as planned for Google. However, it’s worth noting that the lock screen clock styles showcased at Google I/O 2023 weren’t particularly appealing, appearing somewhat flat. Hopefully, the final versions will have more vibrant and engaging styles to choose from.
Magic Compose
Google has an exciting feature called “Magic Compose” coming to the Messages app this summer. It works similarly to the AI generative features demonstrated at Google I/O 2023, which will be added to Google’s Workspace apps. Magic Compose helps you write text messages with different moods and styles. From the preview showcased at I/O, it looks really cool.
For example, if you type “Wanna grab dinner,” Magic Compose offers various rewrites that add excitement, lyrical flair, or even Shakespearean language. It’s a clever feature that adds fun and creativity to your messages. We hope it will eventually be available on Gboard as well. It seems like Google’s way of encouraging more people to use RCS and Google Messages in general. However, please note that Magic Compose is currently limited to Pixel devices.
Emoji, Generative AI, and Cinematic Wallpapers
Android has always been known for its customization options, and Android 14 takes it a step further with the addition of Emoji, Generative AI, and Cinematic wallpapers.
The Emoji wallpaper picker lets you create a unique and interactive wallpaper by selecting a few emoji and a dominant color. It combines them to create a fun and personalized wallpaper that reflects your favorite emoji.
The AI Generative Wallpaper feature is particularly exciting. It allows you to input a few words describing the type of wallpaper you want and then generates a selection of unique wallpapers exclusively for your device. These wallpapers are completely one-of-a-kind and tailored to your preferences.
Cinematic wallpapers bring depth and a parallax effect to your photos using AI. You can choose a photo and the feature will add a dynamic effect that responds to your device’s movements. It’s similar to the Cinematic feature in Google Photos, adding a captivating visual element to your device’s wallpaper.
With these customizable features, Android 14 offers even more ways to personalize your device and make it truly your own. Whether it’s through emoji mashups, generative wallpapers, or dynamic effects, Android 14 provides an enhanced level of customization for a unique and enjoyable user experience.
New Find My Device Experience
The Find My Device app on Android has received a fresh new look to match the latest design language. In addition, it will be receiving some exciting new features this fall. One of the notable additions is the expanded device support, allowing you to locate not only your phones but also accessories using other Android devices on the network.
This enhancement is a welcome addition to Android, as Apple has been a leader in the Find My iPhone experience. Furthermore, if you want to track larger objects like bicycles, manufacturers such as Tile and Chipolo will offer tracker tags that can be used with the Find My Device app.
With these updates, Android users can enjoy a more comprehensive and convenient way to locate their devices and belongings. It’s a great step forward in enhancing the Find My Device experience on Android.
Tracker Prevention and Alerts
Although Google’s efforts to convince Apple to adopt RCS have not been successful, both companies have collaborated on enhancing privacy measures, particularly with Tracker Prevention alerts.
BTW, RCS (Rich Communication Services) is an advanced messaging protocol replacing SMS, offering additional features and capabilities. Some of the features offered by RCS include read receipts, typing indicators, high-quality media sharing, group chats, and the ability to send messages over Wi-Fi or mobile data.
Regardless of the Android device you’re using, if an unidentified tracker is monitoring your activities, your Android device will provide a warning and assist you in locating the source. This collaboration between Google and Apple in the privacy department is a significant achievement, ensuring enhanced privacy and security for Android users.
Using your Android device as a Webcam
If you’re disappointed with the low-quality webcam on your laptop, hold off on buying an external webcam just yet. Android 14 might come with a fantastic feature that allows you to use your Android device as an external camera and stream in high-definition at 1080p.
To use this feature, simply connect your Android device to your PC and a menu will pop up. From there, select “webcam” to switch to using your phone’s camera. Currently, this feature is not available in the operating system, even as an experimental option, but it’s expected to be included in Android 14 if Google deems it ready for release.
With Android 14, you could potentially transform your Android device into a high-quality webcam, eliminating the need for an external camera. Keep an eye out for this exciting feature, which aims to provide a better video conferencing and streaming experience for Android users.
App Cloning
App Cloning is undoubtedly one of the most highly anticipated features in Android. In the past, users had to resort to downloading third-party app cloning utilities that often came bundled with spyware. However, with Android 14, Google plans to address this by introducing a native App Cloning utility.
App Cloning allows you to have two instances of the same app on your device. This feature is particularly useful for users with dual SIM phones who want to use multiple accounts of apps like WhatsApp simultaneously. By cloning the app and logging in with a secondary SIM card, you can have two separate accounts running concurrently.
Google initially hinted at the App Cloning feature during the Android 14 Developer Preview 1. However, there haven’t been any recent updates regarding its development. It is speculated that App Cloning may not be included in the initial stable release of Android 14. However, it is expected to be introduced in future Android 14 feature drop updates, specifically for Pixel users.
The addition of a native App Cloning utility will bring convenience and ease of use to Android users who require multiple instances of certain apps. While its exact timeline for availability remains uncertain, it is an exciting feature to look forward to in future updates of Android 14.
Predictive Back Gestures
Predictive back gestures were introduced in Android 14 Developer Preview 2 but were later removed in the following preview. These gestures allowed users to perform a slow back swipe to reveal the underlying app layer. This was particularly useful when you couldn’t remember the previous page or layer you were on.
By using predictive back gestures, you could check the layer below without losing the contents of the current page. It gave you the flexibility to verify if the previous layer was the one you intended to navigate to.
Initially, this feature was only supported in the Settings app and a few other system apps. However, it remains uncertain whether predictive back gestures will be included in the first stable release of Android 14. If not, there’s a possibility that it will be added in future feature updates.
While the fate of predictive back gestures in Android 14 is unclear, it presented an interesting way to navigate within apps and explore layers. We will have to wait and see if it becomes a part of the official release or is introduced in future updates.
App Pair
During Google I/O 2023, Google unveiled a feature called App Pair, which will be introduced in Android 14 later this year. This feature, showcased during the Pixel Fold announcement, allows users to pair and use apps together in split screens. You can also minimize or maximize them simultaneously.
At first glance, App Pair may not appear particularly useful for smartphones. However, with the increasing popularity of tablets, this feature could be a game-changer. It offers a compelling reason why Android tablets are no longer considered inferior to iPads.
With App Pair, users will have the ability to multitask more effectively on larger screens. By pairing apps in split screens, you can simultaneously use two apps side by side, enhancing productivity and convenience. Whether it’s taking notes while reading, watching a video while browsing the web, or messaging while referencing another app, App Pair makes multitasking on Android tablets a seamless experience.
The inclusion of App Pair in Android 14 demonstrates Google’s commitment to enhancing the tablet experience and bridging the gap between Android tablets and their competitors. It opens up new possibilities for users who rely on tablets for work, entertainment, or any other tasks that require multitasking.
With this upcoming feature, Android tablets are poised to offer a more compelling and competitive alternative to iPads, providing users with a powerful multitasking experience. Look forward to the release of Android 14 to enjoy the benefits of App Pair on compatible devices.
Partial Screen Recorder
In Android 14, a new screen recording feature called “Partial Screen Recording” may be introduced. Despite its name, it doesn’t mean recording only a selected area of the screen. Instead, it allows you to record a specific app without capturing any UI elements or notifications that might appear on the screen.
This feature works similarly to how Discord handles screen sharing. When you switch to view another app or the home screen during the recording, the recorded content will appear black. However, as soon as you switch back to the app you want to record, the content will be visible again. It’s a clever and convenient way to focus solely on recording the app without any distractions.
While the availability of the Partial Screen Recording feature in the official release of Android 14 is not confirmed, it is an exciting addition that can enhance the screen recording experience for users. So, keep an eye out for this neat feature in future Android updates.
Drag and Drop Text and Images to Different Apps
One exciting feature that Android 14 is expected to bring is the ability to drag and drop text and images between apps, similar to what iOS 15 offers. In the Android 14 Beta 3 build, you can already experience this feature with text, and it works seamlessly.
To use the text drag and drop feature, simply select the text you want to move, long press on it, and then drag it to another app where you want to paste the text. With your other hand, switch to the desired app and drop the text into the text area. It’s a convenient way to transfer text quickly and easily between different apps.
While the current beta version only supports text drag and drop, it is anticipated that the final Android 14 release will also include the ability to drag and drop images. This will allow you to effortlessly move images from one app to another, enhancing your productivity and ease of use.
Keep an eye out for the official Android 14 update to enjoy the full drag and drop functionality, making it simpler and more convenient to transfer both text and images between apps on your Android device.
Forced Themed Icons
One of the challenges with adaptive mono icons in Android 12 is that app developers need to add support for them. Without proper support, the overall experience may feel incomplete. However, in Android 13, Google introduced a feature that automatically converts icons to themed icons if they are not supported by developers. This helpful feature may also make its way to Android 14.
Currently, the Pixel launcher has a hidden flag that allows users to force themed icons, which has been present since Android 13 QPR Beta 3. This suggests that Google might enable this feature in the future. If enabled, it will contribute to a seamless and intuitive Android experience, ensuring that the icons match the overall theme of the device.
With automatic icon conversion, users won’t have to worry about inconsistent or mismatched icons on their devices. Android 14 aims to enhance the visual cohesiveness of the user interface, making it more polished and pleasing to the eye.
Keep an eye out for this feature in the upcoming Android 14 release, as it has the potential to improve the overall aesthetic and user experience on your Android device.
Conclusion
Android 14 introduces a range of features and improvements that enhance user experience. It offers customization options like LockScreen customizations and Emoji wallpaper pickers, along with privacy enhancements such as Tracker Prevention alerts. Quality of life improvements includes the Photo Picker feature and Notification Flashes. The update brings camera advancements, App Cloning utility, predictive back gestures, and the ability to use Android devices as external cameras. Android 14 promises a seamless and personalized experience, focusing on user customization and functionality.