2024年最全Kotlin学习手记——注解、注解处理器、编译器插件,2024年最新嵌入式工程师面试都问什么

最后

其实要轻松掌握很简单,要点就两个:

  1. 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
  2. 多练。 (视频优势是互动感强,容易集中注意力)

你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

阿里P7Android高级教程

下面资料部分截图,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。

附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、近期面试跳槽、自身职业规划迷茫的朋友们。

Android核心高级技术PDF资料,BAT大厂面试真题解析;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

var location: String,

var bio: String)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.CLASS)

annotation class Api(val url: String)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.CLASS)

annotation class Path(val url: String = “”)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.FUNCTION)

annotation class Get(val url: String = “”)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class PathVariable(val name: String = “”)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class Query(val name: String = “”)

@Api(“https://api.github.com”)

interface GitHubApi {

@Api(“users”)

interface Users {

@Get(“{name}”)

fun get(name: String): User

@Get(“{name}/followers”)

fun followers(name: String): List

}

@Api(“repos”)

interface Repos {

@Get(“{owner}/{repo}/forks”)

fun forks(owner: String, repo: String)

}

}

object RetroApi {

const val PATH_PATTERN = “”“({(\w+)})”“”

val okHttp = OkHttpClient()

val gson = Gson()

val enclosing = {

cls: Class<*> ->

var currentCls: Class<*>? = cls

sequence {

while(currentCls != null){

// enclosingClass获取下一个class

// yield将对象添加到正在构建的sequence序列当中

currentCls = currentCls?.also { yield(it) }?.enclosingClass

}

}

}

//内联特化

inline fun create(): T {

val functionMap = T::class.functions.map{ it.name to it }.toMap() //【函数名,函数本身】的Pair转成map

val interfaces = enclosing(T::class.java).takeWhile { it.isInterface }.toList() //拿到所有接口列表

println("interfaces= i n t e r f a c e s " ) / / 输出 [ G i t H u b A p i interfaces")// 输出 [GitHubApi interfaces")//输出[GitHubApiUsers, GitHubApi]

//foldRight从interfaces序列的右边开始拼

val apiPath = interfaces.foldRight(StringBuilder()) {

clazz, acc ->

// 拿到每个接口类的Api注解的url参数值,如果url参数为空,则使用类名作为url值

acc.append(clazz.getAnnotation(Api::class.java)?.url?.takeIf { it.isNotEmpty() } ?: clazz.name)

.append(“/”)

}.toString()

println(“apiPath= $apiPath”) // https://api.github.com/users/

//动态代理

return Proxy.newProxyInstance(RetroApi.javaClass.classLoader, arrayOf(T::class.java)) {

proxy, method, args ->

//所有函数中的抽象函数 即接口的方法

functionMap[method.name]?.takeIf { it.isAbstract }?.let {

function ->

//方法的参数

val parameterMap = function.valueParameters.map {

//参数名和参数的值放在一起

it.name to args[it.index - 1] //valueParameters包含receiver 因此需要index-1来对应args

}.toMap()

println(“parameterMap= $parameterMap”) //{name=bennyhuo}

//{name} 拿到Get注解的参数 如果注解参数不为空就使用注解参数,如果为空使用方法名称

val endPoint = function.findAnnotation()!!.url.takeIf { it.isNotEmpty() } ?: function.name

println(“endPoint= $endPoint”) //{name}/followers

//正则找到endPoint中的所有符合"{owner}/{repo}/forks"其中{xxx}的结果

val compiledEndPoint = Regex(PATH_PATTERN).findAll(endPoint).map {

matchResult ->

println(“matchResult.groups= ${matchResult.groups}”) // [MatchGroup(value={name}, range=0…5), MatchGroup(value={name}, range=0…5), MatchGroup(value=name, range=1…4)]

println(“matchResult.groups1.range= ${matchResult.groups[1]?.range}”) // 0…5

println(“matchResult.groups2.value= ${matchResult.groups[2]?.value}”) // name

matchResult.groups[1]!!.range to parameterMap[matchResult.groups[2]!!.value]

}.fold(endPoint) {

acc, pair ->

//acc的初始值就是endPoint即{name}/followers

println(“acc= ${acc}”) // {name}/followers

println(“pair= ${pair}”) // (0…5, bennyhuo) pair是一个 range to name

acc.replaceRange(pair.first, pair.second.toString()) // 把{name}/followers中的0到5的位置的字符串{name}替换成bennyhuo

}

println(“compiledEndPoint= ${compiledEndPoint}”) //bennyhuo/followers

//拼接api和参数

val url = apiPath + compiledEndPoint

println(“url ==== $url”)

println(“*****************”)

okHttp.newCall(Request.Builder().url(url).get().build()).execute().body()?.charStream()?.use {

gson.fromJson(JsonReader(it), method.genericReturnType)//返回json的解析结果

}

}

} as T

}

}

fun main() {

//interface com.bennyhuo.kotlin.annotations.eg.GitHubApi

//println(“enclosingClass=${GitHubApi.Users::class.java.enclosingClass}”)

val usersApi = RetroApi.create<GitHubApi.Users>()

val user = usersApi.get(“bennyhuo”)

val followers = usersApi.followers(“bennyhuo”).map { it.login }

println(“user ====== $user”)

println(“followers ======== $followers”)

}

这个例子还是有点复杂,不太好理解,有些方法没接触过不知道啥意思,这里加了很多打印方法,把结果打印输出一下,这样能知道具体是代表的啥,就好理解一点了。

实例:注解加持反射版 Model 映射

这个例子是在前面反射一节实现的model映射例子的基础上,通过添加注解方式处理那些字段名称不是相同风格的情况,比如两个对象中的avatar_urlavatarUrl的相互映射。

//不写默认是RUNTIME

//@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class FieldName(val name: String)

@Target(AnnotationTarget.CLASS)

annotation class MappingStrategy(val klass: KClass)

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© // 否则直接拼接

}

//返回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©

}

//返回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().firstOrNull()?.name?.let(this::get))

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

?: To::class.findAnnotation()?.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 映射

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

上面两个都是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, roundEnv: RoundEnvironment): Boolean {

roundEnv.getElementsAnnotatedWith(ModelMap::class.java)

.forEach {

element ->

element.enclosedElements.filterIsInstance()

.firstOrNull { it.simpleName() == “” }

?.let {

val typeElement = element as TypeElement

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

.addFunction(

FunSpec.builder(“toMap”)

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

.addStatement(“return mapOf(KaTeX parse error: Expected '}', got 'EOF' at end of input: …nToString {""""{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 t y p e E l e m e n t . s i m p l e N a m e ( ) ( {typeElement.simpleName()}( 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()

最后笔者收集整理了一份Flutter高级入门进阶资料PDF

以下是资料目录和内容部分截图



里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

typeElement.simpleName()}(KaTeX parse error: Expected '}', got 'EOF' at end of input: …ring{ """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()

最后笔者收集整理了一份Flutter高级入门进阶资料PDF

以下是资料目录和内容部分截图

[外链图片转存中…(img-tnYnUciu-1715888516788)]
[外链图片转存中…(img-iImFpwu7-1715888516789)]
里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。

[外链图片转存中…(img-iBINGE2S-1715888516789)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值