本文是根据慕课网kotlin相关课程本人笔记和个人理解所作
代码部分参考慕课网新kotlin入门课程
本章简介
这一章主要就是要注意java类和kotlin反射的区别
结构导图
反射
数据结构 | 概念及使用说明 |
---|---|
KType | 描述未擦除的类型或泛型参数等,例如Map<String,Int>; 可通过typeOf或者以下类型获取对应的父类、属性、函数参数等 |
KClass | 描述对象的实际类型,不包含泛型参数,例如Map; 可通过对象、类型名直接获得 |
KProperty | 描述属性,可通过属性引用、属性所在类的KClass获取 |
KFunction | 描述函数,可通过函数引用、函数所在类的KClass获取 |
java
public class JavaReflections {
public static void main(String... args) throws NoSuchFieldException {
Class<String> cls = String.class;
Class<? extends Object> clsOfObj = String.class;
Field field = cls.getDeclaredField("value");
}
}
反射实现
kotlin.Metadata这个注解,Kotlin 反射过程中,注解的内容解析之后会实例化一个叫做 KotlinClassHeader
的类。
Metadata | KotlinClassHeader | 说明 |
---|---|---|
k | kind | 注解标注目标类型,例如类、文件等等 |
mv | metadataVersion | 该元数据的版本 |
bv | bytecodeVersion | 字节码版本 |
d1 | data | 自定义元数据 |
d2 | strings | 自定义元数据补充字段 |
xs | extraString | 附加字段 |
xi | extraInt | 1.1 加入的附加标记,标记类文件的来源类型 |
- d1:存储了自定义格式的元数据,官方声称针对不同的类型格式不定,甚至可以为空,研究发现目前采用 Protobuf 进行序列化存储。这些数据会被 Kotlin 反射读取,是反射的一个非常重要的数据来源。其中包含不限于类型、函数、属性等的可见性、类型是否可空、函数是否为 suspend等等信息。
- d2:存储明文字符串字面量,主要存储 Jvm 签名等信息。之所以这样设计,主要是为了将这些字符串在运行时直接加载到虚拟机内存的常量池中予以复用,减少内存开销。
kotlin
@ExperimentalStdlibApi
fun main() {
var cls: KClass<String> = String::class
val property = cls.declaredMemberProperties.firstOrNull()
val mapCls = Map::class
println(mapCls)
val mapType = typeOf<Map<String, Int>>()
mapType.arguments.forEach {
println(it)
}
}
object B {
fun hello(){
}
}
class A {
fun String.hello(){
}
}
open class Super<T>
class Sub: Super<String>()
获取泛型实参
kotlin
interface Api {
fun getUsers(): List<UserDTO>
}
abstract class SuperType<T> {
val typeParameter by lazy {
this::class.supertypes.first().arguments.first().type!!
}
val typeParameterJava by lazy {
this.javaClass.genericSuperclass.safeAs<ParameterizedType>()!!.actualTypeArguments.first()
}
}
class SubType : SuperType<String>()
fun main() {
Api::class.declaredFunctions.first { it.name == "getUsers" }
.returnType.arguments.forEach {
println(it)
}
Api::getUsers.returnType.arguments.forEach {
println(it)
}
Api::class.java.getDeclaredMethod("getUsers")
.genericReturnType.safeAs<ParameterizedType>()?.actualTypeArguments?.forEach {
println(it)
}
val subType = SubType()
subType.typeParameter.let(::println)
subType.typeParameterJava.let(::println)
}
fun <T> Any.safeAs(): T? {
return this as? T
}
DeepCopy
kotlin
fun <T : Any> T.deepCopy(): T {
if(!this::class.isData){
return this
}
return this::class.primaryConstructor!!.let {
primaryConstructor ->
primaryConstructor.parameters.map { parameter ->
val value = (this::class as KClass<T>).memberProperties.first { it.name == parameter.name }
.get(this)
if((parameter.type.classifier as? KClass<*>)?.isData == true){
parameter to value?.deepCopy()
} else {
parameter to value
}
}.toMap()
.let(primaryConstructor::callBy)
}
}
//region demo
data class Person(val id: Int, val name: String, val group: Group)
data class Group(val id: Int, val name: String, val location: String)
fun main() {
val person = Person(
0,
"Bennyhuo",
Group(
0,
"Kotliner.cn",
"China"
)
)
val copiedPerson = person.copy()
val deepCopiedPerson = person.deepCopy()
println(person === copiedPerson) //false
println(person === deepCopiedPerson) //false
println(person.group === copiedPerson.group) //true for shallow copy.
println(person.group === deepCopiedPerson.group) //false
println(deepCopiedPerson)
}
//endregion
Model映射
kotlin
//mapping fields. Using Annotations next chapter.
data class UserVO(val login: String, val avatarUrl: String)
data class UserDTO(
var id: Int,
var login: String,
var avatarUrl: String,
var url: String,
var htmlUrl: String
)
fun main() {
val userDTO = UserDTO(
0,
"Bennyhuo",
"https://avatars2.githubusercontent.com/u/30511713?v=4",
"https://api.github.com/users/bennyhuo",
"https://github.com/bennyhuo"
)
val userVO: UserVO = userDTO.mapAs()
println(userVO)
val userMap = mapOf(
"id" to 0,
"login" to "Bennyhuo",
"avatarUrl" to "https://api.github.com/users/bennyhuo",
"url" to "https://api.github.com/users/bennyhuo"
)
val userVOFromMap: UserVO = userMap.mapAs()
println(userVOFromMap)
}
inline fun <reified From : Any, reified To : Any> From.mapAs(): To {
return From::class.memberProperties.map { it.name to it.get(this) }
.toMap().mapAs()
}
inline fun <reified To : Any> Map<String, Any?>.mapAs(): To {
return To::class.primaryConstructor!!.let {
it.parameters.map {
parameter ->
parameter to (this[parameter.name] ?: if(parameter.type.isMarkedNullable) null
else throw IllegalArgumentException("${parameter.name} is required but missing."))
}.toMap()
.let(it::callBy)
}
}
释放对象不可空引用的类型
kotlin
//region impl
fun <T : Any> releasableNotNull() = ReleasableNotNull<T>()
class ReleasableNotNull<T: Any>: ReadWriteProperty<Any, T> {
private var value: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Not initialized or released already.")
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
this.value = value
}
fun isInitialized() = value != null
fun release() {
value = null
}
}
inline val KProperty0<*>.isInitialized: Boolean
get() {
isAccessible = true
return (this.getDelegate() as? ReleasableNotNull<*>)?.isInitialized()
?: throw IllegalAccessException("Delegate is not an instance of ReleasableNotNull or is null.")
}
fun KProperty0<*>.release() {
isAccessible = true
(this.getDelegate() as? ReleasableNotNull<*>)?.release()
?: throw IllegalAccessException("Delegate is not an instance of ReleasableNotNull or is null.")
}
//endregion
//region demo
class Bitmap(val width: Int, val height: Int)
class Activity {
private var bitmap by releasableNotNull<Bitmap>()
fun onCreate(){
println(this::bitmap.isInitialized)
bitmap = Bitmap(1920, 1080)
println(::bitmap.isInitialized)
}
fun onDestroy(){
println(::bitmap.isInitialized)
::bitmap.release()
println(::bitmap.isInitialized)
}
}
fun main() {
val activity = Activity()
activity.onCreate()
activity.onDestroy()
}
//endregion
插件化加载类
kotlin
class PluginClassLoader(private val classPath: String) : URLClassLoader(arrayOf(File(classPath).toURI().toURL())) {
init {
println("Classloader init @${hashCode()}")
}
protected fun finalize() {
println("Classloader will be gc @${hashCode()}")
}
}
class PluginLoader(private val classPath: String) {
private val watcher = FileWatcher(
File(classPath),
onCreated = ::onFileChanged,
onModified = ::onFileChanged,
onDeleted = ::onFileChanged
)
private var classLoader: PluginClassLoader? = null
private var plugin: Plugin? = null
fun load() {
reload()
watcher.start()
}
private fun onFileChanged(file: File) {
println("$file changed, reloading...")
reload()
}
@Synchronized
private fun reload() {
plugin?.stop()
this.plugin = null
this.classLoader?.close()
this.classLoader = null
val classLoader = PluginClassLoader(classPath)
val properties = classLoader.getResourceAsStream(Plugin.CONFIG)?.use {
Properties().also { properties ->
properties.load(it)
}
} ?: run {
classLoader.close()
return println("Cannot find config file for $classPath")
}
plugin = properties.getProperty(Plugin.KEY)?.let {
val pluginImplClass = classLoader.loadClass(it) as? Class<Plugin>
?: run {
classLoader.close()
return println("Plugin Impl from $classPath: $it should be derived from Plugin.")
}
pluginImplClass.kotlin.primaryConstructor?.call()
?: run {
classLoader.close()
return println("Illegal! Plugin has no primaryConstructor!")
}
}
plugin?.start()
this.classLoader = classLoader
System.gc()
}
}
fun main() {
arrayOf(
"plugin-1/build/libs/plugin-1-1.0-SNAPSHOT.jar",
"plugin-2/build/libs/plugin-2-1.0-SNAPSHOT.jar"
).map {
PluginLoader(it).also { it.load() }
}
}
文件监听
typealias FileEventListener = (file: File) -> Unit
private val EMPTY: FileEventListener = {}
class FileWatcher(private val watchFile: File,
private val recursively: Boolean = true,
private val onCreated: FileEventListener = EMPTY,
private val onModified: FileEventListener = EMPTY,
private val onDeleted: FileEventListener = EMPTY) {
private val folderPath by lazy {
Paths.get(watchFile.canonicalPath).let {
if (Files.isRegularFile(it)) it.parent else it
} ?: throw IllegalArgumentException("Illegal path: $watchFile")
}
@Volatile
private var isWatching = false
private fun File.isWatched() = watchFile.isDirectory || this.name == watchFile.name
private fun Path.register(watchService: WatchService, recursively: Boolean, vararg events: Kind<Path>) {
when (recursively && watchFile.isDirectory) {
true -> // register all subfolders
Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
dir.register(watchService, *events)
return FileVisitResult.CONTINUE
}
})
false -> register(watchService, *events)
}
}
@Synchronized
fun start() {
if(!isWatching){
isWatching = true
}
thread {
// We obtain the file system of the Path
val fileSystem = folderPath.fileSystem
// We create the new WatchService using the try-with-resources block(in kotlin we use `use` block)
fileSystem.newWatchService().use { service ->
// We watch for modification events
folderPath.register(service, recursively,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY
)
// Start the infinite polling loop
while (isWatching) {
// Wait for the next event
val watchKey = service.take()
watchKey.pollEvents().forEach { watchEvent ->
// Get the type of the event
Paths.get(folderPath.toString(), (watchEvent.context() as Path).toString()).toFile()
.takeIf {
it.isWatched()
}
?.let(
when (watchEvent.kind()) {
StandardWatchEventKinds.ENTRY_CREATE -> {
println("onCreated")
onCreated
}
StandardWatchEventKinds.ENTRY_DELETE -> {
println("onDeleted")
onDeleted
}
else ->{
println("onModified")
onModified // modified.
}
}
)
}
if (!watchKey.reset()) {
// Exit if no longer valid
break
}
}
}
}
}
@Synchronized
fun stop(){
isWatching = false
}
}