preface

The way generics are used in Kotlin is largely similar to Java, with some feature differences. Whether it’s generics in Java or in Kotlin, there are some concepts that can be confusing. Let’s go back to Kotlin’s generics in conjunction with Java generics.

The use of generics in Kotlin

In the use of generics, the most commonly used are generic functions and generic classes. Before we look at the use of these two methods, let’s look at the type parameters of generics.

Generic type parameters

The type parameter of a generic type is the type parameter defined when the generic type is declared, indicating a type defined when the generic type is used. Type parameters are replaced with type arguments during generic instantiation. For example, when List is instantiated as List, the type parameter T is replaced with the type argument String.

Since generics were introduced to Java in Java 1.5, generics without type parameters are allowed in Java for compatibility with older versions. List List = new ArrayList(); Such life is allowed to exist. But in Kotlin a generic declaration must either declare a type parameter or be able to derive a type parameter. Val list = listof(“a”, “b”); The type parameter is pushed to String or the List is declared to be String.

Generic functions and generic classes

// Generic functions
fun <T> someFunc(item: T): List<T> {
    // do something
}
Copy the code
/ / a generic class
class Hello<T> {
    val value: T
}
Copy the code

As you can see, generic functions and generic classes are used in Kotlin in much the same way as in Java. In addition, Kotlin supports the ability to specify multiple constraints for a type.

Parameter type constraint

In Kotlin, you can specify constraints on type parameters, such as fun sum() T. This generic function specifies that the upper limit of type parameters is Number, which can only be Number or a subclass of Number. This is represented in Java as the extends implementation, which is:

.

In addition to the above constraints, Kotlin can specify multiple constraints.

fun <T> someFunc(value: T) where T : CharSequence, T : Appendable {
	if(! value.endsWith('. ')) {
		value.append('. ')}}Copy the code

The above generic function specifies multiple type parameter constraints, in which case the type parameter must implement the CharSequence and Appendable interfaces.

Null and non-null for type parameters: In Kotlin each type is null and non-null, and this is also true when generics are implemented. For example, a List < String? > declares the type argument to be a nullable String, while List declares the type argument to be a non-nullable String.

Run-time generics

As with Java, Kotlin’s type information is erased at runtime, and with type erasures the type disappears.

The impact of type erasure in Kotlin

  • Function arguments are affected by type erasings, such as fun func1(list: list); fun func2(list: List); These two use List generics, which are affected by type erasings, and at run time cannot tell the difference between the two function arguments;
  • Type check. After type erasure, the runtime cannot check the corresponding type. For example, value is List. Kotlin’s alternative is value is List<*>, which uses asterisk projection checks.

Implements type parameters

To implement type parameters means that type parameters can be used. This feature was added to Kotlin, usually in inline functions

inline fun <reified T> isA(value: Any) = value is T
Copy the code

You can see that the type parameter T can be used directly in inline functions. Such code would not compile properly in normal functions (Error: Cannot check for instance of Erased Type: T). It can be used in inline functions because inline functions are inserted directly into the call as bytecode, so type arguments can be retrieved at run time. Also, the reified keyword is used to state that type parameters are not erased at runtime.

Type change in Kotlin

Not to distort

What Kotlin means by immutable is that, for example, for two arbitrary types A and B, MutableList is neither A subtype nor A supertype of MutableList, so it becomes immutable in terms of the type parameters (all types in Java are immutable).

covariance


In Kotlin for A generic class, if A is A subtype of B, SomeClass is A subtype of SomeClass, that is, the subtype is preserved, which is covariant.

// Classes are declared covariant on T
class SomeClass<out T> {
	fun someFinc(a): T
}
Copy the code

This is how covariation is declared in the code above, by adding the out keyword. Covariance can only produce type T but not consume type T. For example, covariance is read-only.

The equivalent in Java is <? The upper bound wildcard of extends T>

inverter

In Kotlin, contravariant can be seen as a mirror image of covariant, and here is how it is declared.

// Declare the invert type
interface Comparator<in T> {
	fun compare(e1: T, e2: T): Int {} // Declare inverting using the in keyword
}
Copy the code

Contravariant types can be written, but only base types can be returned.

In Java, the equivalent is <? Super T> lower bound wildcard.

The asterisk projection

In Kotlin, it is safe to use generics with star projections when the parameter type is unknown. But MutableList<*> and MutableList<Any? > is not the same. MutableList< * > equals MutableList<out Any? >, is read-only. Because it is safe to read and not safe to write without specifying the specific type.

The equivalent in Java is <? > unbounded wildcard.

conclusion

Generics in Kotlin are the same as In Java, with a few new Kotlin features (such as multi-constrained types, parameters to be implemented, etc.), and the rest in Java.

This article doesn’t go into any more details than listing what generics are in Kotlin and how they relate to Java generics. Those of you who are interested can go further.