2024年kotlin 入门中文教程(1),2024年最新android应用开发面试题

最后

为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。

加油,共勉。

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

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

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

val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
while (index < list.size) {
println(list[index])
index++
}

val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
do {
println(list[index])
index++
} while (index < list.size)

5.6、返回和跳转

kotlin 有三种结构化跳转表达式:

  • return 默认从最直接包围它的函数或者匿名函数返回
  • break 终止最直接包围它的循环
  • continue 继续下一次最直接包围它的循环

在 kotlin 中任何表达式都可以用标签(label)来标记,标签的格式为标识符后跟 @ 符号,例如:abc@ 、fooBar@ 都是有效的标签

fun main() {
fun1()
}

fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
loop@ for (it in list) {
if (it == 8) {
continue
}
if (it == 23) {
break@loop
}
println(“value is $it”)
}
println(“function end”)
}

value is 1
value is 4
value is 6
value is 12
function end

kotlin 有函数字面量、局部函数和对象表达式。因此 kotlin 的函数可以被嵌套

标签限制的 return 允许我们从外层函数返回,最重要的一个用途就是从 lambda 表达式中返回。通常情况下使用隐式标签更方便,该标签与接受该 lambda 的函数同名

fun main() {
fun1()
fun2()
fun3()
fun4()
fun5()
}

fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return
}
println(“value is $it”)
}
println(“function end”)

// value is 1
// value is 4
// value is 6
}

fun fun2() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return@fun2
}
println(“value is $it”)
}
println(“function end”)

// value is 1
// value is 4
// value is 6
}

//fun3() 和 fun4() 中使用的局部返回类似于在常规循环中使用 continue
fun fun3() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return@forEach
}
println(“value is $it”)
}
println(“function end”)

// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}

fun fun4() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach loop@{
if (it == 8) {
return@loop
}
println(“value is $it”)
}
println(“function end”)

// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}

fun fun5() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) {
//局部返回到匿名函数的调用者,即 forEach 循环
return
}
println(“value is $value”)
})
println(“function end”)
}

六、区间

Ranges 表达式使用一个 操作符来声明一个闭区间,它被用于定义实现了一个 RangTo 方法

以下三种声明形式是等价的

var index = 5

if (index >= 0 && index <= 10) {

}

if (index in 0…10) {

}

if (index in 0.rangeTo(10)) {

}

数字类型的 ranges 在被迭代时,编译器会将它们转换为与 Java 中使用 index 的 for 循环的相同字节码的方式来进行优化

Ranges 默认会自增长,所以如果像以下的代码就不会被执行

for (index in 10…0) {
println(index)
}

可以改用 downTo 函数来将之改为递减

for (index in 10 downTo 0) {
println(index)
}

可以在 ranges 中使用 step 来定义每次循环递增或递增的长度:

for (index in 1…8 step 2){
println(index)
}
for (index in 8 downTo 1 step 2) {
println(index)
}

以上声明的都是闭区间,如果想声明的是开区间,可以使用 until 函数:

for (index in 0 until 4){
println(index)
}

扩展函数 reversed() 可用于返回将区间反转后的序列

val rangeTo = 1.rangeTo(3)
for (i in rangeTo) {
println(i) //1 2 3
}
for (i in rangeTo.reversed()) {
println(i) //3 2 1
}

七、修饰符

7.1、final 和 oepn

kotlin 中的类和方法默认都是 final 的,即不可继承的,如果想允许创建一个类的子类,需要使用 open 修饰符来标识这个类,此外,也需要为每一个希望被重写的属性和方法添加 open 修饰符

open class View {
open fun click() {

}
//不能在子类中被重写
fun longClick() {

}
}

class Button : View() {
override fun click() {
super.click()
}
}

如果重写了一个基类或者接口的成员,重写了的成员同样默认是 open 的。例如,如果 Button 类是 open 的,则其子类也可以重写其 click() 方法

open class Button : View() {
override fun click() {
super.click()
}
}

class CompatButton : Button() {
override fun click() {
super.click()
}
}

如果不想要类的子类重写该方法的实现,可以显式地将重写的成员标记为 final

open class Button : View() {
override final fun click() {
super.click()
}
}

7.2、public

public 修饰符是限制级最低的修饰符,是默认的修饰符。如果一个定义为 public 的成员被包含在一个 private 修饰的类中,那么这个成员在这个类以外也是不可见的

7.3、protected

protected 修饰符只能被用在类或者接口中的成员上。在 Java 中,可以从同一个包中访问一个 protected 的成员,但对于 kotlin 来说,protected 成员只在该类和它的子类中可见。此外,protected 不适用于顶层声明

7.4、internal

一个定义为 internal 的包成员,对其所在的整个 module 可见。如果它是一个其它领域的成员,它就需要依赖那个领域的可见性了。比如,如果我们写了一个 private 类,那么它的 internal 修饰的函数的可见性就会限制于它所在的这个类的可见性

我们可以访问同一个 module 中的 internal 修饰的类,但是其它 module 是访问不到该 internal 类的,该修饰符可用于对外发布的开源库,将开源库中不希望被外部引用的代码设为 internal 权限,可避免对外部引用库造成混淆

根据 Jetbrains 的定义,一个 module 应该是一个单独的功能性的单位,可以看做是一起编译的 kotlin 文件的集合,它应该是可以被单独编译、运行、测试、debug 的。相当于在 Android Studio 中主工程引用的 module,Eclipse 中在一个 workspace 中的不同的 project

7.5、private

private 修饰符是限制级最高的修饰符,kotlin 允许在顶层声明中使用 private 可见性,包括类、函数和属性,这表示只在自己所在的文件中可见,所以如果将一个类声明为 private,就不能在定义这个类之外的地方中使用它。此外,如果在一个类里面使用了 private 修饰符,那访问权限就被限制在这个类里面,继承这个类的子类也不能使用它。所以如果类、对象、接口等被定义为 private,那么它们只对被定义所在的文件可见。如果被定义在了类或者接口中,那它们只对这个类或者接口可见

7.6、总结
修饰符类成员顶层声明
public(默认)所有地方可见所有地方可见
internal模块中可见模块中可见
protected子类中可见
private类中可见文件中可见

八、空安全

8.1、可空性

在 kotlin 中,类型系统将一个引用分为可以容纳 null (可空引用)或者不能容纳 null(非空引用)两种类型。 例如,String 类型的常规变量不能指向 null

var name: String = “leavesC”
//编译错误
//name = null

如果希望一个变量可以储存 null 引用,需要显式地在类型名称后面加上问号

var name: String? = “leavesC”
name = null

问号可以加在任何类型的后面来表示这个类型的变量可以存储 null 引用:Int?、Doubld? 、Long?

kotlin 对可空类型的显式支持有助于防止 NullPointerException 导致的异常问题,编译器不允许不对可空变量做 null 检查就直接调用其属性。这个强制规定使得开发者必须在编码初期就考虑好变量的可赋值范围并为其各个情况做好分支处理

fun check(name: String?): Boolean {
//error,编译器不允许不对 name 做 null 检查就直接调用其属性
return name.isNotEmpty()
}

正确的做法事显式地进行 null 检查

fun check(name: String?): Boolean {
if (name != null) {
return name.isNotEmpty()
}
return false
}

8.2、安全调用运算符:?.

安全调用运算符:?. 允许把一次 null 检查和一次方法调用合并为一个操作,如果变量值非空,则方法或属性会被调用,否则直接返回 null

例如,以下两种写法是完全等同的:

fun check(name: String?) {
if (name != null) {
println(name.toUpperCase())
} else {
println(null)
}
}

fun check(name: String?) {
println(name?.toUpperCase())
}

8.3、Elvis 运算符:?:

Elvis 运算符:?: 用于替代 ?. 直接返回默认值 null 的情况,Elvis 运算符接收两个运算数,如果第一个运算数不为 null ,运算结果就是其运算结果值,如果第一个运算数为 null ,运算结果就是第二个运算数

例如,以下两种写法是完全等同的:

fun check(name: String?) {
if (name != null) {
println(name)
} else {
println(“default”)
}
}

fun check(name: String?) {
println(name ?: “default”)
}

8.4、安全转换运算符:as?

安全转换运算符:as? 用于把值转换为指定的类型,如果值不适合该类型则返回 null

fun check(any: Any?) {
val result = any as? String
println(result ?: println(“is not String”))
}

8.5、非空断言:!!

非空断言用于把任何值转换为非空类型,如果对 null 值做非空断言,则会抛出异常

fun main() {
var name: String? = “leavesC”
check(name) //7

name = null
check(name) //kotlinNullPointerException
}

fun check(name: String?) {
println(name!!.length)
}

8.6、可空类型的扩展

为可空类型定义扩展函数是一种更强大的处理 null 值的方式,可以允许接收者为 null 的调用,并在该函数中处理 null ,而不是在确保变量不为 null 之后再调用它的方法

例如,如下方法可以被正常调用而不会发生空指针异常

val name: String? = null
println(name.isNullOrEmpty()) //true

isNullOrEmpty() 的方法签名如下所示,可以看到这是为可空类型 CharSequence? 定义的扩展函数,方法中已经处理了方法调用者为 null 的情况

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || this.length == 0
}

8.7、平台类型

平台类型是 kotlin 对 java 所作的一种平衡性设计。kotlin 将对象的类型分为了可空类型和不可空类型两种,但 java 平台的一切对象类型均为可空的,当在 kotlin 中引用 java 变量时,如果将所有变量均归为可空类型,最终将多出许多 null 检查;如果均看成不可空类型,那么就很容易就写出忽略了NPE 风险的代码。为了平衡两者,kotlin 引入了平台类型,即当在 kotlin 中引用 java 变量值时,既可以将之看成可空类型,也可以将之看成不可空类型,由开发者自己来决定是否进行 null 检查

九、类型的检查与转换

9.1、类型检查

is 与 !is 操作符用于在运行时检查对象是否符合给定类型:

fun main() {
val strValue = “leavesC”
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}

fun parserType(value: Any) {
when (value) {
is String -> println(“value is String , length : ${value.length}”)
is Int -> println(“value is Int , toLong : ${value.toLong()}”)
!is Long -> println(“value !is Long”)
else -> println(“unknown”)
}
}

9.2、智能转换

在许多情况下,不需要在 kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的 is 检查以及显式转换,并在需要时自动插入安全的转换

例如,对于以下例子来说,当判断 value 为 String 类型通过时,就可以直接将 value 当做 String 类型变量并调用其内部属性

fun main() {
val strValue = “leavesC”
parserType(strValue) //value is String , length : 7

val intValue = 100
parserType(intValue) //value is Int , toLong : 100

val doubleValue = 100.22
parserType(doubleValue) //value !is Long

val longValue = 200L
parserType(longValue) //unknown
}

fun parserType(value: Any) {
when (value) {
is String -> println(“value is String , length : ${value.length}”)
is Int -> println(“value is Int , toLong : ${value.toLong()}”)
!is Long -> println(“value !is Long”)
else -> println(“unknown”)
}
}

编译器会指定根据上下文环境,将变量智能转换为合适的类型

if (value !is String) return
//如果 value 非 String 类型时直接被 return 了,所以此处可以直接访问其 length 属性
println(value.length)

// || 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value !is String || value.length > 0) {

}

// && 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value is String && value.length > 0) {

}

9.3、不安全的转换操作符

如果转换是不可能的,转换操作符 as 会抛出一个异常。因此,我们称之为不安全的转换操作符

fun main() {
parserType(“leavesC”) //value is String , length is 7
parserType(10) //会抛出异常 ClassCastException
}

fun parserType(value: Any) {
val tempValue = value as String
println(“value is String , length is ${tempValue.length}”)
}

需要注意的是,null 不能转换为 String 变量,因为该类型不是可空的

因此如下转换会抛出异常

val x = null
val y: String = x as String //会抛出异常 TypeCastException

为了匹配安全,可以转换的类型声明为可空类型

val x = null
val y: String? = x as String?

9.4、安全的转换操作符

可以使用安全转换操作符 as? 来避免在转换时抛出异常,它在失败时返回 null

val x = null
val y: String? = x as? String

尽管以上例子 as? 的右边是一个非空类型的 String,但是其转换的结果是可空的

十、类

10.1、基本概念

类的概念就是把数据和处理数据的代码封装成一个单一的实体。在 Java 中,数据存储在一个私有字段中,通过提供访问器方法:getter 和 setter 来访问或者修改数据

在 Java 中以下的示例代码是很常见的,Point 类包含很多重复的代码:通过构造函数把参数赋值给有着相同名称的字段,通过 getter 来获取属性值

public final class Point {

private final int x;

private final int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public final int getX() {
return this.x;
}

public final int getY() {
return this.y;
}

}

使用 kotlin 来声明 Point 类则只需要一行代码,两者完全等同

class Point(val x: Int, val y: Int)

kotlin 也使用关键字 class 来声明类,类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成,类头与类体都是可选的,如果一个类没有类体,可以省略花括号。此外,kotlin 中类默认是 publish(公有的) 且 final (不可继承)的

kotlin 区分了主构造方法(在类体外部声明)和次构造方法(在类体内部声明),一个类可以有一个主构造函数和多个次构造函数,此外也允许在初始化代码块中 init 添加额外的初始化逻辑

10.2、主构造函数

主构造函数是类头的一部分,跟在类名(和可选的类型参数)后,主构造函数的参数可以是可变的(var)或只读的(val)

class Point constructor(val x: Int, val y: Int) {

}

如果主构造函数没有任何注解或者可见性修饰符,可以省略 constructor 关键字

class Point(val x: Int, val y: Int) {

}

//如果不包含类体,则可以省略花括号
class Point(val x: Int, val y: Int)

如果构造函数有注解或可见性修饰符,则 constructor 关键字是必需的,并且这些修饰符在它前面

class Point public @Inject constructor(val x: Int, val y: Int) {

}

主构造函数不能包含任何的代码,初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中,初始化块包含了在类被创建时执行的代码,主构造函数的参数可以在初始化块中使用。如果需要的话,也可以在一个类中声明多个初始化语句块。需要注意的是,构造函数的参数如果用 val/var 进行修饰,则相当于在类内部声明了一个同名的全局属性。如果不加 val/var 进行修饰,则构造函数只能在 init 函数块和全局属性初始化时进行引用

此外,要创建一个类的实例不需要使用 Java 中的 new 关键字,像普通函数一样调用构造函数即可

class Point(val x: Int, val y: Int) {

init {
println(“initializer blocks , x value is: $x , y value is: $y”)
}

}

fun main() {
Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}

主构造函数的参数也可以在类体内声明的属性初始化器中使用

class Point(val x: Int, val y: Int) {

private val localX = x + 1

private val localY = y + 1

init {
println(“initializer blocks , x value is: $x , y value is: $y”)
println(“initializer blocks , localX value is: $localX , localY value is: $localY”)
}

}

fun main() {
Point(1, 2)
//initializer blocks , x value is: 1 , y value is: 2
//initializer blocks , localX value is: 2 , localY value is: 3
}

10.3、次构造函数

类也可以声明包含前缀 constructor 的次构造函数。如果类有一个主构造函数,每个次构造函数都需要直接委托给主构造函数或者委托给另一个次构造函数以此进行间接委托,用 this 关键字来进行指定即可

class Point(val x: Int, val y: Int) {

private val localX = x + 1

private val localY = y + 1

init {
println(“initializer blocks , x value is: $x , y value is: $y”)
println(“initializer blocks , localX value is: $localX , localY value is: $localY”)
}

constructor(base: Int) : this(base + 1, base + 1) {
println(“constructor(base: Int)”)
}

constructor(base: Long) : this(base.toInt()) {
println(“constructor(base: Long)”)
}

}

fun main() {
Point(100)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
Point(100L)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
//constructor(base: Long)
}

初始化块中的代码实际上会成为主构造函数的一部分,委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块。如果一个非抽象类没有声明任何(主或次)构造函数,会默认生成一个不带参数的公有主构造函数

10.4、属性

在 Java 中,字段和其访问器的组合被称作属性。在 kotlin 中,属性是头等的语言特性,完全替代了字段和访问器方法。在类中声明一个属性和声明一个变量一样是使用 val 和 var 关键字。val 变量只有一个 getter ,var 变量既有 getter 也有 setter

fun main() {
val user = User()
println(user.name)
user.age = 200
}

class User() {

val name: String = “leavesC”

var age: Int = 25

}

10.5、自定义访问器

访问器的默认实现逻辑很简单:创建一个存储值的字段,以及返回属性值的 getter 和更新属性值的 setter。如果需要的话,也可以自定义访问器

例如,以下就声明了三个带自定义访问器的属性

class Point(val x: Int, val y: Int) {

val isEquals1: Boolean
get() {
return x == y
}

val isEquals2
get() = x == y

var isEquals3 = false
get() = x > y
set(value) {
field = !value
}

}

如果仅需要改变一个访问器的可见性或者为其添加注解,那么可以定义访问器而不定义其实现

fun main() {
val point = Point(10, 10)
println(point.isEquals1)
//以下代码会报错
//point.isEquals1 = true
}

class Point(val x: Int, val y: Int) {

var isEquals1: Boolean = false
get() {
return x == y
}
private set

}

10.6、延迟初始化

一般地,非空类型的属性必须在构造函数中初始化,但像使用了 Dagger2 这种依赖注入框架的项目来说就十分的不方便了,为了应对这种情况,可以用 lateinit 修饰符来标记该属性,用于告诉编译器该属性会在稍后的时间被初始化

用 lateinit 修饰的属性或变量必须为非空类型,并且不能是原生类型

class Point(val x: Int, val y: Int)

class Example {

lateinit var point: Point

var point2: Point

constructor() {
point2 = Point(10, 20)
}

}

如果访问了一个未经过初始化的 lateinit 变量,则会抛出一个包含具体原因(该变量未初始化)的异常信息

Exception in thread “main” kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized

十一、类的分类

11.1、抽象类

声明为 abstract 的类内部可以包含没有实现体的成员方法,且该成员方法也用 abstract 标记,这种类称为抽象类,包含的没有实现体的方法称为抽象方法

此外,我们并不需要用 open 标注一个抽象类或者抽象方法,因为这是默认声明的

abstract class BaseClass {
abstract fun fun1()
}

class Derived : BaseClass() {
override fun fun1() {

}
}

11.2、数据类

数据类是一种非常强大的类,可以避免重复创建 Java 中的用于保存状态但又操作非常简单的 POJO 的模版代码,它们通常只提供了用于访问它们属性的简单的 getter 和 setter

定义一个新的数据类非常简单,例如

data class Point(val x: Int, val y: Int)

数据类默认地为主构造函数中声明的所有属性生成了如下几个方法

  • getter、setter(需要是 var)
  • componentN()。按主构造函数的属性声明顺序进行对应
  • copy()
  • toString()
  • hashCode()
  • equals()

为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:

  • 主构造函数需要包含一个参数
  • 主构造函数的所有参数需要标记为 val 或 var
  • 数据类不能是抽象、开放、密封或者内部的

可以利用 IDEA 来反编译查看 Point 类的 Java 实现,了解其内部实现

public final class Point {
private final int x;
private final int y;

public final int getX() {
return this.x;
}

public final int getY() {
return this.y;
}

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public final int component1() {
return this.x;
}

public final int component2() {
return this.y;
}

@NotNull
public final Point copy(int x, int y) {
return new Point(x, y);
}

// $FF: synthetic method
// F F : b r i d g e m e t h o d @ N o t N u l l p u b l i c s t a t i c P o i n t c o p y FF: bridge method @NotNull public static Point copy FF:bridgemethod@NotNullpublicstaticPointcopydefault(Point var0, int var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.x;
}

if ((var3 & 2) != 0) {
var2 = var0.y;
}

return var0.copy(var1, var2);
}

public String toString() {
return “Point(x=” + this.x + “, y=” + this.y + “)”;
}

public int hashCode() {
return this.x * 31 + this.y;
}

public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Point) {
Point var2 = (Point)var1;
if (this.x == var2.x && this.y == var2.y) {
return true;
}
}

return false;
} else {
return true;
}
}
}

通过数据类可以简化很多的通用操作,可以很方便地进行:格式化输出变量值、映射对象到变量、对比变量之间的相等性、复制变量等操作

fun main() {
val point1 = Point(10, 20)
val point2 = Point(10, 20)
println(“point1 toString() : $point1”) //point1 toString() : Point(x=10, y=20)
println(“point2 toString() : $point2”) //point2 toString() : Point(x=10, y=20)

val (x, y) = point1
println(“point1 x is $x,point1 y is $y”) //point1 x is 10,point1 y is 20

//在 kotlin 中,“ == ” 相当于 Java 的 equals 方法
//而 “ === ” 相当于 Java 的 “ == ” 方法
println(“point1 == point2 : ${point1 == point2}”) //point1 == point2 : true
println(“point1 === point2 : ${point1 === point2}”) //point1 === point2 : false

val point3 = point1.copy(y = 30)
println(“point3 toString() : $point3”) //point3 toString() : Point(x=10, y=30)
}

需要注意的是,数据类的 toString()、equals()、hashCode()、copy() 等方法只考虑主构造函数中声明的属性,因此在比较两个数据类对象的时候可能会有一些意想不到的结果

data class Point(val x: Int) {

var y: Int = 0

}

fun main() {
val point1 = Point(10)
point1.y = 10

val point2 = Point(10)
point2.y = 20

println(“point1 == point2 : ${point1 == point2}”) //point1 == point2 : true
println(“point1 === point2 : ${point1 === point2}”) //point1 === point2 : false
}

11.3、密封类

Sealed 类(密封类)用于对类可能创建的子类进行限制,用 Sealed 修饰的类的直接子类只允许被定义在 Sealed 类所在的文件中(密封类的间接继承者可以定义在其他文件中),这有助于帮助开发者掌握父类与子类之间的变动关系,避免由于代码更迭导致的潜在 bug,且密封类的构造函数只能是 private 的

例如,对于 View 类,其子类只能定义在与之同一个文件里,Sealed 修饰符修饰的类也隐含表示该类为 open 类,因此无需再显式地添加 open 修饰符

sealed class View {

fun click() {

}

}

class Button : View() {

}

class TextView : View() {

}

因为 Sealed 类的子类对于编译器来说是可控的,所以如果在 when 表达式中处理了所有 Sealed 类的子类,那就不需要再提供 else 默认分支。即使以后由于业务变动又新增了 View 子类,编译器也会检测到 check 方法缺少分支检查后报错,所以说 check 方法是类型安全的

fun check(view: View): Boolean {
when (view) {
is Button -> {
println(“is Button”)
return true
}
is TextView -> {
println(“is TextView”)
return true
}
}
}

11.4、枚举类

kotlin 也提供了枚举的实现,相比 Java 需要多使用 class 关键字来声明枚举

enum class Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

枚举可以声明一些参数

enum class Day(val index: Int) {
SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)
}

此外,枚举也可以实现接口

interface OnChangedListener {

fun onChanged()

}

enum class Day(val index: Int) : OnChangedListener {
SUNDAY(0) {
override fun onChanged() {

}
},
MONDAY(1) {
override fun onChanged() {

}
}
}

枚举也包含有一些共有函数

fun main() {
val day = Day.FRIDAY
//获取值
val value = day.index //5
//通过 String 获取相应的枚举值
val value1 = Day.valueOf(“SUNDAY”) //SUNDAY
//获取包含所有枚举值的数组
val value2 = Day.values()
//获取枚举名
val value3 = Day.SUNDAY.name //SUNDAY
//获取枚举声明的位置
val value4 = Day.TUESDAY.ordinal //2
}

11.5、匿名内部类

使用对象表达式来创建匿名内部类实例

interface OnClickListener {

fun onClick()

}

class View {

fun setClickListener(clickListener: OnClickListener) {

}

}

fun main() {
val view = View()
view.setClickListener(object : OnClickListener {
override fun onClick() {

}

})
}

11.6、嵌套类

在 kotlin 中在类里面再定义的类默认是嵌套类,此时嵌套类不会包含对外部类的隐式引用

class Outer {

private val bar = 1

class Nested {
fun foo1() = 2
//错误
//fun foo2() = bar
}
}

fun main() {
val demo = Outer.Nested().foo1()
}

以上代码通过 IDEA 反编译后可以看到其内部的 Java 实现方式

可以看到 Nested 其实就是一个静态类,因此 foo2() 不能访问外部类的非静态成员,也不用先声明 Outer 变量再指向 Nested 类,而是直接通过 Outer 类指向 Nested 类

public final class Outer {
private final int bar = 1;

public static final class Nested {
public final int foo1() {
return 2;
}
}
}

public final class MainkotlinKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, “args”);
int demo = (new Outer.Nested()).foo1();
}
}

11.7、内部类

如果需要去访问外部类的成员,需要用 inner 修饰符来标注被嵌套的类,这称为内部类。内部类会隐式持有对外部类的引用

class Outer {

private val bar = 1

inner class Nested {
fun foo1() = 2
fun foo2() = bar
}
}

fun main() {
val demo = Outer().Nested().foo2()
}

再来看其内部的 Java 实现方式

使用 inner 来声明 Nested 类后,就相当于将之声明为非静态内部类,因此 foo2() 能访问其外部类的非静态成员,在声明 Nested 变量前也需要通过 Outer 变量来指向其内部的 Nested 类

public final class Outer {
private final int bar = 1;

public final class Nested {
public final int foo1() {
return 2;
}

public final int foo2() {
return Outer.this.bar;
}
}
}

public final class MainkotlinKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, “args”);
int demo = (new Outer().new Nested()).foo2();
}
}

类A在类B中声明在Java中在kotlin中
嵌套类(不存储外部类的引用)static class Aclass A
内部类(存储外部类的引用)class Ainner class A

十二、接口

12.1、抽象方法与默认方法

kotlin 中的接口与 Java 8 中的类似,可以包含抽象方法的定义以及非抽象方法的实现,不需要使用 default 关键字来标注有默认实现的非抽象方法,但在实现接口的抽象方法时需要使用 override 进行标注

fun main() {
val view = View()
view.click()
view.longClick()
}

class View : Clickable {

override fun click() {
println(“clicked”)
}

}

interface Clickable {
fun click()
fun longClick() = println(“longClicked”)
}

如果一个类实现了多个接口,而接口包含带有默认实现且签名相同的方法,此时编译器就会要求开发者必须显式地实现该方法,可以选择在该方法中调用不同接口的相应实现

class View : Clickable, Clickable2 {

override fun click() {
println(“clicked”)
}

override fun longClick() {
super.longClick()
super.longClick()
}
}

interface Clickable {
fun click()
fun longClick() = println(“longClicked”)
}

interface Clickable2 {
fun click()
fun longClick() = println(“longClicked2”)
}

12.2、抽象属性

接口中可以包含抽象属性声明,接口不定义该抽象属性是应该存储到一个支持字段还是通过 getter 来获取,接口本身并不包含任何状态,因此只有实现这个接口的类在需要的情况下会存储这个值

看以下例子,Button 类和 TextView 类都实现了 Clickable 接口,并都提供了取得 statusValue 值的方式

Button 类提供了一个自定义的 getter 用于在每次访问时重新获取 statusValue 值,因此在多次获取属性值时其值可能都会不一致,因为每次 getRandom() 方法都会被调用

TextView 类中的 statusValue 属性有一个支持字段来存储在类初始化时得到的数据,因此其值在初始化后是不会再次获取值,即 TextView 类中的 getRandom() 只会被调用一次

fun main() {
val button = Button()
println(button.statusValue)
val textView = TextView()
println(textView.statusValue)
}

class Button : Clickable {

override val statusValue: Int
get() = getRandom()

private fun getRandom() = Random().nextInt(10)

}

class TextView : Clickable {

override val statusValue: Int = getRandom()

private fun getRandom() = Random().nextInt(10)

}

interface Clickable {

val statusValue: Int

}

除了可以声明抽象属性外,接口还可以包含具有 getter 和 setter 的属性,只要它们没有引用一个支持字段(支持字段需要在接口中存储状态,而这是不允许的)

interface Clickable {

val statusValue: Int

val check: Boolean
get() = statusValue > 10

}

十三、继承

在 kotlin 中所有类都有一个共同的超类 Any ,对于没有超类声明的类来说它就是默认超类。需要注意的是, Any 并不是 java.lang.Object ,它除了 equals() 、 hashCode() 与 toString() 外没有其他属性或者函数

要声明一个显式的超类,需要把父类名放到类头的冒号之后

open class Base()

class SubClass() : Base()

当中,类上的 open 标注与 Java 中的 final 含义相反,用于允许其它类从这个类继承。默认情况下,kotlin 中所有的类都是 final

如果派生类有一个主构造函数,其基类型必须直接或间接调用基类的主构造函数

open class Base(val str: String)

class SubClass(val strValue: String) : Base(strValue)

class SubClass2 : Base {

constructor(strValue: String) : super(strValue)

constructor(intValue: Int) : super(intValue.toString())

constructor(doubValue: Double) : this(doubValue.toString())

}

13.1、覆盖方法

与 Java 不同,kotlin 需要显式标注可覆盖的成员和覆盖后的成员:

open class Base() {
open fun fun1() {

}

fun fun2() {

}
}

class SubClass() : Base() {
override fun fun1() {
super.fun1()
}
}

用 open 标注的函数才可以被子类重载,子类用 override 表示该函数是要对父类的同签名函数进行覆盖。标记为 override 的成员本身也是开放的,也就是说,它可以被子类覆盖。如果想禁止再次覆盖,可以使用 final 关键字标记 如果父类没有使用 open 对函数进行标注,则子类不允许定义相同签名的函数。对于一个 final 类(没有用 open 标注的类)来说,使用 open 标记属性和方法是无意义的

13.2、属性覆盖

属性覆盖与方法覆盖类似。在超类中声明为 open 的属性,如果要进行覆盖则必须在派生类中重新声明且以 override 开头,并且它们必须具有兼容的类型

每个声明的属性可以由具有初始化器的属性或者具有 getter 方法的属性覆盖

设计模式学习笔记

设计模式系列学习视频

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

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

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

s Base()

class SubClass() : Base()

当中,类上的 open 标注与 Java 中的 final 含义相反,用于允许其它类从这个类继承。默认情况下,kotlin 中所有的类都是 final

如果派生类有一个主构造函数,其基类型必须直接或间接调用基类的主构造函数

open class Base(val str: String)

class SubClass(val strValue: String) : Base(strValue)

class SubClass2 : Base {

constructor(strValue: String) : super(strValue)

constructor(intValue: Int) : super(intValue.toString())

constructor(doubValue: Double) : this(doubValue.toString())

}

13.1、覆盖方法

与 Java 不同,kotlin 需要显式标注可覆盖的成员和覆盖后的成员:

open class Base() {
open fun fun1() {

}

fun fun2() {

}
}

class SubClass() : Base() {
override fun fun1() {
super.fun1()
}
}

用 open 标注的函数才可以被子类重载,子类用 override 表示该函数是要对父类的同签名函数进行覆盖。标记为 override 的成员本身也是开放的,也就是说,它可以被子类覆盖。如果想禁止再次覆盖,可以使用 final 关键字标记 如果父类没有使用 open 对函数进行标注,则子类不允许定义相同签名的函数。对于一个 final 类(没有用 open 标注的类)来说,使用 open 标记属性和方法是无意义的

13.2、属性覆盖

属性覆盖与方法覆盖类似。在超类中声明为 open 的属性,如果要进行覆盖则必须在派生类中重新声明且以 override 开头,并且它们必须具有兼容的类型

每个声明的属性可以由具有初始化器的属性或者具有 getter 方法的属性覆盖

设计模式学习笔记

[外链图片转存中…(img-5EGQfBXj-1714983497795)]

设计模式系列学习视频

[外链图片转存中…(img-45jtuZB8-1714983497796)]

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

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

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

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值