Before we get to the kotlin keyword, let’s talk briefly about generics in Java. We use generics a lot in programming for reuse and efficiency purposes. Generics are implemented through a mechanism of type erasing underlying the JVM, as is The case with Kotlin.
The generic
Generics are a unique feature in Java SE 1.5. The nature of generics is parameterized types, which can be divided into generic classes, generic interfaces, and generic methods. Without generics under can only be achieved by reference to the Object parameters of arbitrary, the shortcoming is to explicitly casts, and casting is not examined at compile time, easy to leave the question until run time, so the benefits of the generic type is the compile-time type safety examination, and all of the casting are automatically and implicitly, Improved code reuse and avoidance of ClasscastExceptions at runtime.
Generics were introduced in JDK 1.5 to allow strong types to be type checked at compile time; Generic instantiation types in JDK 1.7 have automatic inference capabilities, such as List
mList = new ArrayList
() can be written as List
mList = new ArrayList<>()
Type erasure
Generics are implemented by type wiping. The compiler erases all information about generic types at compile time, meaning that there is no information about generic types at runtime. For example, List
is represented by only a List at runtime, which is intended to be compatible with Java versions prior to 1.5.
fun test() {
val mList= ArrayList<String>()
mList.add("123")
Log.v("tag",mList[0])
}
Copy the code
The bytecode is as follows:
public final test()V
L0
LINENUMBER 18 L0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1
L1
LINENUMBER 19 L1
ALOAD 1
LDC "123"INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;) Z POP L2 LINENUMBER 20 L2 LDC"tag"ALOAD 1 ICONST_0 INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object; CHECKCAST java/lang/String INVOKESTATIC android/util/Log.v (Ljava/lang/String; Ljava/lang/String;) I POP L3 LINENUMBER 21 L3 RETURN L4 LOCALVARIABLE mList Ljava/util/ArrayList; L1 L4 1 LOCALVARIABLE this Lcom/github/coroutinesdemo/Test; L0 L4 0 MAXSTACK = 3 MAXLOCALS = 2Copy the code
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;) Z list.add(“123”) is actually “123” stored in the collection as Object
INVOKEVIRTUAL Java /util/ arrayList. get (I)Ljava/lang/Object
CHECKCAST Java /lang/String type conversion
Generic erasers perform type checking before type erasers when coding bytecode (that is, all type parameters are replaced with qualified types, including classes, variables, and methods. If the type variables are finite, the primitive type is replaced with the type of the first boundary. Class Test<T extends Comparable & Serializable> {}
Bridge methods are generated in subclasses if type erasures and polymorphisms conflict, then casts are inserted when the method is called if the return type of the calling generic method is erased.
Type erasure problem
Type erasure has a number of problems that I won’t expand here
- Automatic casting is an issue when a generic method is read, so if the return type of the calling generic method is erased, a cast is inserted when the method is called
- Generic type parameters cannot be primitive types. Erased Object is a reference type, not a primitive type
- Cannot do runtime type checking for specific generic parameter types,
instanceof ArrayList<? >
- Objects of generic classes cannot be thrown or caught because exceptions are caught and thrown at run time, and the generic information is erased at compile time, making the two catches the same thing. You cannot use generic variables in the catch clause because the generic information is replaced by the original type at compile time (for example, catch(T) becomes Throwable in the case of qualifiers). If you can use it in the catch clause, you violate the order in which exceptions are caught
fun <T>Int.toCase():T? {return (this as T)
}
Copy the code
The code above is unchecked when casting the type, which could result in an unchecked runtime crash. The compiler prompts an unchecked cast warning that will crash if the type of data obtained is not what it expects
fun testCase() { 1.toCase<String>()? .substring(0) }Copy the code
This results in a TypeCastException error, so it is usually necessary to pass class information explicitly in order to safely retrieve data:
fun <T> Int.toCase(clz:Class<T>):T? {return if (clz.isInstance(this)){
this as? T
}else{
null
}
}
Copy the code
fun testCase() { 1.toCase(String::class.java)? .substring(0) }Copy the code
But it’s too cumbersome to pass class by display, especially when passing multiple types of arguments. Type-based erasers don’t get the type of T at runtime, so use the safe conversion operators as or as?
fun <T> Bundle.putCase(key: String, value: T, clz:Class<T>){
when(clz){
Long::class.java -> putLong(key,value as Long)
String::class.java -> putString(key, value as String)
Char::class.java -> putChar(key, value as Char)
Int::class.java -> putInt(key, value as Int)
else -> throw IllegalStateException("Type not supported")}}Copy the code
Is there an elegant implementation that excludes this passing argument??
Reified keyword
The reified keyword is simple to use:
-
Add reified to the front of a generic type
-
Add inline before the method
Improve the above code
inline fun <reified T> Int.toCase():T? {return if (this is T) { this } else { null } } Copy the code
TestCase () method call into Java code:
public final void testCase() { int $this$toCase$iv = 1; int $i$f$toCase = false; String var10000 = (String)(Integer.valueOf($this$toCase$iv) instanceof String ? Integer.valueOf($this$toCase$iv) : null); // Inline part String var1;if(var10000 ! = null) {// Start the replacement var1 = var10000;$this$toCase$iv = 0; if (var1 == null) { throw new TypeCastException("null cannot be cast to non-null type java.lang.String"); } var10000 = var1.substring($this$toCase$iv); Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).substring(startIndex)"); } else { var10000 = null; } // reified替换结束 var1 = var10000; System.out.println(var1); } Copy the code
I won’t go into Inline here, but what are noinline and Crossinline? You can see it here.
At runtime, generics are erased by type, but in inline functions we can specify that the type is not erased, because at compile time inline functions copy bytecode to the method that calls them, so the compiler knows what the specific type of the generic is in the current method and replaces the generic with the specific type. To avoid erasure, inline functions can be replaced by the reified keyword at compile time
The sample
How do we parse json data with Gson into a generic type Bean structure? TypeToken is a way to get the generic parameter types of the generic classes we use using getType(). However, with reflection resolution, Gson calls the default no-parameter constructor when constructing an object instance, so it relies on the generic parameter information stored in Java’s Class bytecode. Java generics mechanism while erasing at compile time, but the Java at compile time in byte code instruction set outside to keep some generic information, interfaces, classes, methods, all the members of generics, variable declarations on the definition of a generic will be reserved type information, in other parts of the generic information will be erased, This information is stored in the class bytecode constant pool. Code that uses generics generates a signature field that specifies the address of the constant pool. The JDK provides methods to read this generic information. Reflection can be used to obtain the specific types of generic parameters, such as:
(mList.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
Copy the code
General Gson analysis:
inline fun <reified T> Gson.fromJson(jsonStr: String) =
fromJson(json, T::class.java)
Copy the code
If Moshi is used,
inline fun <reified T> Moshi.fromJson(jsonStr: String) = Moshi.Builder().add(KotlinJsonAdapterFactory()).build().adapter(T::class.java).fromJson(jsonStr)
Copy the code