赞
踩
val USER_TYPE = 0x01
}
}
上面的companion object会生成一个内部类Companion,并添加返回USER_TYPE的静态getter,如下
public final class UserManager {
private static final int USER_TYPE = 1;
public static final UserManager.Companion Companion = new UserManager.Companion((DefaultConstructorMarker)null);
public static final class Companion {
public final int getUSER_TYPE() {
return UserManager.USER_TYPE;
}
…
}
}
const关键字只能用在静态类中, 只能与val连用,即const val,而且只能修饰基本类型。意义为编译期常量,在用到的地方替换为该常量的值。如下:
object SingleTon {
const val str = “const”
}
fun test(): String? {
return SingleTon.str
}
其中test反编译Java如下:
public final String test() {
return “const”;
}
可以看到kotlin对const常量做了内联。
在Java中,可以使用来比较基本数据类型和引用类型,基本数据类型比较的是值,引用类型上比较的是引用。在kotlin中就等于调用Java中的equals。如果需要比较引用则需要用===。
装饰器模式的代码通常就较为模板,kotlin中可以利用by关键字来实现类的委托。比如:
class MyList : List by ArrayList() {
//这里面默认利用ArrayList实现了List的所有接口
}
转换成Java:
public final class MyList implements List, KMappedMarker {
// $FF: synthetic field
private final ArrayList KaTeX parse error: Expected '}', got 'EOF' at end of input: … { return this.delegate_0.get(index);
}
…
}
当然,我们也可以通过重写来实现自己的逻辑。
by也可以用来实现延迟加载:
private val arr by lazy { MyList() }
它的实现是double-check的懒加载方式,如下:
private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress(“UNCHECKED_CAST”)
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress(“UNCHECKED_CAST”) (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else “Lazy value not initialized yet.”
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
lambda表达式,本质上就是可以传递给其他函数的一小段代码。
kotlin中简单lambda:
button.setOnclickListener{
}
kotlin中lambda始终被花括号{}包围。可以把lambda表达式存储在变量中:
val sum = { x:Int,y:Int -> x + y }
println(sum(1,2))
3
kotlin中,lambda作为最后一个参数可以把它放到()后面如下1;如果只有lambda作为参数,可以省略(),如下2。
list.maxBy({it.length})
list.maxBy(){it.length}//1
list.maxBy{it.length}//2
比如list过滤:
val list = arrayListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.filter { it % 2 == 0 }
.forEach { print(it) }
//打印结果
246810
类似的还有:
all:全部匹配返回true
any:存在一个匹配返回true
count:返回匹配的数量
firstOrNull:第一个匹配,没有返回null
first:第一个匹配,没有抛出异常
find:找到第一个匹配 等于 firstOrNull
还有map,flatmap,groupby…
基本涵盖了RxJava的常用操作符。
apply改变this指向调用者。方便各种操作,返回调用者
val str = “123456”
val rec = str.apply {
println(“lastChar:KaTeX parse error: Expected 'EOF', got '}' at position 20: …(lastIndex)}") }̲ println("rec:rec”)
//打印结果
lastChar:6
rec:123456
with改变this指向参数,返回lambda最后一行结果
let创建局部变量,返回最后一行结果。
val str :String ? = “123456”
val res = str?.let {
println(“it:KaTeX parse error: Expected 'EOF', got '}' at position 15: it") "return" }̲ println("res:res”)
//打印结果
it:123456
res:return
also:创建it,返回调用者
run:改变this指向调用者,返回最后一行
类似的语法糖takeIf,repeat等等,都在Standard.kt中有定义。
kotlin中类型定义如果没有添加为可空,当它接受到一个null时,kotlin会在运行时抛出ERROR:Type mismatch的错误。
当一个类规定为可空,可以使用安全调用?.,后面可以跟上Elvis运算符?:。标识在前面?.调用者为null时执行。
val str :String ? = “123456”
str?.get(str.lastIndex) ?: toast(“str is null”) //toast会在str为null时执行
安全转换:as?
is:检查类型,可以自动转型
val obj: Any = “str”
if (obj is String) {
println(“obj:$obj”)//obg自动转型为string
}
//打印结果
obj:str
as:类型转换,as?安全类型转换
val obj: Any = “str”
(obj as? String)?.print()//在obj为String时才会执行后面的语句,print为本地定义的拓展函数
//打印结果
str
!!非空断言
让kotlin在对象为空的时候抛出异常。
val obj: Any? = “str”
obj!!.toString()
obj.hashCode()//不需要再加!!,kotlin编译器自动检查
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
val p1 = Point(1,2)
val p2 = Point(5,6)
val p = p1 + p2
println(“p:$p”)//自动调用toString
//打印结果
p:Point(x=6, y=8)
可用于重载的运算符:
表达式 | 函数名 |
---|---|
a * b | times |
a / b | div |
a % b | mod |
a + b | plus |
a - b | minus |
PS:位运算也有自己的符号
运算符 | 操作 |
---|---|
shl | 带符号左移 |
shr | 带符号右移 |
ushr | 无符号右移 |
and | 按位与 |
or | 按位或 |
xor | 按位异或 |
inv | 按位取反 |
无其他参数:
fun exec(func:()->Unit){
func.invoke()
}
exec {
println(“hello world”)
}
带其他参数:
fun exec(msg: String,func:(msg:String)->Unit){
func.invoke(msg)
}
exec(“hello world”) {msg->
println(msg)
}
以上的lambda是作为不能为空的形参。如果为空,需要将其定义用()?包裹。如下:
fun exec(msg: String,func:((msg:String)->Unit)?){
func?.invoke(msg)
}
lambda作为参数传递虽然好,但是其实现传递的还是对象(匿名类),在每一次调用都会创建一个对象,如何避免这部分开销提升性能?
答案是内联函数。
当一个函数声明为inline时,它的函数体是内联的–换句话说,函数体会被直接替换到函数被调用的地方,而不是被正常调用。
inline fun inlineTest(inlineFunc:()->Unit){
println(“before invoke”)
inlineFunc()
println(“after invoke”)
}
inlineTest {
println(“hello world”)
}
//打印结果
before invoke
hello world
after invoke
to Java:
String var2 = “before invoke”;
System.out.println(var2);
String var5 = “hello world”;
System.out.println(var5);
var2 = “after invoke”;
System.out.println(var2);
当我们在内联的lambda中添加return,代码就不正确运行了,它在中途就return掉了。
inlineTest {
println(“hello world”)
return
}
println(“code works as well”)
//打印结果
before invoke
hello world
针对这种情况,我们需要添加return的范围
inlineTest {
println(“hello world”)
return@inlineTest
}
println(“code works as well”)
//打印结果
before invoke
hello world
after invoke
code works as well
当然也可以用其他方法,比如noinline
noinline
修饰的是 inline
方法中的 lambda
参数。noinline
用于我们不想让 inline
特性作用到 inline
方法的某些 lambda
参数上的场景。
// Kotlin
fun main(args: Array) {
val methodName = “main”
multiplyByTwo(5) {
result: Int -> println(“call method $methodName, Result is: $result”)
}
}
inline fun multiplyByTwo(
num: Int,
noinline lambda: (result: Int) -> Unit): Int {
val result = num * 2
lambda.invoke(result)
return result
}
反编译的结果是:
public final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, “args”);
final String methodName = “main”;
byte num
i
v
=
5
;
F
u
n
c
t
i
o
n
1
l
a
m
b
d
a
iv = 5; Function1 lambda
iv=5;Function1lambdaiv = (Function1)(new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke(((Number)var1).intValue());
return Unit.INSTANCE;
}
public final void invoke(int result) {
String var2 = "call method " + methodName + ", Result is: " + result;
boolean var3 = false;
System.out.println(var2);
}
});
int
i
i
if
m
u
l
t
i
p
l
y
B
y
T
w
o
=
f
a
l
s
e
;
i
n
t
r
e
s
u
l
t
multiplyByTwo = false; int result
multiplyByTwo=false;intresultiv = num
i
v
∗
2
;
l
a
m
b
d
a
iv * 2; lambda
iv∗2;lambdaiv.invoke(result$iv);
}
public final int multiplyByTwo(int num, @NotNull Function1 lambda) {
int
i
i
if$multiplyByTwo = 0;
Intrinsics.checkParameterIsNotNull(lambda, “lambda”);
int result = num * 2;
lambda.invoke(result);
return result;
}
可以看到, 因为使用了 noinline
修饰了 lambda
,所以,编译器使用了匿名内部类的方式来处理这个 lambda,生成了一个 Function1
对象。
是不是有了 inline
和 noinline
,对于我们开发人员来讲就够了呢?就满足了呢?显然不是的。考虑一种情况,我们既想让 lambda
也被 inline
,但是又不想让 lambda
对调用方的控制流程产生影响。这个产生影响,可以是有意识的主动控制,但是大多数情况下是开发人员的不小心导致的。我们知道 java 语言是一个编译型语言,如果能在编译期间对这种 inline
lambda
对调用方产生控制流程影响的地方进行提示甚至报错,就万无一失了。
crossinline
就是为了处理这种情况而产生的。crossinline
保留了 inline
特性,但是如果想在传入的 lambda
里面 return
的话,就会报错。return
只能 return
当前的这个 lambda
。
// Kotlin
fun main(args: Array) {
val methodName = “main”
multiplyByTwo(5) {
result: Int -> println(“call method $methodName, Result is: $result”)
return@multiplyByTwo
}
}
如面代码所示,必须 return@multiplyByTwo
,而不能直接写 return
。
1)、首先我们解释下什么是泛型,泛型就是参数化类型,它允许我们在不指定具体类型的情况下进行编程。我们在定义一个类,方法,或者接口的时候,给他们加上一个类型参数,就是为这个类,方法,或者接口添加了一个泛型
//1、定义一个泛型类,在类名后面使用 这种语法结构就是为这个类定义一个泛型
class MyClass{
fun method(params: T) {
}
}
//泛型调用
val myClass = MyClass()
myClass.method(12)
//2、定义一个泛型方法,在方法名的前面加上 这种语法结构就是为这个方法定义一个泛型
class MyClass{
fun method(params: T){
}
}
//泛型调用
val myClass = MyClass()
myClass.method(12)
//根据 Kotlin 类型推导机制,我们可以把泛型给省略
myClass.method(12)
//3、定义一个泛型接口,在接口名后面加上 这种语法结构就是为这个接口定义一个泛型
interface MyInterface{
fun interfaceMethod(params: T)
}
2)、为泛型指定上界,我们可以使用 <T : Class>
这种语法结构,如果不指定泛型的上界,默认为 Any? 类型
class MyClass{
//我们指定了泛型的上界为 Number, 那么我们就只能传入数字类型的参数了
fun method(params: T) {
}
}
注意JVM上的泛型一般是通过泛型擦除实现的,所以kotlin上的泛型也是如此。但是kotlin可以通过内联函数来实化泛型(reified)
inline fun Iterable<*>.filterIsInstance(): List { //reified声明后不再进行擦除
val des = mutableListOf()
forEach {
if (it is T) des.add(it)
}
return des
}
fun main(args: Array) {
val items = arrayListOf(“one”, 2, “three”)
println(items.filterIsInstance())
}
//打印结果
[one, three]
kotlin为什么可以避免泛型?因为kotlin编译器把实现内联函数的字节码插入每一次调用的地方的时候,带上了实际作为参数的类型。
在Java中,在 Source 类型的变量中存储 Source 实例的引用是极为安全的——没有消费者-方法可以调用。但是 Java 并不知道这一点,并且仍然禁止这样操作:
void demo(Source strs) {
Source objects = strs; // !!!在 Java 中不允许
// ……
}
为了修正这一点,我们必须声明对象的类型为 Source<? extends Object>,这是毫无意义的,因为我们可以像以前一样在该对象上调用所有相同的方法,所以更复杂的类型并没有带来价值。但编译器并不知道。
所以在Kotlin中,有一种方法向编译器解释这种情况。这称为声明处型变:我们可以标注Source 的参数类型T 来确保它仅从Source 成员中返回(只读取,相当于Java中? extends T)。为此,kotlin提供out 修饰符。
//kotlin
interface Source {
fun nextT(): T
}
fun demo(strs: Source) {
val objects: Source = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
简单来说:消费者用 in,生产者用 out
一般原则是:当一个类C 的类型参数T 被声明为out 时,它就只能出现在C 的成员的输出位置,但回报是C 可以安全的作为C 的超类。 另外除了 out,Kotlin 又补充了一个型变注释:in。它使得一个类型参数逆变:只可以被写入而不可以被读取(相当于Java中 ? super T)。逆变类型的一个很好的例子是 Comparable:
interface Comparable {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,我们可以将 x 赋给类型为 Comparable 的变量
val y: Comparable = x // OK!
}
DSL英文全称:domain specific language,中文翻译即领域特定语言,例如:HTML,XML等 DSL 语言
特点
总的来说,DSL 的核心思想就是:“求专不求全,解决特定领域的问题”。
首先介绍一下Gradle:Gradle 是一个开源的自动化构建工具,是一种基于 Groovy 或 Kotin 的 DSL。我们的 Android 应用就是使用 Gradle 构建的,因此后续写脚本,写插件,我们可以使用 Kotlin 去编写,而且 AndroidStudio 对 Kotlin 的支持很友好,各种提示,写起来很爽。
比如:
例1-请求回调:
open class RequestCallback : Callback {
private val builder: Builder
…
private fun onSuccess(data: T) {
builder.onSuccess?.invoke(data)
}
private fun onError(code: Int, msg: String?) {
builder.onError?.invoke(code, msg) ?: toast(msg)
}
private fun onFinished() {
decrement()
builder.onFinished?.invoke()
}
class Builder {
internal var onSuccess: ((data: T) -> Unit)? = null
internal var onError: ((code: Int, msg: String?) -> Unit)? = null
internal var onFinished: (() -> Unit)? = null
fun onSuccess(func: ((data: T) -> Unit)?) {
onSuccess = func
}
fun onError(func: ((code: Int, msg: String?) -> Unit)?) {
onError = func
}
fun onFinished(func: (() -> Unit)?) {
onFinished = func
}
}
}
fun getCallback(refresh:Boolean = false,dsl: RequestCallback.Builder.() -> Unit): RequestCallback {
…
return RequestCallback(b)
}
//使用
api.fetchQrCode(orderNo, faceSuccess).enqueue(getCallback {
onSuccess {
…
}
onError { _, msg ->
…
}
})
例2-拓展系统Animator监听:
class AnimationCallbackBuilder {
internal var onRepeat: ((animation: Animator?) -> Unit)? = null
internal var onCancel: ((animation: Animator?) -> Unit)? = null
internal var onStart: ((animation: Animator?) -> Unit)? = null
internal var onEnd: ((animation: Animator?) -> Unit)? = null
fun onRepeat(function: ((animation: Animator?) -> Unit)?) {
this.onRepeat = function
}
fun onCancel(function: ((animation: Animator?) -> Unit)?) {
this.onCancel = function
}
fun onStart(function: ((animation: Animator?) -> Unit)?) {
this.onStart = function
}
fun onEnd(function: ((animation: Animator?) -> Unit)?) {
this.onEnd = function
}
}
fun AnimatorSet.registerCallback(listener: AnimationCallbackBuilder.() -> Unit) {
val mListener = AnimationCallbackBuilder().also(listener)
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
mListener.onRepeat?.invoke(animation)
}
override fun onAnimationEnd(animation: Animator?) {
mListener.onEnd?.invoke(animation)
}
override fun onAnimationCancel(animation: Animator?) {
mListener.onCancel?.invoke(animation)
}
override fun onAnimationStart(animation: Animator?) {
mListener.onStart?.invoke(animation)
}
})
}
//使用
set.registerCallback {
onEnd {
}
}
在kotlin中,规定一个方法返回一个可空类型,那么在每次调用的时候都需要打个?判断是否为空是挺麻烦的。比如:
//当然也可以更加定义自己的apply
inline fun T.exist(block: T.() -> Unit) = block()
inline fun curActivity(block: Activity.() -> Unit) = ActivityCollector.currentActivity()?.let(block)
//使用
item?.exist{
this.toString()//这里改变了this的指向,同时this不会为空
}
curActivity{
startActivity(intent)//同上,this为ActivityCollector.currentActivity()获取到的不为空的activity,
//当然为空的时候,则不执行,需要警惕
}
带默认实现的接口,比如截图权限配置:
interface ICaptureView {
fun canCapture() = false
}
不能截图的类只需要实现接口,能够截图的实现重写即可。
Ktorm 是什么?
Ktorm 是直接基于纯 JDBC 编写的高效简洁的轻量级 Kotlin ORM 框架,它提供了强类型而且灵活的 SQL DSL 和方便的序列 API,以减少我们操作数据库的重复劳动。当然,所有的 SQL 都是自动生成的。Ktorm 基于 Apache 2.0 协议开放源代码,源码托管在 GitHub,如果对你有帮助的话,请留下你的 star:kotlin-orm/ktorm
特性
- 没有配置文件、没有 xml、没有注解、甚至没有任何第三方依赖、轻量级、简洁易用
- 强类型 SQL DSL,将低级 bug 暴露在编译期
- 灵活的查询,随心所欲地精确控制所生成的 SQL
- 实体序列 API,使用
filter
、map
、sortedBy
等序列函数进行查询,就像使用 Kotlin 中的原生集合一样方便- 易扩展的设计,可以灵活编写扩展,支持更多运算符、数据类型、 SQL 函数、数据库方言等
kotlin相比Java有一些不少优势:
①Kotlin 语法更加简洁,使用 Kotlin 开发的代码量可能会比 Java 开发的减少 50% 甚至更多
②Kotlin 的语法更加高级,相比于 Java 老旧的语法,Kotlin 增加了很多现代高级语言的语法特性(语法糖),大大提升了我们的开发效率
③Kotlin 和 Java 是 100% 兼容的,Kotlin 可以直接调用 Java 编写的代码,也可以无缝使用 Java 第三方开源库,这使得 Kotlin 在加入了诸多新特性的同时,还继承了 Java 的全部财富
…
当然也有一些劣势:
①Kotlin在编译后会生成比Java更多的类,这可能会影响安装包的体积。
②Kotlin虽然和Java完全兼容,但是部分功能在Java下使用并不太方便美观,所以库作者如果对代码(美观)有严格要求,最好在外层包裹一层Java调用层。
HttpManager.INSTANCE.create(MineApi.class).getCashRecordList(map).enqueue(new RequestCallback<>(baseResponseBuilder -> {//dsl会变成如下,然后lambda变成Function,Function1,Function2等(根据参数位数)
baseResponseBuilder.onSuccess(new Function1<BaseResponse<ArrayList>, Unit>({
@Override
public Unit invoke(BaseResponse<ArrayList> arrayListBaseResponse)
…
return null;
}
});
return null;
}));
③Kotlin的KAPT需要生成APT可以解析的stub(Java代码),然后再利用APT生成代码,这影响了KAPT的性能,从而拖慢Kotlin项目整体编译速度。
对应kotlin也发布的alpha版原生处理的KSP,号称带来了两倍的速度提升[Announcing Kotlin Symbol Processing (KSP) Alpha]
KSP offers a powerful and yet simple API for parsing Kotlin code directly, dramatically reducing the build speed tax imposed by KAPT’s stub generation. Indeed, initial benchmarks with the Room library show that KSP is approximately 2x faster than KAPT.
总的来说,Kotlin带来的优点是大于缺点的,简洁易用的语法,代码量大大减少。实用的类型系统,避免了绝大部分的空指针异常。而且与Java完全兼容,谷歌宣布Kotlin First后,Android开发迟早也是要迁移到Kotlin的,再加上随着KSP的发布,kotlin编译缓慢的问题得到解决,生态会越来越好。
《kotlin实战》 [俄]Dmitry Jemerov , Svetlana Isakova著
Kotlin inline, noinline and crossinline_
由Kotlin 中关键字out和in和Java中的泛型的比较学习
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
[外链图片转存中…(img-CNs90nWq-1711941213734)]
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-7270nGOv-1711941213734)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-ghBTSDaw-1711941213734)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-FIfvpfx0-1711941213734)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。