ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Kotlin学习手记——注解(1),Android高级工程师面试实战

2021-09-10 15:29:49  阅读:150  来源: 互联网

标签:String bennyhuo Kotlin 手记 kotlin fun import Android com


interface NameStrategy {

fun mapTo(name: String): String

}

//下划线转驼峰

object UnderScoreToCamel : NameStrategy {

// html_url -> htmlUrl

override fun mapTo(name: String): String {

    //先转成字符数组,然后fold操作

    return name.toCharArray().fold(StringBuilder()) { acc, c ->

        when (acc.lastOrNull()) { //上一次的acc不是空

            '_' -> acc[acc.lastIndex] = c.toUpperCase() //上一次结果的最后一个字符是下划线就把下划线位置替换成当前字符的大写字母

            else -> acc.append(c) // 否则直接拼接

        }

        //返回acc

        acc

    }.toString()

}

}

//驼峰转下划线

object CamelToUnderScore : NameStrategy {

override fun mapTo(name: String): String {

    //先转成字符数组,然后fold操作

    return name.toCharArray().fold(StringBuilder()) { acc, c ->

        when {

            c.isUpperCase() -> acc.append('_').append(c.toLowerCase()) //如果是大写字母直接拼一个下划线再拼上小写

            else -> acc.append(c)

        }

        //返回acc

        acc

    }.toString()

}

}

//使用定义的策略注解,驼峰转下划线

@MappingStrategy(CamelToUnderScore::class)

data class UserVO(

val login: String,

//@FieldName("avatar_url") //这种是单个字段上面添加注解,只能一个一个添加

val avatarUrl: String,

var htmlUrl: String

)

data class UserDTO(

var id: Int,

var login: String,

var avatar_url: String,

var url: String,

var html_url: 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",

    "avatar_url" to "https://api.github.com/users/bennyhuo",

    "html_url" to "https://github.com/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 Map<String, Any?>.mapAs(): To {

return To::class.primaryConstructor!!.let {

    it.parameters.map { parameter ->

        parameter to (this[parameter.name]

                // let(this::get)等价于let{this[it]} userDTO["avatar_url"]

            ?: (parameter.annotations.filterIsInstance<FieldName>().firstOrNull()?.name?.let(this::get))

                // 拿到UserVO类的注解MappingStrategy的kclass即CamelToUnderScore,它是一个object calss, objectInstance获取实例,然后调用mapTo把avatarUrl转成avatar_url,最后调用userDTO["avatar_url"]

            ?: To::class.findAnnotation<MappingStrategy>()?.klass?.objectInstance?.mapTo(parameter.name!!)?.let(this::get)

            ?: if (parameter.type.isMarkedNullable) null

            else throw IllegalArgumentException("${parameter.name} is required but missing."))

    }.toMap().let(it::callBy)

}

}




这里如果注解上不写`@Retention(AnnotationRetention.RUNTIME)`默认就是运行时类型。  

下面两种写法是等价的:



parameter.annotations.filterIsInstance()

parameter.findAnnotation()




下面两种写法是等价的:



let(this::get)

let{

this[it]

}




mapAs()方法中做了几件事:



1.  尝试直接从当前Map中获取To对象的同名参数值,

2.  尝试从To对象的字段上面的注解来获取需要转换的参数名,再根据名字获取Map中的值

3.  尝试获取To对象的类注解得到处理类,调用处理类方法驼峰转下划线,再根据名字获取Map中的值

4.  以上大招都没有获取到,如果To对象的字段可接受空值,就赋值null, 否则就抛异常



驼峰和下划线转换那里稍微有点绕。。



实例:注解处理器版 Model 映射



![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210114141602798.jpg?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70#pic_center)  

![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210114141617715.jpg?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70#pic_center)  

![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210114141630891.jpg?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70#pic_center)  

这个例子会用到一些著名的代码生成库:



*   生成Java代码:[JavaPoet](

)

*   生成Kotlin代码:[KotlinPoet](

)



上面两个都是square公司出品的开源库,[JakeWharton](

)大神的杰作,这个例子中主要用到了KotlinPoet,还有一个这个学习课程资料主讲大神自己写的一个库。



dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

implementation "com.squareup:kotlinpoet:1.4.3"

implementation "com.bennyhuo.aptutils:aptutils:1.7.1"

implementation project(":apt:annotations")

}




注解声明:



@Retention(AnnotationRetention.BINARY)

@Target(AnnotationTarget.CLASS)

annotation class ModelMap




这里不需要在运行时保留注解,编译就会生成代码了,因此使用的是`AnnotationRetention.BINARY`



注解生成代码:



package com.bennyhuo.kotlin.annotations.apt.compiler

import com.bennyhuo.aptutils.AptContext

import com.bennyhuo.aptutils.logger.Logger

import com.bennyhuo.aptutils.types.ClassType

import com.bennyhuo.aptutils.types.asKotlinTypeName

import com.bennyhuo.aptutils.types.packageName

import com.bennyhuo.aptutils.types.simpleName

import com.bennyhuo.aptutils.utils.writeToFile

import com.bennyhuo.kotlin.annotations.apt.ModelMap

import com.squareup.kotlinpoet.*

import javax.annotation.processing.*

import javax.lang.model.SourceVersion

import javax.lang.model.element.ExecutableElement

import javax.lang.model.element.TypeElement

import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy

//必须指定注解的类型

@SupportedAnnotationTypes(“com.bennyhuo.kotlin.annotations.apt.ModelMap”)

@SupportedSourceVersion(SourceVersion.RELEASE_8)

class ModelMapProcessor: AbstractProcessor() {

override fun init(processingEnv: ProcessingEnvironment) {

    super.init(processingEnv)

    AptContext.init(processingEnv)

}



//fun Sample.toMap() = mapOf("a" to a, "b" to b)

//fun Map<String, V>.toSample() = Sample(this[“a”] as Int, this[“b”] as String)

override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {

    roundEnv.getElementsAnnotatedWith(ModelMap::class.java)

        .forEach {

            element ->

            element.enclosedElements.filterIsInstance<ExecutableElement>()

                .firstOrNull { it.simpleName() == "<init>" }

                ?.let {

                    val typeElement = element as TypeElement

                    FileSpec.builder(typeElement.packageName(), "${typeElement.simpleName()}\$\$ModelMap") //$$转义

                        .addFunction(

                            FunSpec.builder("toMap")

                                .receiver(typeElement.asType().asKotlinTypeName())

                                .addStatement("return mapOf(${it.parameters.joinToString {""""${it.simpleName()}" to ${it.simpleName()}""" }})")//mapOf("a" to a, "b" to b)

                                .build()

                        )

                        .addFunction(

                            FunSpec.builder("to${typeElement.simpleName()}")

                                .addTypeVariable(TypeVariableName("V"))

                                .receiver(MAP.parameterizedBy(STRING, TypeVariableName("V")))

                                .addStatement(

                                    "return ${typeElement.simpleName()}(${it.parameters.joinToString{ """this["${it.simpleName()}"] as %T """ } })", //Sample(this["a"] as Int, this["b"] as String) %T是模板字符串 用后面的参数替换

                                    *it.parameters.map { it.asType().asKotlinTypeName() }.toTypedArray()

                                )

                                .build()

                        )

                        .build().writeToFile()

                }

        }

    return true

}

}




![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210114142758262.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70)  

这是注解处理器的模块,然后新建一个模块来使用它:  

![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210114142847127.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70)  

gradle中必须通过`kapt`的方式来依赖注解处理器的库:



plugins {

id 'java'

id 'org.jetbrains.kotlin.jvm'

id 'org.jetbrains.kotlin.kapt'

}

group ‘com.bennyhuo.kotlin’

version ‘1.0-SNAPSHOT’

sourceCompatibility = 1.8

repositories {

jcenter()

}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"



kapt project(":apt:compiler")

implementation project(":apt:annotations")



testCompile group: 'junit', name: 'junit', version: '4.12'

}

compileKotlin {

kotlinOptions.jvmTarget = "1.8"

}

compileTestKotlin {

kotlinOptions.jvmTarget = "1.8"

}




注意注解处理器仅在编译器起作用,而注解库的依赖方式是在运行时,也就是会参与编译到字节码中的。



最后使用代码:



import com.bennyhuo.kotlin.annotations.apt.ModelMap

fun main() {

val sample = Sample(0, "1")

val map = sample.toMap();

println(map)

val sample2 = map.toSample2()

println(sample2)

}

@ModelMap

data class Sample(val a: Int, val b: String)

//fun Sample.toMap() = mapOf(“a” to a, “b” to b)

//fun Map<String, V>.toSample() = Sample(this[“a”] as Int, this[“b”] as String)

@ModelMap

data class Sample2(val a: Int, val b: String)




使用前先build一下,这样注解处理器会在下面的目录下生成kotlin的代码文件:  

![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210114143506541.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70)  

可以看到每个添加了`@ModelMap`的data类都会生成一个文件 ,打开看一下内容:



package com.bennyhuo.kotlin.annotations.sample

import kotlin.Int

import kotlin.String

import kotlin.collections.Map

fun Sample.toMap() = mapOf(“a” to a, “b” to b)

fun Map<String, V>.toSample() = Sample(this[“a”] as Int , this[“b”] as String )


package com.bennyhuo.kotlin.annotations.sample



import kotlin.Int

import kotlin.String

import kotlin.collections.Map



fun Sample2.toMap() = mapOf("a" to a, "b" to b)



fun <V> Map<String, V>.toSample2() = Sample2(this["a"] as Int , this["b"] as String ) 

```



**最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿**

**[CodeChina开源项目地址:https://codechina.csdn.net/m0_60958482/android_p7](

)**

![录播视频图.png](https://www.icode9.com/i/ll/?i=img_convert/91a9df01bb5efb9ed7f00b0023661ab6.png)



> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**

import kotlin.String

import kotlin.collections.Map



fun Sample2.toMap() = mapOf("a" to a, "b" to b)



fun <V> Map<String, V>.toSample2() = Sample2(this["a"] as Int , this["b"] as String ) 

```



**最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿**

**[CodeChina开源项目地址:https://codechina.csdn.net/m0_60958482/android_p7](

)**

[外链图片转存中...(img-WcjPCmWY-1631258885181)]



> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**

标签:String,bennyhuo,Kotlin,手记,kotlin,fun,import,Android,com
来源: https://blog.csdn.net/m0_61369817/article/details/120223101

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有