目录

一、基础语法

Koltin 是一种静态强类型语言,它要求在编译时确定变量类型,并在运行时严格执行类型检查。

  1. 声明变量时必须指定变量类型,无法隐式地将不同类型的值分配给变量。
  2. 有助于减少类型错误,使得代码更加稳健和可维护。

1.1 常见数据类型

Byte、 Short、 Int、 Long、 Float、 Double、 Char、 Boolean、 String

特殊的类型: Unit, 类似 java 中的 Void, 函数或者表达式没有返回值时用 Unit

无 java 中的 int、 Integer 等,避免装箱拆箱的性能损耗。

在某些情况下,Kotlin 仍然需要进行装箱拆箱操作,例如将基本类型的值传递给需要对象类型的函数时,或者将基本类型的值放入集合或数组中时。

转换:

3.toFloat()

val a = 5.6f
a.toInt()

字符串 String

val name: String = "Lucy"

// 可循环
for (c in name) { } // 可拼接
println(name + "abc") // Lucyabc // 字面值
val s = "Hello, world!\n" // 反斜杠转义 // 三引号内部不转义,可以包含任何字符,包括换行符
val text = """
for (c in "abc") {
println(c)
}
""" // 字符串模板
val name = "James"
println("$name is a basketball player!") // 用 ${} 引用表达式
println("$name 这个单词的长度是 ${name.length}")

1.2 变量

1.2.1 变量声明

var um: Int = 0  // 使用 var 声明一个可读可写的变量
val name: String = "Jimy" // 使用 val 声明一个引用不可变的变量,对象成员可变,Java 中 final 的效果, 尽可能采用 val 声明变量。

1.2.2 类型推断

val name = "Lucy"  // name 可以推断出 name 是 String  类型
name.uppercase() // 正确 // fails to compile
name.inc() // inc() 是 Int 类型的的运算浮, String 类型无法调用

1.2.3 Null 安全

可空类型

// Fails to complie
val name: String = null

要使变量持有 null 值,它必须是可为 null 类型,在变量类型后面加上一个 ? 后缀。

val name: String? = null

指定 String? 类型后,可以为 name 赋予 String 值或者 null

安全调用

安全调用 ?.

非空断言 !!

// java
String name = "Lucy"
if (name != null ) {
name.toUpperCase()
} // Koltin
val name: String? = null
name?.uppercase() // name 不为 null 时执行方法调用
name!!.uppercase() // name 为 null 时,抛出异常

多层判断可以链式调用。

Elvis操作符

// java
int nameLength = name!=null ? name.length : -1 // Koltin
val nameLength = name?.length?:-1

注意: 上面 Kotlin 版本中 nameLength 类型为 Int

val name: String? = "Lucy"
val length = name?.length

上面这段代码中 length 是什么类型? ----- Int?

1.2.4 面向对象语言

Any ---- 类似于 java 中的 Object, 所有非空类型的根类型

Any? ---- 所有类型的根类型。 自然也是 Any 的跟类型。

Nothing ---- 所有类型的最底层,包含一个值 null

1.3 流程控制

1.3.1 if 表达式

// 传统写法
var max: Int
if (a > b) {
max = a
} else {
max = b
} // 表达式写法
var max: Int = if (a > b) a else b

注意: if 作为表达式而不是语句时,需要有 else 分支。

1.3.2 When 表达式

when(x) {
1 -> { println("x == 1") }
is Int -> { println("x is a Int") }
2, 3 -> {println("x == 2 or x == 3")} // 多个分支条件放在一起用 , 隔开
else -> { println("x maybe a string") }
}

多个分支条件放在一起用 , 隔开。

when 将它的参数与所有的分支条件顺序比较,直到某个分支满足条件(匹配第一个满足条件的分支)

when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值。必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。(例如 枚举 或者 密封类子类。

When 进阶用法,不提供参数:

if (age > 60) {
println("老年人,可以享受6折优惠")
} else if (age > 0) {
if (identity is Student) {
println("学生,可以享受半价优惠")
} else {
println("需要全价")
}
} else {
throw AgeIlligalException("age must be greater than 0")
} 等价于 when {
age < 0 -> throw AgeIlligalException("age must be greater than 0")
age > 60 -> println("老年人,可以享受6折优惠")
identity is Student -> println("学生,可以享受半价优惠")
else -> println("需要全价")
}

1.3.3 For 循环

// rangeTo [0, 10]
for(item in 0 .. 10) {
...
} // [0, 10) 中缀函数
for (i in 0 until 10) { } for(item in list) { } for ((index, value) in list.withIndex()) { }

1.3.4 While 循环

while (x > 0) {
x--
} do {
val y = 5
y--
} while (y > 0) // y 在此处可见的

二、函数与 lambda 表达式

2.1 函数声明

Kotlin 函数用 fun 声明,默认是 public 的。

代码块函数体:

fun sum(x: Int, y: Int): Int {
return x + y
}

表达式函数体:

fun sum(x: Int, y: Int) = x + y

Kotlin 支持类型推导,使用表达式函数体,一般情况下可以不声明返回值类型。在一些诸如递归等复杂情况下,即使是使用表达式函数体,也必须显示声明返回值类型。

总结:

  1. 函数参数必须显示声明类型
  2. 非表达式函数体,函数参数必须显示声明类型, 返回值除了类型是 Unit,可以省略,其它情况,返回值都必须显示声明类型
  3. 如果它是一个递归的函数,必须显示声明参数和返回值类型
  4. 如果它是一个公有方法,为了代码的可读性,尽量显示声明函数参数和返回值类型

2.2 函数类型

2.2.1 示例引入

先看一个例子:

// 定义一个打招呼的方法
fun greetPeople(name: String) {
// ... 做一些额外的事情 greetingWithEnglish(name)
} private greetingWithChinese(name: String) {
println("早上好, $name!")
}

假设现在要进行国际化,加入了英语打招呼的方式

fun greetingWithEnglish(name: String) {
println("Good morning, $name!")
}

需要对 greetPeople 方法进行改造

enum class Language {
Chinese, English
} fun greetPeople(name: String, lang: Languge) {
// ... 做一些额外的事情
when(lang) {
Chinese -> greetingWithChinese(name)
English -> greetingWithEnglish(name)
}
}

这样是不是拓展性很差,有没有一种可能,我把要调用的函数单做一个参数传进去,我传什么参数,就调用什么方法?

我希望的样子,大概如下:

// 定义 greetPeople 方法
fun greetPeople(name: String, makeGreet: ***) {
makeGreet(name)
}

注意看,这个 *** 所在的这个位置应该是放置参数的类型,那这个应该是什么类型呢,很多编程语言里面有,很遗憾,在 java 里面没有这样的类型。但是 java 我们可以有一种变通方案来实现 —— 接口回调。

用接口包装一下方法,先看一下用 java 如何实现

// 定义一个接口
public interface IGreet {
void makeGreet(String name);
} // 把接口作为参数
public void greetPeople(String name, IGreet: greetMethod) {
greetMethod.makeGreet(name);
} // 调用
greetPeople("Jimy", new IGreet() {
@Override
public void makeGreet(String name) {
greetingWithEnglish("Jimy")
}
}); // java 8 lambda 表达式
greetPeople("Jimy", name -> {
greetingWithEnglish("Jimy")
});

2.2.2 Koltin 函数类型

从上面例子来看,实际上我希望把一个函数当做参数传入一个方法,但是又找不到合适的类型来声明这个参数,幸运的是,Kotlin 提供了这样的类型——函数类型。

在 Kotlin 中,声明函数类型必须遵循一下几点:

Kotlin 中,函数类型声明必须遵循以下几点:

  1. 通过 -> 符号来组织参数类型和返回值类型。左边是参数类型,右边是返回值类型
  2. 必须用一个括号来包裹参数类型
  3. 返回值类型即使是 Unit, 也必须显示声明。
() -> Unit  // 代表无参数,无返回值的函数类型
(Int, String) -> Boolean // 代表第一个参数是 Int 类型,第二个参数是 String 类型,返回值类型是 Boolean 类型的函数类型

在 Kotlin 中 函数是一等公民。

再看上面的例子,我们需要的参数 makeGreet 的类型应该是只有一个参数,类型为 String, 且无返回值的函数类型,如下:

(String) -> Unit

Kotlin 实现上面的代码 :

// 定义 greetPeople 方法
fun greetPeople(name: String, makeGreet: (String) -> Unit) {
makeGreet(name)
} // 调用
greetPeople("Jimy", :: greetingWithChinese)

在上面的定义中,markGreet 是个什么?是个函数类型对象的引用,只有对象才能被作为函数的参数。也就是说,我们需要把一个函数类型的一个具体 “对象” 当做函数参数传递。

这个 makeGreet 被称之为函数引用,Function Reference,这也是 Kotlin 官方的说法。

这也说明,在 Kotlin 中,函数也可以是一个对象。

2.2.3 函数引用

在 Kotlin 里,一个函数名的左边加上双冒号,它就不表示这个函数本身了,而表示一个对象,或者说一个指向对象的引用,但,这个对象可不是函数本身,而是一个和这个函数具有相同功能的对象。

// 这是函数的定义
fun greetingWithChinese(name: String) {
println("早上好, $name !")
} :: greetingWithChinese // 表示函数对象

既然它表示一个函数的对象,那么,我们也可以把它赋值给一个变量。

val a = :: greetingWithChinese
val b = a

所以,我们也可以把这个变量作为函数参数来调用

greetPeople("Jimy", a)
greetPeople("Jimy", b)

甚至,我们还可以直接通过这个引用调用 invoke() 来执行这个函数对象。

(:: greetingWithChinese).invoke("老王")
a.invoke("Jimy")
b("Lucy") // 还可以这样调用,这个是 Kotlin 的语法糖,实际上调用的 invoke 方法

2.2.4 高阶函数

事实上,函数不仅可以当做一个函数的参数,还能作为函数的返回值

定义:一个函数如果参数类型是函数或者返回值类型是函数,那么这就是一个高阶函数。

换个角度看函数类型

fun greetingWithChinese(name: String) {
println("早上好, $name !")
}

如果将这个函数的参数类型和返回值类型,抽象出来,它的函数类型就是 (String) -> Unit,

那么,我们就可以直接声明一个变量,指定这个变量的函数类型是它,

val a = (String) -> Unit = :: greetingWithChinese

注意

前面说了,可以把这个变量,当做参数传递给另一个函数。那么思考一下,有没有一种可能:我把这个函数本身直接挪过来作为参数使用呢?什么意思?

// 这个是之前说的调用方式
greetPeople("Jimy", a)

我能不能像下面这么写?

greetPeople(
"Lucy",
fun greetingWithChinese(name:String) {
println("早上好, $name !")
}
)

如图

咦,编辑器报错了,Anonymous functions with names are prohibited

禁止使用带有名称的匿名函数。提示我把函数名称移除。

greetPeople(
"Lucy",
fun(name: String) {
println("早上好, $name !")
}
)

这样就可以了。这也很好理解,函数名称是给其他地方调用的,当前这种情况下这个函数作为参数,并不会在其它地方调用,这个函数名称也就没有什么作用,不允许有函数名称,这种写法叫做匿名函数。

而匿名函数是一个表达式。本质上是一个函数类型的具体实例对象。

既然是表达式,那也就可以赋值给一个变量:

// 把匿名函数赋值给变量 c
val c = fun(name: String) {
println("早上好, $name !")
} // 将 c 作为参数传递,调用 greetPeople
greetPeople("Lucy", c)

那又有人会问了,能否把一个常规有名字的函数赋值给一个变量。比如

// 编译报错
val d = fun greetingWithChinese(name: String) {
println("早上好, $name !")
}

这是不允许的。

讲道理,我都要把它赋值给一个变量了,那么它本身的名字完全没有必要。

有名字的函数不能赋值给一个变量,但是前面提到了,有名字的函数的引用,用双冒号的形式,则可以。

只有对象才可以赋值给一个变量,仔细想一想,匿名函数,其实不是函数,是一个对象。它是一个表达式,本质上是一个与它形式相同(参数,返回值一致)的函数类型的具体对象。

它和一个函数前面加上双冒号是一样的。

2.3 lambda 表达式

2.3.1 lambda 表达式的概念

对于一个函数类型的变量,我们除了可以把一个函数对象或者一个匿名函数赋值给它,还可以赋值一个 lambda 表达式。

什么是 lambda 表达式?

// 匿名函数
val c = fun(name: String) {
println("早上好, $name !")
} // 等价于下面的 lambda 表达式
{ name: String -> Unit
println("早上好, $name !")
}

一个完整的 Lambda 表达式声明如下:

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

由于 Kotlin 支持类型推导,所以它可以简化为:

val sum: (Int, Int) -> Int = { x, y -> x + y }

或者

val sum = { x: Int, y: Int -> x + y }

这个 lambda 表达式,也是一个函数类型的具体对象。所以也可以当做另一个函数的参数传入。

2.3.2 lambda 表达式的写法演变过程

回到前面的例子,调用 greetPeople() 就可以写为

greetPeople(
"Lucy",
{ name: String -> Unit
println("早上好, $name !")
})

Kotlin 中的函数,如果最后一个参数是 lambda 表达式,则可以将 lambda 表达式写到参数外面:

greetPeople("Lucy") { name: String -> Unit
println("早上好, $name !")
}

就是这么个形式,还有,lambda 表达式支持类型推导,前面定义了函数

fun greetPeople(name: String, makeGreet: (String) -> Unit)

从这个函数定义处,已经知道,调用时 lambda 函数表达式应该是什么样的函数类型,它的参数,返回值类型都确定了,所以可以在 lambda 表达式中省略,如下:

greetPeople("Lucy") { name ->
println("早上好, $name !")
}

再来看 Android 中常用的一个例子, 给 View 设置点击事件:

public interface OnClickListener {
void onClick(View v);
}
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}

java 中调用:

view.setOnClickListener(new OnClickListener() {
@Override
void onClick(View v) {
doSomething();
}
});

到 Kotlin 中就可以这么写:

fun setOnclickListener(onClick: (View) -> Unit) {
this.onClick = onClick
} view.setOnclickListener({view: View -> Unit
doSomething()
})

将 lambda 表达式移到括号外面:

view.setOnclickListener() { view: View -> Unit
doSomething()
}

参数和返回值类型可推导,省略参数和返回值类型:

view.setOnclickListener() {view ->
doSomething()
}

如果 lambda 表达式是唯一参数,则括号都可以直接省略,同时 lambda 表达式如果是单参数,则这个参数也可以直接省略不写。因为对于 Kotlin 的 Lambda,单参数有一个默认的名称:it, 使用的时候用 it 替代

view.setOnclickListener {
doSomething()
}

所以就变成我们经常看到的样子了。

对于多参数的 lambda 表达式,我们不能省略掉参数,但是如果参数没有被用到,可以用 _ 来代替。

2.3.3 lambda 表达式自调用

另外 lambda 表达式也可以进行自调用

{x: Int, y: Int -> x + y}(1, 2)

2.3.4 总结

所以 Kotlin lambda 表达式本质是什么呢?

其实和匿名函数一样,本质上也是一个函数类型的具体对象。它可以作为函数的参数传入,也可以赋值给一个变量。

总结一下

lambda 表达式语法:

  1. lambda 表达式必须通过 {} 来包裹
  2. 如果 lambda 声明了参数部分的类型,且返回值支持类型推导,则 lambda 表达式变量就可以省略函数类型声明
  3. 如果 lambda 变量声明了函数类型,那么 lambda 表达式的参数部分的类型就可以省略
  4. 如果 lambda 表达式返回的不是 Unit 类型,则默认最后一行表达式的值类型就是返回值类型。

lambda 表达式和函数的区别

  1. fun 在没有等号、只有花括号的情况下,就是代码块函数体,如果返回值非 Unit,必须带 return
fun foo(x: Int) {
print(x)
} fun foo(x: Int, y: Int): Int {
return x + y
}
  1. fun 带有等号,没有花括号,是单表达式函数体,可以省略 return
fun foo(x: Int, y: Int) = x + y
  1. 不管是用 val 还是 fun 声明,如果是等号加花括号的语法,就是声明一个 lambda 表达式。
val foo = { x: Int, y: Int ->
x + y
}
// 调用方式: foo.invoke(1, 2) 或者 foo(1, 2) fun foo(x: Int) = { y: Int ->
x + y
}
// 调用方式: foo(1).invoke(2) 或者 foo(1)(2)

三、接口、类与对象

3.1 类

Kotlin 中用 class 或者 object 声明类,如果没有类体,可以省略 {}

区别在于用 object 声明的类是一个单例类,无法被实例化。

Kotlin 多个类可以声明在同一个文件中。

3.1.1 构造函数

在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后。

class Student constructor(id: Int, name: String, age: Int) {

}

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

class Student(id: Int, name: String, age: Int) {
constructor(name: String, age: Int): this(0, name, age) // 次构造函数
}

3.1.2 init 代码块

类中允许存在多个 init 代码块,对于不同的业务初始化可以放在不同的 init 代码块中,按代码顺序依次执行。

类似于 java 类中的静态代码块。

init 代码块中可以直接访问构造函数的参数,其它地方不可以。

3.1.3 成员变量

声明

  1. 和 java 类似,可以在类代码块中直接声明。
  2. 构造方法参数加上 var 或者 val 修饰,则可以视为类中声明了一个同名的成员变量。也可以加上可见性修饰符。

初始化

成员变量声明时需进行初始化,或者显式声明延迟初始化,使用关键字 lateinit 显示声明延迟初始化

class Student(id: Int, name: String, var age: Int) {
constructor(name: String, age: Int): this(0, name, age) // init 代码块中访问构造函数的参数
init {
println("student name is $name")
} fun printInfo() {
age += 1
} lateinit var gender: String // 延迟初始化
}

3.1.4 继承

使用

open class Person(id: Int, name: String)

class Student(id: Int, var name: String, var age: Int): Person(id,name) {
// ...
}

被继承的类需要用 open 修改,同样父类的中法,默认是不可被重写的,若允许被重写,需要使用 open 修饰。

3.2 接口

interface MyInterface {
fun bar()
val propertyWithImplementation: String
get() = "foo" fun foo() {
print(prop)
}
}

接口允许有属性,接口中的方法允许有默认实现,接口不需要用 open 修饰,因为接口就是用来让子类实现,然后被被子类重写的。包括属性也是可以被重写的。接口可以继承自其它接口。

3.3 数据类

data class Student(val name: String, var age: Int = 18)
  • 自动生成 getter()/setter() 方法 !!!???
  • 编译器自动生成 equals()/hashCode() 方法
  • 编译器自动生成 toString() 方法
  • 编译器自动生成 `componentN()`` 函数(解构)
  • 编译器自动生成 copy 函数
data class Student(val name: String, var age: Int = 18) {
var isBoy = true // 该属性不会在生成的方法中
}

3.4 内部类

java 中在类中再声明类,称之为内部类,如果使用 static 修饰,则为静态内部类。

class A {
private String a class B { } static class C { }
}

问题:请问 B 和 C 中能直接访问到 A 的成员 a 吗?

在 Kotlin 中没有 static 关键字,在类中默认声明的就是静态内部类,如要声明内部类,需要添加 inner 关键字

class A {
// 声明内部类需要使用 inner 关键字
inner class B { } // 静态内部类
class c { }
}

3.5 密封类

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

密封类可以用来代替枚举,使用 When 表达式时如果能覆盖所有,则无需 else 分支。

fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}

3.6 伴生对象

Kotlin 中没有了 static 关键字,但是有伴生对象的概念,类似于 java 中的 static。

class Student {
companion object {
const val TAG = "Student" // 常量
var count = 0 // 静态变量 // 静态方法
fun test() { }
}
}

object 是在 Kotlin 中的用法。

  1. 在类中与 companion 一起,表示伴生对象。
  2. 声明类。Kotlin 中除了用 class 声明类以外,还可以用 object 来声明一个类,用 object 声明的类,天生单例。
object SystemUtils {
...
}
  1. 实现某个接口,或者抽象类:
interface ICount {
fun count(num: Int): Int
} val myCount = object: ICount {
override fun count(num: Int): Int {
TODO("Not yet implemented")
}
}

3.7 可见性修饰符

  1. public 公开,可见性最大,哪里都可以引用
  2. private 私有,可见性最小,根据声明位置可以分为类中可见和文件中可见。
  3. protected 保护, 相当于 private + 子类可见。
  4. internal 内部,同一 module 可见

java 中 protected 表示包内可见 + 子类可见

Kotlin protected 的可见范围收窄了,原因是 相比于包, Kotlin 更注重 module。

另外 private 修饰 java 中的内部类对外部类可见,Kotlin 中的内部类对外部类不可见。

Kotlin 中的类和方法,不写时,默认是 public + final 的。

四、 Kotlin 的一些其它特性

4.1 函数参数默认值

fun sayHi(name: String = "world") = println("Hello, " + name)

重载函数再也不用写很多个了。

4.2 本地函数(嵌套函数)

本地函数是个啥玩意?

我们知道在函数中可以声明局部变量(这不是废话吗?)在 Kotlin 中我们甚至还可以在方法中类似声明局部变量一样声明一个方法,称之为本地函数。

fun login(user: String, password: String, illegalStr: String) {
// 验证 user 是否为空
if (user.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
// 验证 password 是否为空
if (password.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
// 执行登录
}

校验参数这不部分代码有些冗余,我们可以抽成一个方法,但是我们又没有必要暴露到其它地方,因为只有这个 login 方法会用到,则可以在 login 方法内部声明一个嵌套函数:

fun login(user: String, password: String, illegalStr: String) {
fun validate(value: String, illegalStr: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
}
validate(user, illegalStr)
validate(password, illegalStr)
// 执行登录
}

4.3 try-catch 表达式

Kotlin 中 try-catch 语句也可以是一个表达式,允许代码块的最后一行作为返回值

val a: Int? = try {
parseInt(input)
} catch (e: NumberFormatException) {
null
}

4.4 == 和 ===

Kotlin 中用 == 判断 equals 相等,对象内容相等

=== 判断相等引用的内存地址相等

4.5 拓展函数和拓展属性

顾名思义,可以给一个类“增加”额外的方法和属性

举个例子:

data class Student(val name: String)

这个类本身没有“上课”这个方法,我现在给它增加一个拓展函数。

fun Student.attendClass() {
println("${this.name} 正在上课")
}

这样,Student 这个类的实例对象就可以调用这个拓展方法了。

val student = Student("Jimy")
student.attendClass()

拓展方法直接定义在文件中。

拓展属性类似。

想一想:三方库里面的类,不能直接修改,是不是可以增加拓展方法和拓展属性了?有没有很激动。

4.6 Top-level

kotlin 文件已 .kt 为后缀,在 Kotlin 文件中,我们可以直接声明的变量,方法,也就是不在 class 内部声明,称之为 top-level declaration 顶层声明。

// 属于 package,不在 class/object 内
const val KEY = "123456" fun test() {
println("---test---")
}

它不属于任何 class , 而是直接属于 package, 它和静态变量一样是全局的,使用起来更方便,调用它的时候连类名都不用写:

import com.chongjiu.demo.test
import com.chongjiu.demo.KEY test()
val x = KEY

结合前面说的,写工具类一般就有三种方式:

// companion object
class Util {
companion object {
fun test1() { }
fun test2() { }
}
} // object
object Util {
fun test1() { }
fun test2() { }
} // top level 方法
fun test1() {
}
fun test2() {
}

建议:

  • 如果想写工具类的功能,直接创建文件,写 top-level「顶层」函数。虽然没有 class 的概念,但是相关的方法,写在同一个文件中,方便阅读管理。
  • 如果需要继承别的类或者实现接口,就用 object 或 companion object。

4.7 by lazy (委托)

前面提到,如果一个属性需要延迟初始化,可以使用 lateinit 进行修饰,另外还有另外一种方式,就是使用 by lazy 方法初始化。

在给一个变量赋值的时候使用 by lazy 代码块,可以做到单例,在第一次使用时初始化。by lazy 实际上使用了 Kotlin 的委托特性,底层原理和 DCL 的单例模式类似。

val student: Student by lazy {
Student(name = "Lucy", age = 18)
}

这里顺便说一下用 Kotlin 实现单例,看看有多有多方便了。

前面提到,用 object 声明一个类,即是单例的。

object Singleton

再看看 DCL 的方式,java 实现:

public class Singleton {
private Singleton(){} private volatile static Singleton instance; private static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

再看看 Kotlin 实现

class Singleton private constructor() {
companion object {
val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Singleton()
}
}
}

这样是不是很方便,当然上述方式也有一个缺点,就是获取单例的时候没有办法传参,那么改为和 java 一样的方式看看:

class Singleton private constructor() {
companion object {
@Volatile
private var instance: Singleton? = null fun getInstance(): Singleton {
return instance?: synchronized(this) {
instance?:Singleton().also {
// 初始化工作
instance = it
}
}
}
}
}

这次可以传参进来了,代码依然简洁很多。

五、 More

Kotlin 其它内容还有很多:

数组

集合(可变集合、不可变集合、集合操作符)

协程

泛型(泛型类、泛型函数、逆变和协变)

注解

反射

委托

内联函数、中缀函数等等

...

Kotlin 基础入门的更多相关文章

  1. Kotlin基础入门之必知必会,查漏补缺来一手~~~

    数据类型 Kotlin跟 java 相同,基本数据类型有八种 boolean,char,int,short,long,float,double,byte 类型 位宽 最小值 最大值 Short 16 ...

  2. Kotlin基础(一)Kotlin快速入门

    Kotlin快速入门 一.函数 /* * 1.函数可以定义在文件最外层,不需要把它放在类中 * 2.可以省略结尾分号 * */ fun main(args: Array<String>) ...

  3. kotlin面向对象入门

    之前在学kotlin基础语法时咱们是采用三方jar包在eclipse工程下进行的,很显然这工具在实际商用中基本上很少用到了,最终是要编写android程序,所以说从这里起得更换一个更加智能更加贴近实际 ...

  4. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  5. 「译」JUnit 5 系列:基础入门

    原文地址:http://blog.codefx.org/libraries/junit-5-basics/ 原文日期:25, Feb, 2016 译文首发:Linesh 的博客:JUnit 5 系列: ...

  6. .NET正则表达式基础入门

    这是我第一次写的博客,个人觉得十分不容易.以前看别人写的博客文字十分流畅,到自己来写却发现十分困难,还是感谢那些为技术而奉献自己力量的人吧. 本教程编写之前,博主阅读了<正则指引>这本入门 ...

  7. 从零3D基础入门XNA 4.0(2)——模型和BasicEffect

    [题外话] 上一篇文章介绍了3D开发基础与XNA开发程序的整体结构,以及使用Model类的Draw方法将模型绘制到屏幕上.本文接着上一篇文章继续,介绍XNA中模型的结构.BasicEffect的使用以 ...

  8. 从零3D基础入门XNA 4.0(1)——3D开发基础

    [题外话] 最近要做一个3D动画演示的程序,由于比较熟悉C#语言,再加上XNA对模型的支持比较好,故选择了XNA平台.不过从网上找到很多XNA的入门文章,发现大都需要一些3D基础,而我之前并没有接触过 ...

  9. Shell编程菜鸟基础入门笔记

    Shell编程基础入门     1.shell格式:例 shell脚本开发习惯 1.指定解释器 #!/bin/bash 2.脚本开头加版权等信息如:#DATE:时间,#author(作者)#mail: ...

  10. [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.

    前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...

随机推荐

  1. [转帖]Nginx 性能测试

    https://plantegg.github.io/2022/10/10/Nginx%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/ 压测工具选择 wrk ,apache ...

  2. HanLP — 感知机(Perceptron) -- Python

    HanLP - 感知机(Perceptron) 感知机 感知机是根据输入实例的特征向量 x 对其进行二类分类的线性模型: \[f(x)=sign(w\cdot x+b) \] 感知机模型对应于输入空间 ...

  3. SAP FICO 前台财务过账、预制功能分开

    最近遇到一个变态要求,FB01 等涉及过账功能 要求根据'权限'判断用户是否有过账的功能.以下实现会有遗漏场景: 实现:hide 'SAVE'按钮 (ok_code = 'BU'). 根据状态栏设置' ...

  4. 【实践篇】最全的【DDD领域建模】小白学习手册(文末附资料)

    导读 DDD领域建模被各个大小厂商提起并应用,而每个人都有自己的理解,本文就是针对小白,系统地讲解DDD到底是什么,解决了什么问题,及一些建议和实践.本文主要是思想的一种碰撞和分享,希望能对朋友们有所 ...

  5. vue3封装搜索表单组件

    seacrch 表单完成的功能 1.根据配置json配置项自动生成表单 ok 2.是响应式的排版 ok 3.点击搜索按钮会向上抛出值 ok 4.点击重置按钮会自动清空数据,不需要父组件额外的处理 ok ...

  6. Vue3中readonly 和 shallowReadonly和toRaw

    1.readonly 深度只读 被readonly包裹的数据只能够读取. 是一个深度只读,不能够修改. 我们看一下面的代码. 我们想修改值,但是修改后视图无响应. 并且控制台警告目标为只读 reado ...

  7. vue动画appear 实现页面刚展示出来的时候,入场效果

    <style> /* 给动画添加一组过度效果 */ .v-enter, .v-leave-to { opacity: 0; transform: translateY(80px); } . ...

  8. vuex标准化看这篇文章就够了~

    1.新建一个store文件夹,新建index.js文件,内容如下: import Vue from 'vue' import Vuex from 'vuex' import state from '. ...

  9. Postman 简单使用随笔记

    1.要先使用Postman post请求,返回token,否则提示未登陆系统,下图为发送请求后的接口返回的信息 2.为方便,每次在访问接口时都要访问权限,所以将其作为局部的环境变量,设置如下: 3.要 ...

  10. vs code python(Pylance server) crash

    The Pylance server crashed 5 times in the last 3 minutes. The server will not be restarted. See the ...