Kotlin-KClass解析
Kotlin-KClass探究
引入
为什么会遇到这个问题?
工作中, 在Json中尝试将获取到的Json字符串转化为特定对象时, 使用到Boolean::class.java
来表示接收一个布尔值, 但最终却没有拿到该值, 使用Boolean::class.javaObjectType后成功解决该问题, 因此产生了探究Kotlin与Java Class之间区别的兴趣。来一探究竟 KClass是什么?
通过这篇文章将了解到什么? ❓
- Kotlin Class的表示以及与Java Class的区别
- Kotlin KClass结构以及如何创建一个KClass
- Kotlin ::class语法糖到底表示什么
- Kotlin class.javaObjectType区别
介绍
Java中的Class
以前在Java中, 表达一个类的类对象时, 经常会使用ExampleClass.class
或 exampleClassInstance.getClass()
, 如以下表达式。两者表达方式不同但过程和结果都相同, 可以理解为.class是从语法糖上的调用, getClass()是基于实例的调用, 编译后最终都转化为字节码指令存储在class文件中
public static void main(String[] args) {
System.out.println(String.class);
String str = "123";
System.out.println(str.getClass());
}
class java.lang.String
class java.lang.String
而getClass是Object类的native方法, 在Native层获取运行时Class在常量区中的信息
// Object.java
/**
* Returns the runtime class of this {@code Object}. The returned
* {@code Class} object is the object that is locked by {@code
* static synchronized} methods of the represented class.
*
* <p><b>The actual result type is {@code Class<? extends |X|>}
* where {@code |X|} is the erasure of the static type of the
* expression on which {@code gmm,etClass} is called.</b> For
* example, no cast is required in this code fragment:</p>
*
* <p>
* {@code Number n = 0; }<br>
* {@code Class<? extends Number> c = n.getClass(); }
* </p>
*
* @return The {@code Class} object that represents the runtime
* class of this object.
* @jls 15.8.2 Class Literals
*/
@IntrinsicCandidate
public final native Class<?> getClass();
好奇Java中如何表示基本数据类型的class
System.out.println(int.class);
//output
int
看下字节码反编译后的结果
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #13 // Field java/lang/Integer.TYPE:Ljava/lang/Class;
6: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
9: return
LineNumberTable:
line 7: 0
line 8: 9
}
可以看到其实调用的是Integer的Type,那这个Type是什么呢?别急,后面我们会说到,这里先留个印象。
Kotlin中的Class
KClass
在kotlin中, 我们经常使用Example::class
以及 Example:class.java
来表示一个运行时类。我们知道::class上表示类引用的一种方式, 那到底是个什么东西呢? Introduce local variable后再看下, 发现其实质上一个KClass<T>
对象
fun main() {
val kClass: KClass<String> = String::class
println(kClass)
}
// output:
// class java.lang.String (Kotlin reflection is not available)
看下源码看KClass到底是个什么东西呢?
/**
* Represents a class and provides introspection capabilities.
* Instances of this class are obtainable by the `::class` syntax.
* See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/reflection.html#class-references)
* for more information.
*
* @param T the type of the class.
*/
public actual interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
/**
* All functions and properties accessible in this class, including those declared in this class
* and all of its superclasses. Does not include constructors.
*/
override val members: Collection<KCallable<*>>
public val constructors: Collection<KFunction<T>>
/**
* All classes declared inside this class. This includes both inner and static nested classes.
*/
public val nestedClasses: Collection<KClass<*>>
/**
* The instance of the object declaration, or `null` if this class is not an object declaration.
*/
public val objectInstance: T?
/**
* The list of type parameters of this class. This list does *not* include type parameters of outer classes.
*/
public val typeParameters: List<KTypeParameter>
public val supertypes: List<KType>
public val visibility: KVisibility?
actual override fun equals(other: Any?): Boolean // KT-24971
actual override fun hashCode(): Int // KT-24971
public actual val simpleName: String?
public actual val qualifiedName: String?
public val isFinal: Boolean
public val isOpen: Boolean
public val isData: Boolean
public val isSealed: Boolean
public val isCompanion: Boolean
public val isFun: Boolean
}
KClass接口
KClass实现了三个接口,目前只了解Annotated以及Declaration
/**
* A classifier is either a class or a type parameter.
*
* @see [KClass]
* @see [KTypeParameter]
*/
@SinceKotlin("1.1")
public interface KClassifier
/**
* Represents an annotated element and allows to obtain its annotations.
* See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/annotations.html)
* for more information.
*/
public interface KAnnotatedElement {
/**
* Annotations which are present on this element.
*/
public val annotations: List<Annotation>
}
/**
* Represents an entity which may contain declarations of any other entities,
* such as a class or a package.
*/
public interface KDeclarationContainer {
/**
* All functions and properties accessible in this container.
*/
public val members: Collection<KCallable<*>>
}
从上可以看出, KClass是一个接口, 是一种“规范”的定义, 具体包括了一个KClass中的所有构造函数声明、所有成员方法与函数的声明、类的类型声明等等...
那说到接口, 则必然谈到实现, 没有被实现的接口如同纸上谈兵, 是没有意义的, 看下是哪个类实现了KClass
public class ClassReference(override val jClass: Class<*>) : KClass<Any>, ClassBasedDeclarationContainer {
override val simpleName: String?
get() = getClassSimpleName(jClass)
override val qualifiedName: String?
get() = getClassQualifiedName(jClass)
@SinceKotlin("1.1")
override fun isInstance(value: Any?): Boolean =
isInstance(value, jClass)
//...
override fun equals(other: Any?) =
other is ClassReference && javaObjectType == other.javaObjectType
override fun hashCode() =
javaObjectType.hashCode()
override fun toString() =
jClass.toString() + Reflection.REFLECTION_NOT_AVAILABLE
companion object {
}
}
需要注意的是, 这里的jClass类型即为Java的Class,可理解为将jClass封装为kClass
public interface ClassBasedDeclarationContainer : KDeclarationContainer {
public val jClass: Class<*>
}
那现在我们知道了KClass接口的实际实现类ClassReflection, 那该类是在哪里用到呢?
实际上其是通过反射来进行构建
package kotlin.jvm.internal;
public class ReflectionFactory {
private static final String KOTLIN_JVM_FUNCTIONS = "kotlin.jvm.functions.";
public KClass createKotlinClass(Class javaClass) {
return new ClassReference(javaClass);
}
}
package kotlin.jvm.internal;
/**
* This class serves as a facade to the actual reflection implementation. JVM back-end generates calls to static methods of this class
* on any reflection-using construct.
*/
public class Reflection {
private static final ReflectionFactory factory;
static {
ReflectionFactory impl;
try {
Class<?> implClass = Class.forName("kotlin.reflect.jvm.internal.ReflectionFactoryImpl");
impl = (ReflectionFactory) implClass.newInstance();
}
catch (ClassCastException e) { impl = null; }
catch (ClassNotFoundException e) { impl = null; }
catch (InstantiationException e) { impl = null; }
catch (IllegalAccessException e) { impl = null; }
factory = impl != null ? impl : new ReflectionFactory();
}
/* package */ static final String REFLECTION_NOT_AVAILABLE = " (Kotlin reflection is not available)";
private static final KClass[] EMPTY_K_CLASS_ARRAY = new KClass[0];
public static KClass createKotlinClass(Class javaClass) {
return factory.createKotlinClass(javaClass);
}
::class语法糖是如何得到KClass的呢
目前我们明白的是, Kotlin在有需要调用KClass时, 会通过Reflection来调用createKotlinClass
方法, 基于ClassReflection 通过Java的Class创建KClass,该过程可能是在JVM中执行, 也可能是在编译后字节码中运行,具体是哪一种,还不清楚。
本着打破砂锅问到底的心态, Decompile了下Kotlin代码, 发现是这样的。
// Test.Kt
fun main() {
val kClass: KClass<String> = String::class
kClass.isInstance("123")
}
// Decompile .class
public static final void main() {
KClass kClass = Reflection.getOrCreateKotlinClass(String.class);
kClass.isInstance("123");
}
与猜想基本一致, 那在字节码层面上是怎样的呢?用kotlinc编译kt文件为字节码文件,然后再用javap
反编译看下实现,确认了之前的猜想,即:class
在kotlin层面, 在kotlinc编译时会处理为Reflection.getOrCreateKotlinClass
进而获得KClass, 该操作是基于kotlin编译器对语法糖的处理。
public static final void main();
descriptor: ()V
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=0
0: ldc #8 // class java/lang/String
2: invokestatic #14 // Method kotlin/jvm/internal/Reflection.getOrCreateKotlinClass:(Ljava/lang/Class;)Lkotlin/reflect/KClass;
5: astore_0
6: aload_0
7: ldc #16 // String 123
9: invokeinterface #22, 2 // InterfaceMethod kotlin/reflect/KClass.isInstance:(Ljava/lang/Object;)Z
14: pop
15: return
LineNumberTable:
line 6: 5
line 7: 6
line 8: 15
LocalVariableTable:
Start Length Slot Name Signature
6 10 0 kClass Lkotlin/reflect/KClass;
一个小例子
比如如下代码, 我们想调用基于KClass的isInstance方法
fun main() {
val kClass: KClass<String> = String::class
kClass.isInstance("123")
}
最终会发现, 其调用直接走到了ClassReference.Companion中的静态方法isInstance中,也可以证明其类型为ClassReference
public fun isInstance(value: Any?, jClass: Class<*>): Boolean {
FUNCTION_CLASSES[jClass]?.let { arity ->
return TypeIntrinsics.isFunctionOfArity(value, arity)
}
val objectType = if (jClass.isPrimitive) jClass.kotlin.javaObjectType else jClass
return objectType.isInstance(value)
}
入题
那说了这么多, 现在基本知道了 KClass的结构、调用过程, 那就来看看javaObjectType与::class.java有什么不同吧
println(123::class.java)
println(123::class.javaObjectType)
同时打印以上两句, 有以下结果
int
class java.lang.Integer
那就来看下为什么会有此不同吧。
javaObjectType
实质: Kotlin扩展函数语法糖, 基于KClass的带泛型的成员变量,返回Java中Class类型
// JvmClassMapping.kt
import java.lang.Boolean as JavaLangBoolean
import java.lang.Byte as JavaLangByte
import java.lang.Character as JavaLangCharacter
import java.lang.Double as JavaLangDouble
import java.lang.Float as JavaLangFloat
import java.lang.Integer as JavaLangInteger
import java.lang.Long as JavaLangLong
import java.lang.Short as JavaLangShort
/**
* Returns a Java [Class] instance corresponding to the given [KClass] instance.
* In case of primitive types it returns corresponding wrapper classes.
*/
public val <T : Any> KClass<T>.javaObjectType: Class<T>
get() {
val thisJClass = (this as ClassBasedDeclarationContainer).jClass
if (!thisJClass.isPrimitive) return thisJClass as Class<T>
return when (thisJClass.name) {
"boolean" -> JavaLangBoolean::class.java
"char" -> JavaLangCharacter::class.java
"byte" -> JavaLangByte::class.java
"short" -> JavaLangShort::class.java
"int" -> JavaLangInteger::class.java
"float" -> JavaLangFloat::class.java
"long" -> JavaLangLong::class.java
"double" -> JavaLangDouble::class.java
"void" -> Void::class.java
else -> thisJClass
} as Class<T>
}
- 从之前的源码, 我们知道KClass实现了ClassBasedDeclarationContainer,因此jClass代表其实际的Java的Class对象
- 此处判断我们先剔除掉非基本类型的Class, 返回Java的Class泛型对象即可
- 对于基本类型, 则根据其Class的name字段来手动判断并转化为Class对象,这里用import ... as ...来实际指代Java中的基本类型的包装类, 有点意思, 之前没有看过这种Kotlin语法
查漏补缺--Object isPrimitive方法
Java Object类中有isPrimitive方法
作用: 判断一个Object类的Class是否为基本类型对象(9种, 包括Void)
/**
* Determines if the specified {@code Class} object represents a
* primitive type.
*
* <p> There are nine predefined {@code Class} objects to represent
* the eight primitive types and void. These are created by the Java
* Virtual Machine, and have the same names as the primitive types that
* they represent, namely {@code boolean}, {@code byte},
* {@code char}, {@code short}, {@code int},
* {@code long}, {@code float}, and {@code double}.
*
* <p> These objects may only be accessed via the following public static
* final variables, and are the only {@code Class} objects for which
* this method returns {@code true}.
*
* @return true if and only if this class represents a primitive type
*
* @see java.lang.Boolean#TYPE
* @see java.lang.Character#TYPE
* @see java.lang.Byte#TYPE
* @see java.lang.Short#TYPE
* @see java.lang.Integer#TYPE
* @see java.lang.Long#TYPE
* @see java.lang.Float#TYPE
* @see java.lang.Double#TYPE
* @see java.lang.Void#TYPE
* @since 1.1
*/
@IntrinsicCandidate
public native boolean isPrimitive();
java
实质: 直接返回jClass,不会对基本类型进行特殊处理
/**
* Returns a Java [Class] instance corresponding to the given [KClass] instance.
*/
@Suppress("UPPER_BOUND_VIOLATED")
public val <T> KClass<T>.java: Class<T>
@JvmName("getJavaClass")
get() = (this as ClassBasedDeclarationContainer).jClass as Class<T>
那基本类型没有字面上的Class, 则当然也不能用KClass表示, 那这里怎么还能够用123::class.java的方式呢?就要看下::class
是不是对基本类型进行了特殊处理
println(123::class)
println(123::class.java)
println(123::class.javaObjectType)
kotlinc编译后再用javap看下
public static final void main();
descriptor: ()V
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=0, args_size=0
0: getstatic #12 // Field java/lang/Integer.TYPE:Ljava/lang/Class;
3: invokestatic #18 // Method kotlin/jvm/internal/Reflection.getOrCreateKotlinClass:(Ljava/lang/Class;)Lkotlin/reflect/KClass;
6: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
9: swap
10: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
13: getstatic #12 // Field java/lang/Integer.TYPE:Ljava/lang/Class;
16: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
19: swap
20: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
23: ldc #8 // class java/lang/Integer
25: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
28: swap
29: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
32: return
LineNumberTable:
line 4: 0
line 5: 13
line 6: 23
line 7: 32
可以看到, 通过kotlinc编译后, ::class确实获取的是int的基本类型, 藏在了Integer中, 以成员变量Type表示(Boolean/Short等其他包装类型类似)
// Integer
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
// Object
/*
* Return the Virtual Machine's Class object for the named
* primitive type.
*/
static native Class<?> getPrimitiveClass(String name);
通过类名来获取JVM层的基本类型Class, 与Integer的Class不同
断点看下该Class有何不同, 如下图所示, 感觉主要区别是name不同, 还有其他属性可能也不同, 这里不做具体区分。


基本数据类型有Class吗
因此需要注意,Java中基本数据类型也有对应的Class, 只是需要通过Native方法去获取
综上
基于基本数据类型的::class语法糖获取的对象为Primitive 类对象, 且.javaObjectType对基本数据类型转化为包装类型, 但.java没有,因此出现了上述差异。