Kotlin 基础入门

这篇具有很好参考价值的文章主要介绍了Kotlin 基础入门。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 一、基础语法
    • 1.1 常见数据类型
    • 1.2 变量
      • 1.2.1 变量声明
      • 1.2.2 类型推断
      • 1.2.3 Null 安全
      • 1.2.4 面向对象语言
    • 1.3 流程控制
      • 1.3.1 if 表达式
      • 1.3.2 When 表达式
      • 1.3.3 For 循环
      • 1.3.4 While 循环
  • 二、函数与 lambda 表达式
    • 2.1 函数声明
    • 2.2 函数类型
      • 2.2.1 示例引入
      • 2.2.2 Koltin 函数类型
      • 2.2.3 函数引用
      • 2.2.4 高阶函数
    • 2.3 lambda 表达式
      • 2.3.1 lambda 表达式的概念
      • 2.3.2 lambda 表达式的写法演变过程
      • 2.3.3 lambda 表达式自调用
      • 2.3.4 总结
  • 三、接口、类与对象
    • 3.1 类
      • 3.1.1 构造函数
      • 3.1.2 init 代码块
      • 3.1.3 成员变量
      • 3.1.4 继承
    • 3.2 接口
    • 3.3 数据类
    • 3.4 内部类
    • 3.5 密封类
    • 3.6 伴生对象
    • 3.7 可见性修饰符
  • 四、 Kotlin 的一些其它特性
    • 4.1 函数参数默认值
    • 4.2 本地函数(嵌套函数)
    • 4.3 try-catch 表达式
    • 4.4 == 和 ===
    • 4.5 拓展函数和拓展属性
    • 4.6 Top-level
    • 4.7 by lazy (委托)
  • 五、 More

一、基础语法

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 !")
    }
)

如图
Kotlin 基础入门

咦,编辑器报错了,Anonymous functions with names are prohibited
禁止使用带有名称的匿名函数。提示我把函数名称移除。

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

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

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

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

// 把匿名函数赋值给变量 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 表达式写到参数外面:
Kotlin 基础入门

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 其它内容还有很多:
数组
集合(可变集合、不可变集合、集合操作符)
协程
泛型(泛型类、泛型函数、逆变和协变)
注解
反射
委托
内联函数、中缀函数等等
...文章来源地址https://www.toymoban.com/news/detail-700918.html

到了这里,关于Kotlin 基础入门的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringMVC零基础入门 - 概述、入门搭建、PostMan的使用(常见数据类型的传输)、REST风格编程

    SpringMVC是隶属于Spring框架的一部分,主要是用来 进行Web开发 ,是 对Servlet进行了封装 SpringMVC是处于 Web层 的框架,所以其主要的作用就是用来 接收前端发过来的请求和数据 然后经过处理并将处理的 结果响应给前端 ,所以如何处理 请求 和 响应 是SpringMVC中非常重要的一块内

    2024年01月19日
    浏览(47)
  • Kotlin基础学习-入门篇

    本篇文章来自郭霖大佬的第一行代码中的 Kotlin 教程,笔者只是在学习 Kotlin 过程中记录学习过程。 Kotlin系列已更新: Kotlin基础学习-入门篇 Kotlin基础学习-第二篇 Kotlin进阶学习-第三篇 Kotlin进阶学习-第四篇 Kotlin进阶学习-第五篇 Kotlin由 JetBrains 公司开发设计,2011年公布第一版

    2024年01月16日
    浏览(33)
  • Kotlin 基础入门

    目录 一、基础语法 1.1 常见数据类型 1.2 变量 1.2.1 变量声明 1.2.2 类型推断 1.2.3 Null 安全 1.2.4 面向对象语言 1.3 流程控制 1.3.1 if 表达式 1.3.2 When 表达式 1.3.3 For 循环 1.3.4 While 循环 二、函数与 lambda 表达式 2.1 函数声明 2.2 函数类型 2.2.1 示例引入 2.2.2 Koltin 函数类型 2.2.3 函数引用

    2024年02月09日
    浏览(22)
  • Kotlin基础入门 - for、forEach 循环

    不论身处何方 for循环这种操作都随处可见,鉴于大多数Android开发都是从Java转到Kt的,所以我的思路是从Java的使用习惯来讲一些Kt 的for、forEach 循环方式 基础 for循环 for循环 一般作用于 list、map数据集合 ,这里我直接创建了一个只读List集合 提前了解 惯性 for循环 所谓的惯性

    2024年01月16日
    浏览(30)
  • 【Python 零基础入门】基础语法

    当我们学习一门新语言, 首先要熟悉它的语法规则. 这就如同学习一门外语, 我们需要知道句子的结构, 词汇的使用和语法的规则. 与 Java 中的 “{}” 不同, Python 使用缩进. 缩进在 Python 中非常重要, 定义了代码的结构和层次. 通常用 4 个空格作为标准的缩进 (TAP 键). 在我们编写代

    2024年02月04日
    浏览(33)
  • Kotlin系列一(快速入门,kotlin的数据类型)

    kontlin可以写脚本 创建一个\\\"script.kts\\\"文件 编写脚本内容 使用 kotlinc -script script.kts 命令运行kotlin脚本 创建一个文件\\\"HelloWorld.kt\\\" 编写内容 使用 kotlinc HelloWorld.kt 编译kotlin代码 使用 kotlin HelloWorld Kt运行编译后的字节码 使用\\\"var\\\"定义变量,变量定义后值可以改变,且可以在定义

    2024年02月03日
    浏览(32)
  • C++(1) —— 基础语法入门

    一、C++初识 1.1 第一个C++程序 1.2 注释  1.3 变量 1.4 常量 1.5 1.6 标识符命名规则 二、数据类型 2.1 整型 2.2 sizeof 2.3 实型(浮点型) 2.4 字符型 2.5 转义字符 2.6 字符串型 2.7 布尔类型 bool 2.8 数据的输入 三、运算符 3.1 算术运算符 3.1.1 加减乘除运算  3.1.2 取模运算

    2024年01月18日
    浏览(78)
  • Unity入门基础语法

    transform.position 世界坐标 transform.localPosition  相对坐标 设置物体的坐标: this.transform.localPosition = new Vector3(1.5f, 0, 2.0f); Update(),称为帧更新 此方法会被游戏引擎定时调用,已更新游戏的状态 Time.time 游戏启动的时间 Time.deltaTime 距上次帧更新的时间差 Unity不支持固定帧率,但可

    2024年02月03日
    浏览(75)
  • IDAPython入门基础语法

    IDAPython入门教程 基于IDA7.5_Python3 第一讲 简介与地址获取 IDAPython拥有强大的功能,在使用IDA分析程序时非常有用,可以简化许多操作例如花指令的特征码匹配修改 学习IDAPython需要了解一点Python语言的基本知识以及查询IDAPython文档 IDAPython官方文档: IDAPython documentation 直接在搜索框

    2024年02月01日
    浏览(34)
  • 【Python入门】Python基础语法

    前言 📕作者简介: 热爱跑步的恒川 ,致力于C/C++、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于Python零基础入门系列,本专栏主要内容为Python基础语法、判断、循环语句、函数、函数进阶、数据容器、文件操作、异常模块与包、数据可视化等,

    2024年02月03日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包