diff --git a/docs-source/src/en/library/kavaref-extension.md b/docs-source/src/en/library/kavaref-extension.md index aa50acc..ef7be51 100644 --- a/docs-source/src/en/library/kavaref-extension.md +++ b/docs-source/src/en/library/kavaref-extension.md @@ -287,6 +287,38 @@ val myClass: Class<*> val arguments = myClass.genericSuperclassTypeArguments() ``` +### Type Reference Extensions + +In Java, method generics are erased after compilation, and the type obtained at runtime is `java.lang.Object`. + +KavaRef provides the `TypeRef` class to wrap your target generics to ensure that you can get the correct generic type at runtime. +Its core functionality is referenced from [Gson](https://github.com/google/gson)'s `TypeToken`. + +It is very simple to use, you can use it like this. + +> The following example + +```kotlin +val listStringType = typeRef>() +// Get the stored type, which will be List. +val type = listStringType.type +// Get its raw type, which will be List. +val rawType = listStringType.rawType +``` + +In scenarios where you need to pass in `Type` such as when using Gson, you can implement an extension method with `reified` generics for this purpose. + +> The following example + +```kotlin +val gson = Gson() + +inline fun T.toJson(): String = gson.toJson(this, typeRef().type) + +// Usage +val json = listOf("KavaRef", "is", "awesome").toJson() +``` + ### Java Wrapper Classes Extensions In Kotlin, you can directly use `Boolean::class`, `Byte::class`, etc. to obtain Java's original types `boolean` and `byte` instead of their wrapper classes. diff --git a/docs-source/src/zh-cn/library/kavaref-extension.md b/docs-source/src/zh-cn/library/kavaref-extension.md index eae77ad..9522464 100644 --- a/docs-source/src/zh-cn/library/kavaref-extension.md +++ b/docs-source/src/zh-cn/library/kavaref-extension.md @@ -268,6 +268,37 @@ val myClass: Class<*> val arguments = myClass.genericSuperclassTypeArguments() ``` +### 类型引用扩展 + +在 Java 中,方法的泛型会在编译后被类型擦除,在运行获取到的类型是 `java.lang.Object`。 + +KavaRef 提供了 `TypeRef` 类来包装你的目标泛型来确保你可以在运行时获取到正确的泛型类型,它的核心功能参考于 [Gson](https://github.com/google/gson) 的 `TypeToken`。 + +它的使用方法非常简单,你可以像下面这样使用它。 + +> 示例如下 + +```kotlin +val listStringType = typeRef>() +// 获取存储的类型,将会是 List +val type = listStringType.type +// 获取其原始类型,将会是 List +val rawType = listStringType.rawType +``` + +在使用 Gson 等需要传入 `Type` 的场景中,你可以为此实现一个带有 `reified` 泛型的扩展方法。 + +> 示例如下 + +```kotlin +val gson = Gson() + +inline fun T.toJson(): String = gson.toJson(this, typeRef().type) + +// 使用方法 +val json = listOf("KavaRef", "is", "awesome").toJson() +``` + ### Java 包装类扩展 在 Kotlin 中直接使用 `Boolean::class`、`Byte::class` 等方式获取到的是 Java 的原始类型 `boolean`、`byte` 而不是它们的包装类。 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0785e6..271a112 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ kotlin = "2.0.10" dokka = "1.9.20" maven-publish = "0.34.0" slf4j = "2.0.17" +androidx-annotation = "1.9.1" [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } @@ -11,4 +12,5 @@ maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publ [libraries] slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } -slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } \ No newline at end of file +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } +androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "androidx-annotation" } \ No newline at end of file diff --git a/kavaref-extension/build.gradle.kts b/kavaref-extension/build.gradle.kts index 94e6d28..e4d7834 100644 --- a/kavaref-extension/build.gradle.kts +++ b/kavaref-extension/build.gradle.kts @@ -23,4 +23,8 @@ kotlin { "-Xno-receiver-assertions" ) } +} + +dependencies { + implementation(libs.androidx.annotation) } \ No newline at end of file diff --git a/kavaref-extension/src/main/kotlin/com/highcapable/kavaref/extension/TypeRef.kt b/kavaref-extension/src/main/kotlin/com/highcapable/kavaref/extension/TypeRef.kt new file mode 100644 index 0000000..ecc9da3 --- /dev/null +++ b/kavaref-extension/src/main/kotlin/com/highcapable/kavaref/extension/TypeRef.kt @@ -0,0 +1,81 @@ +/* + * KavaRef - A modernizing Java Reflection with Kotlin. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/KavaRef + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/6. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") +@file:JvmName("TypeRefUtils") + +package com.highcapable.kavaref.extension + +import androidx.annotation.Keep +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +/** + * Type reference class for getting generic parameter [T] type. + * + * The purpose of this class is to retain erased generics at runtime. + * @see typeRef + */ +@Keep +abstract class TypeRef { + + /** + * Get the generic parameter [T] type. + * @return [Type] + */ + val type by lazy { + when (val superclass = javaClass.genericSuperclass) { + is ParameterizedType -> + if (superclass.rawType == classOf>()) + superclass.actualTypeArguments.firstOrNull() ?: error("Type argument cannot be null.") + else error("Must only create direct subclasses of TypeRef.") + classOf>() -> error("TypeRef must be created with a type argument: object : TypeRef<...>() {}.") + else -> error("Must only create direct subclasses of TypeRef.") + } + } + + /** + * Get the raw class type of the generic parameter [T]. + * @return [Class]<[T]> + */ + val rawType by lazy { type.toClass() } + + override fun toString() = type.toString() + override fun equals(other: Any?) = other is TypeRef<*> && type == other.type + override fun hashCode() = type.hashCode() +} + +/** + * Create a [TypeRef] instance with the reified type parameter [T]. + * + * Usage: + * + * ```kotlin + * val typeRef = typeRef>() + * // This will be of type `List`. + * val type = typeRef.type + * // This will be of type `List`. + * val rawType = typeRef.rawType + * ``` + * @see TypeRef + * @return [TypeRef]<[T]> + */ +inline fun typeRef() = object : TypeRef() {} \ No newline at end of file