Kotlin
简介
1. Kotlin
Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,由 JetBrains 设计开发并开源。
2. 优点:
- 简洁: 大大减少样板代码的数量;
- 安全: 几乎杜绝了空指针这个全球崩溃率最高的异常;
- 互操作性: 充分利用JVM、Android、浏览器现有库;
- 工具友好: 可用任何 Java IDE 或者使用命令行构建。
3. Java转Kotlin方式
Code -> Convert Java File to Kotlin File (Android studio最后一行)
4. Kotlin使用命令行编译
- Kotlin 命令行编译工具下载地址:
https://github.com/JetBrains/kotlin/releases/tag/v1.1.2-2
可以选择一个最新的稳定版下载。下载完成后,解压到指定目录,然后将 bin 目录添加到系统环境变量。bin 目录包含编译和运行 Kotlin 所需的脚本。
- 在 OS X、Linux、Cygwin、FreeBSD 和 Solaris 系统上也可以使用更简单的安装方法,命令如下:
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh
sdk version
sdk install kotlin
kotlin -version
5. 编译及运行
- 使用 Kotlin 编译器编译应用:
$ kotlinc hello.kt -include-runtime -d hello.jar
-d
: 用来设置编译输出的名称,可以是 class 或 .jar 文件,也可以是目录。-include-runtime
: 让 .jar 文件包含 Kotlin 运行库,从而可以直接运行。
如果你想看所有的可用选项,运行:
$ kotlinc -help
- 运行应用
$ java -jar hello.jar
函数及表达式
1. 函数定义:
函数定义使用关键字 fun,参数格式为:参数 : 类型 (与Java代码顺序相反,并加个冒号)。
//返回类型为Int的函数
fun sum(a: Int, b: Int): Int { //前两个Int为参数类型,最后为输出类型
return a + b
}
//无返回值的函数(类似Java中的void)
fun Sum(a: Int, b: Int): Unit {
print(a + b)
}
// 如果是返回Unit类型,则可以省略(对于public方法也是这样):
public fun Sum(a: Int, b: Int) {
print(a + b)
}
表达式作为函数体时,返回类型自动推断,但若方法为Public,则必须明确返回类型。
fun sum(a: Int, b: Int) = a + b
public fun sum(a: Int, b: Int): Int = a + b
2. 可变长度参数函数
函数的变长参数可以用 "vararg"
关键字进行标识:
fun vars(vararg v: Int){
for(t in v){
print(t)
}
}
3. 匿名函数
fun main(args: Array<String>) {
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
println(sumLambda(1,2)) // 输出 3
}
//即 val 函数名:(输入参数类型)-> 输出参数类型 = {参数 -> 结果}
4. 常量和变量的定义
分为可变常量和不可变常量(类似Java中的final关键字修饰的常量)
val <标识符> : <类型> = <初始化值> //可变常量
例: val a:Int = 1
例: val b = 2 //系统自行判断变量类型为Int
例: val c:Int //仅定义不赋值则必须提供变量类型
var <标识符> : <类型> = <初始化值> //不可变常量
🎯 注意:var
和val
最大的区别就是是否设置了get
和set
,我们都知道var同时有get
和set
,但是在val
中只有get
。
5. 字符串$的用法
$
表示一个变量名或者变量值;$varName
表示变量值;${varName.fun()}
表示变量的方法返回值。 (和Shell脚本有点类似)
6. NULL的机制
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式:
- 字段后加
!!
像Java一样抛出空异常 - 字段后加
?
可不做处理返回值为 null 或配合 ?: 做空判断处理。
//类型后面加?表示可为空
var age: String? = "23"
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回自定义的值
val ages2 = age?.toInt() ?: (为空后的代码块)
// 若函数的返回值可能为NULL则写成
fun sum(a: Int, b: Int): Int? {
...
}
7. 类型转换及检测
使用 is
运算符检测一个表达式是否为某类型的一个实例(类似于Java中的instanceof
关键字)。
fun getStringLength(obj: Any): Int? {
if (obj is String) {
return obj.length
}
return null
}
//从第三行开始,obj会被系统自动转换为String类型
8. 区间表示
- 区间表达式由具有操作符形式
..
的rangeTo
函数辅以in
和!in
形成:
if(i in 1..7)
等同于if(1 <= i && i <= 7)
; - 使用step指定步长:
for (i in 1..7 step 2) print(i) // 输出 1 3 5 7
- 使用downTo实现降序:
for (i in 4 downTo 1 step 2) print(i) // 输出 4 2
- 使用until函数排除边界元素:
for (i in 1 until 10) { // i in [1, 10) 排除了 10
数据类型
1. 基本数据类型
2. 字面常量(和Java类似)
- 十进制:123
- 长整型以大写的 L 结尾:
123L
- 16 进制以 0x 开头:
0x0F
- 2 进制以 0b 开头:
0b00001011
- 注意:8进制不支持
Kotlin 同时也支持传统符号表示的浮点数值: - Doubles 默认写法:
123.5, 123.5e10
- Floats 使用 f 或者 F 后缀:
123.5f
此外,数字常量还可以加下划线使他更易读
val oneMillion = 1_000_000
- 数值的比较
Kotlin 中没有基础数据类型,只有封装的数字类型,你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样可以保证不会出现空指针。因此比较两个数字类型的时候就要区分比较数值还是比较地址。三个等号===
是比较地址,两个等号==
是比较数值。
4. 类型转换
🎯 注意:Java是支持较小类型到较大类型的隐式转换的,而Kotlin不支持隐式转换。
Kotlin要将较小的类型转换为较大的类型需要使用一些数据类型转化方法来完成:
- toByte()
- toShort()
- toInt()
- toLong()
- toFloat()
- toDouble()
- toChar()
5. 位操作符
a.shl(bits) – 左移bits位 (Java中为 a<<bits )
a.shr(bits) – 右移bits位 (Java中为 a>>bits)
a.ushr(bits) – 无符号右移bits位 (Java中为 a>>>bits)
a and b – 与 (Java中为 & )
a or b – 或 (Java中为 | )
a xor b – 异或 (Java中为 ^ )
a.inv() – 反向 (Java中为 ~ )
此外,Boolean类型的比较和Java类似,即 ||, &&, !
6. 数组
数组用类 Array 实现,并且还有一个 size
属性及 get
和 set
方法。数组的创建两种方式:
- 使用函数
arrayOf()
; - 使用工厂函数。
val a = arrayOf(1, 2, 3) //[1,2,3]
//根据数组的索引值,运算后的结果为数组元素
val b = Array(3, { i -> (i * 2) }) //[0,2,4]
// 指定长度为 3 的整型数组,初始化值为 null
val arr3 = arrayOfNulls<Int>(3)
// 创建类型为 String 的空数组
val emptyArr = emptyArray<String>()
// get 和 set 方法
println(arr1[1])
println(arr2.get(0)) // 和使用 [] 相同
arrInt[0] = -1 // 修改第一个数为-1
arrInt.set(0, -1) // 功能同上
数组常用方法:
// 复制数组,长度为4,超出的部分填充 null
val copy = a.copyOf(4)
//打印数组
copy.contentToString() // [1, 2, 3, null]
// 获取数组的最大值和最小值
val max = arr1.maxOrNull()
val min = arr1.minOrNull()
// 获取数值型数组的平均值
val average = arr1.average()
除了类Array
,还有ByteArray
, ShortArray
, IntArray
,用来表示各个类型的数组,省去了装箱操作,因此效率更高。
🎯 注意:与Java不同,Kotlin中数组是不协变(invariant)
。
补充:数组的协变性(covariant)
是指如果类Base是类Sub的基类,那么Base[]
就是Sub[]
的基类。而泛型是不协变的(invariant)
,List<Base>
不会是List<Sub>
的基类,更不会是它的子类。
7. 字符串
和 Java 一样,String 是不可变的。方括号 []
语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环
来遍历。
🎯 注意:与Java不同,Kotlin 支持三个引号"""
扩起来的字符串,支持多行字符串。
val text = """
多行字符串
多行字符串
"""
然而,此时输出有一些前置空格,若想删除多余空格可以通过如下函数:
fun String.trimMargin(marginPrefix: String = "|"): String
其功能为:从源字符串的每一行中修剪前导空白字符,后跟marginPrefix
(不写,默认为|
),如果第一行和最后一行为空白,则删除它们。如果一行不包含marginPrefix(除了第一个和最后一个空白行)
,则不会影响该行。
写法如下:
val text = """
|多行字符串
|菜鸟教程
|多行字符串
|Runoob
""".trimMargin()
8. 字符串模板
字符串可以包含模板表达式 ,会求值并把结果合并到字符串中。 模板表达式以美元符$
开头,具体$
的用法和上一章 “字符串$的用法” 下的三种用法类似。
举例:
val s = "runoob"
val str = "$s.length is ${s.length}"
// $varName 表示变量值
// ${varName.fun()} 表示变量的方法返回值
// 求值结果为 "runoob.length is 6"
🎯 注意:原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $
字符(它不支持反斜杠转义)。
举例
val price = """
${'$'}9.99
"""
// 求值结果为 $9.99
val price = """
'\t'9.99
"""
// 求值结果为 '\t'9.99
val price = """
${'\t'}9.99
"""
// 求值结果为 9.99 (9.99前面有Tab缩进的空格)
控制语法
1. IF表达式
传统用法和Java一样。但作为表达式使用时略有区别;
Kotlin: val c = if (condition) a else b
Java: val c = (condition)? a : b
此外对于表达式使用方式,若赋值的过程中需要进行别的操作,可用:
Kotlin: val c = if (condition) {
a
....
} else {
b
....
}
2. When表达式
when
将它的参数和所有的分支条件顺序比较,直到某分支满足条件。(类似于switch
)
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 即 switch下的 default
print("x 不是 1 ,也不是 2")
}
}
此外也可以根据条件判断(与Java类似)
when (x) {
in 1..10 -> print("x is in the range")
in -1,0 -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
使用 in 运算符来判断集合内是否包含某实例
val items = setOf("apple", "banana", "kiwi")
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
3. For循环
与Java类似,
for (item: Int in ints) {
// ……
}
// 通过索引遍历一个数组或者一个 list:
for (i in array.indices) { //返回此集合的有效索引的 IntRange。
print(array[i])
}
// 注意这种"在区间上遍历"会编译成优化的实现而不会创建额外对象。
4. while与do…while循化
与Java相同,不再叙述。
5. 返回和跳转
1. kotlin 有三种结构化跳转表达式:(与Java类似)
return
。默认从最直接包围它的函数或者匿名函数返回。break
。终止最直接包围它的循环。continue
。继续下一次最直接包围它的循环。
2. Break 和 Continue 标签 (与Java类似)
使用标签的唯一理由就是因为由循环嵌套的存在,而且你想要从多层嵌套循环中break
或者continue
。
类与对象
1. 类定义(和Java相同)
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
class Runoob { // 类名为 Runoob
// 大括号内是类体构成
}
//我们也可以定义一个空类:
class Empty
//可以在类中定义成员函数:
class Runoob() {
fun foo() { print("Foo") } // 成员函数
}
2. 类的属性
类的属性可以用关键字var
声明为可变的,否则使用只读关键字val
声明为不可变。
class Runoob {
var name: String = ...
var url: String = ...
var city: String = ...
}
像使用普通函数那样使用构造函数创建类实例时,要注意Kotlin 中没有new
关键字。
val site : Runoob = Runoob()
需要使用属性时,直接site.name
, site.url
, site.city
。
kotlin有Java所不具备的可以给每个属性变量设置一个get()
和set()
默认方法:([]
为可选)
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
- 对于
set()
默认方法只有在属性变量被赋值的时候才会调用该方法。 - 对于
get()
默认方法只有在获取(使用)属性变量的时候才会调用该方法。
举例:
var no: Int = 100
get() = field // 后端变量
set(value) {
if (value < 10) { // 如果传入的值小于 10 返回该值
field = value
} else {
field = -1 // 如果传入的值大于等于 10 返回 -1
}
}
// person.no = 9 -> no:9
// person.no = 20 -> no:-1
举例:
class PersonBean {
var name: String = ""
get() {
if (field.length > 1) {
return field
} else {
return "name invalid"
}
}
set(value) {
if (value.length > 1) {
field = value
} else {
field = ""
}
}
var lastName: String = "zhang"
get() = field.toUpperCase() // 将变量赋值后转换为大写
set
}
由于getter()
和setter()
是自动生成的,一般不需要去添加,但是需要自定义返回结果时,需要注意,不能使用属性的名称。kotlin
在创建一个属性时,会自动生成一个field
,用来代替属性名。(getter()和setter()跟着对应属性变量的定义,filed即对应上方的属性名)
- name
代表的含义是getName()
,
- name=
代表的含义是setName(String name)
🎯 注意:在自定义getter
时,使用name
,和自定义setter
时,使用name=
,就会循环调用getter
和setter
,造成内存溢出,而使用field
就可以避免这个问题。
此外,非空属性必须在定义的时候初始化。kotlin
还提供了一种可以延迟初始化的方案,使用lateinit
关键字描述属性。
3. 主构造器
主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init
关键字作为前缀。
class Person constructor(firstName: String){
init {
println("FirstName is $firstName")
}
}
4. 次构造器
类也可以有二级构造函数,需要加前缀 constructor
:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this
关键字:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public
。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class DontCreateMe private constructor () {
}
5. 抽象类
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract
的。抽象成员在类中不存在具体的实现。
abstract class Lanauage{
val TAG = this.javaClass.simpleName // 自身的属性
// 自身的函数
fun test() : Unit{
// exp
}
abstract var name : String // 抽象属性
abstract fun init() // 抽象方法
}
/**
* 抽象类Lanauage的实现类TestAbstarctA
*/
class TestAbstarctA : Lanauage(){
override var name: String
get() = "Kotlin"
set(value) {}
override fun init() {
println("我是$name")
}
}
// 测试
val mTestAbstarctA = TestAbstarctA()
println(mTestAbstarctA.name)
mTestAbstarctA.init()
// 结果
Kotlin
我是Kotlin
6. 嵌套类
我们可以把类嵌套在其他类中。
举例:
class Outer { // 外部类
private val bar: Int = 1
class Nested { // 嵌套类
fun foo() = 2
}
}
fun main(args: Array<String>) {
// 调用格式:外部类.嵌套类.嵌套类方法/属性
val demo = Outer.Nested().foo()
println(demo) // == 2
}
7. 内部类
内部类使用 inner 关键字来表示。
举例:
class Outer {
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner {
fun foo() = bar // 访问外部类成员
fun innerTest() {
var o = this@Outer //获取外部类Outer的成员变量
println("内部类可以引用外部类的成员,例如:" + o.v)
}
}
}
fun main(args: Array<String>) {
val demo = Outer().Inner().foo()
println(demo) // 1
val demo2 = Outer().Inner().innerTest()
println(demo2) // 内部类可以引用外部类的成员,例如:成员属性
}
8. 匿名内部类
class Test {
var v = "成员属性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
9. 类的修饰符
类的修饰符包括 classModifier
和accessModifier
。
classModifier
: 类属性修饰符,标示类本身特性。- abstract // 抽象类
- final // 类不可继承 (默认)
- enum // 枚举类
- open // 类可继承
- annotation // 注解类
accessModifier
: 访问权限修饰符- private // 仅在同一个文件中可见
- protected // 同一个文件中或子类可见
- public // 所有调用的地方都可见 (默认)
- internal // 同一个模块中可见
其中,模块可以理解为一组Kotlin文件,它们一起编译为一个库或一个独立的应用程序。
继承
1. kotlin继承
Kotlin 中所有类都继承该Any
类,则它是所有类的超类(Java中称为顶级父类),对于没有超类型声明的类是默认超类。
🎯 注意:父类中声明的private
的属性和方法子类中不能直接获取,但是仍然可以获取到父类中已经私有的属性和方法。
解释:子类能获取直接父类的父类中的结构,能获取父类中private
权限的属性或方法,子类继承超类的一切,只不过看不到private
修饰的内容,但可以通过调用父类本身的方法来获取和利用。
如果一个类要被继承,可以使用 open 关键字进行修饰。
open class Base(p: Int) // 定义基类(具有共性功能的类)
class Derived(p: Int) : Base(p)
2. 构造函数
- 如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
open class Person(var name : String, var age : Int){// 基类
}
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
}
val s = Student("Runoob", 18, "S12346", 89)
- 如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。
class Student : Person {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet)
: super(ctx,attrs) {
}
}
举例:
/**用户基类**/
open class Person(name:String){
/**次级构造函数**/
constructor(name:String,age:Int):this(name){
//初始化
println("-------基类次级构造函数---------")
}
}
/**子类继承 Person 类**/
class Student:Person{
/**次级构造函数**/
constructor(name:String,age:Int,no:String,score:Int)
:super(name,age){
println("-------继承类次级构造函数---------")
println("学生名: ${name}")
println("年龄: ${age}")
println("学生号: ${no}")
println("成绩: ${score}")
}
}
fun main(args: Array<String>) {
var s = Student("Runoob", 18, "S12345", 89)
}
/* 结果
-------基类次级构造函数---------
-------继承类次级构造函数---------
学生名: Runoob
年龄: 18
学生号: S12345成绩: 89
*/
3. 重写
在基类中,使用fun
声明函数时,此函数默认为final
修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open
修饰它, 子类重写方法使用 override
关键词。
/**用户基类中的方法**/
open fun study(){ // 允许子类重写
println("我毕业了")
}
/**子类继承 Person 类,并重写其方法**/
override fun study(){ // 重写方法
println("我在读大学")
}
🎯 注意:子类重写父类方法时,访问级别可以扩大但不能缩小。(其他补充:子类中重写的方法抛出的异常应该为父类中方法的子异常或相同异常,并且抛出的异常种类不能多于父类中的方法。)
如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super
范型去选择性地调用父类的实现。(Java:通过super.方法名()
指定父类,多继承存在相同方法名即可用类.super.方法名()
进行指定。默认是重写第一个继承的类下的方法)
open class A {
open fun f () { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } //接口的成员变量默认是 open 的
fun b() { print("b") }
}
class C() : A(), B{
override fun f() {
super<A>.f()//调用 A.f()
super<B>.f()//调用 B.f()
}
}
fun main(args: Array<String>) {
val c = C()
c.f();
}
4. 属性重写
能够使用一个 var
属性重写一个 val,可是反过来不行(可变比不可变的范围更大)。val
属性自己定义了 getter
方法,重写为 var
属性会在子类中额外声明一个 setter
方法。也就是说,即使属性是val
表示不能更改,还是可以通过特殊手段修改其值:对于val
变量的get()
方法进行赋值。但是官方不建议我们这么做,要想定义一个真正的完全不能修改的常量,就要用到编译时常量(const val
关键字)。
举例:
open class Base {
var count = 0
open val i
get() = count++
}
(补充:Java下,对于final
修改的成员变量,基本数据以及public final String s = "a"
这种方式不可被修改而对于对象数据类型是能够突破final
限制进行修改的(暴力反射)。)
5. Kotlin接口
Kotlin 接口与 Java 8 类似,使用 interface
关键字定义接口,允许方法有默认实现。
具体见本章第三节"重写"。
扩展
1. 扩展函数
扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:
fun receiverType.functionName(params){
body
}
receiverType
:表示函数的接收者,也就是函数扩展的对象functionName
:扩展函数的名称params
:扩展函数的参数,可以为NULL
举例:
class User(var name:String)
/**扩展函数**/
fun User.Print(){
print("用户名 $name")
}
fun main(arg:Array<String>){
var user = User("Runoob")
user.Print()
}
举例:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this 对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
fun main(args: Array<String>) {
val l = mutableListOf(1, 2, 3)
// 位置 0 和 2 的值做了互换
l.swap(0, 2) // 'swap()' 函数内的 'this' 将指向 'l' 的值
println(l.toString())
}
this
关键字指代接收者对象(receiver object)
(也就是调用扩展函数时, 在点号之前指定的对象实例)。
2. 扩展函数的静态解析
扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:
open class C
class D: C()
fun C.foo() = "c" // 扩展函数 foo
fun D.foo() = "d" // 扩展函数 foo
fun printFoo(c: C) {
println(c.foo()) // 类型是 C 类
}
fun main(arg:Array<String>){
printFoo(D())
}
// c
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
class C {
fun foo() { println("成员函数") }
}
fun C.foo() { println("扩展函数") }
fun main(arg:Array<String>){
var c = C()
c.foo()
}
// 实例执行输出结果为:
// 成员函数
3. 扩展一个空对象
在扩展函数内, 可以通过this
来判断接收者是否为 NULL
,这样,即使接收者为 NULL
,也可以调用扩展函数。例如:
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
fun main(arg:Array<String>){
var t = null
println(t.toString())
}
// 执行结果为 null
4. 扩展属性
扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field)
,所以不允许被初始化,只能由显式提供的 getter/setter定义
。扩展属性只能被声明为val。
val <T> List<T>.lastIndex: Int
get() = size - 1
5. 伴生对象的扩展
伴生对象最简单直白的理解就是与一个类相伴而生的对象,由于它处于类内部,所以必然和包含它的类存在某种联系。
伴生对象使用关键字companion
来声明,看起来就像是在object
关键字前面加的一个修饰符。它的一般格式如下:
class 外部类名{
companion object 伴生对象名{
//属性
//方法
}
}
在Java中有static
关键字表示静态成属性和方法,但在Kotlin中没有static
关键字,所以伴生对象和顶层函数一起来弥补了这一缺憾。
调用方法:
外部类.伴生对象内部方法
外部类.伴生对象名.伴生对象内部方法
外部类.Companion.伴生对象内部方法
其实伴生对象的出现也是为了弥补顶层函数的不足,顶层函数不能访问一个类中的private成员,而伴生对象是可以访问的,如下例:
class C{
private val age: Int = 0;
companion object {
fun out(){
//伴生对象中可以调用外部的私有成员
println(C().age)
}
}
}
fun main(args: Array<String>) {
C.out() // 0
println(C().age) //报错
}
6. 扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这个扩展中,有个多个隐含的接受者,其中扩展方法定义所在类的实例称为分发接受者,而扩展方法的目标类型的实例称为扩展接受者。
class D {
fun bar() { println("D bar") }
}
class C {
fun baz() { println("C baz") }
fun D.foo() {
bar() // 调用 D.bar
baz() // 调用 C.baz
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
// D bar
// C baz
假如在调用某一个函数,而该函数在分发接受者和扩展接受者均存在,则以扩展接收者优先,要引用分发接收者的成员你可以使用限定的 this
语法。
class D {
fun bar() { println("D bar") }
}
class C {
fun bar() { println("C bar") }
fun D.foo() {
bar() // 调用 D.bar(),扩展接收者优先
this@C.bar() // 调用 C.bar()
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
// D bar
// C bar
7. 数据类
Kotlin 可以创建一个只包含数据的类,关键字为 data
:
data class User(val name: String, val age: Int)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数,如果这些函数在类中已经被明确定义了,或者从超类中继承而来,则不再会生成。
equals() / hashCode()
toString()
格式如"User(name=John, age=42)"
componentN()
functions 对应于属性,按声明顺序排列copy()
函数
为了保证生成代码的一致性以及有意义,数据类需要满足以下条件:- 主构造函数至少包含一个参数。
- 所有的主构造函数的参数必须标识为
val
或者var
; - 数据类不可以声明为
abstract
,open
,sealed
或者inner
; 要能直接访问 - 数据类不能继承其他类 (但是可以实现接口)。
(补充:sealed
密封类的主要特点是,它的子类必须定义在与密封类相同的文件中,这样做可以保证只有有限的类可以继承密封类。这种限制能够提供更好的代码可读性和安全性,因为我们可以在编译时知道所有可能的子类。具体见本章第九节)
复制使用 copy()
函数,我们可以使用该函数复制对象并修改部分属性, 对于上文的 User 类,其实现会类似下面这样:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
举例:
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
// 输出 User(name=Jack, age=1)
// User(name=Jack, age=2)
8. 数据类以及解构声明
组件函数允许数据类在解构声明中使用:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// prints "Jane, 35 years of age"
9. 密封类
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类 的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用sealed
修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
sealed
不能修饰 interface ,abstract class
(会报 warning,但是不会出现编译错误)
举例:
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
10. 枚举类
枚举类最基本的用法是实现一个类型安全的枚举。枚举常量用逗号分隔,每个枚举常量都是一个对象。
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
- 枚举初始化
每一个枚举都是枚举类的实例,它们可以被初始化:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
默认名称为枚举字符名,值从0开始。若需要指定值,则可以使用其构造函数:
enum class Shape(value:Int){
ovel(100),
rectangle(200)
}
枚举还支持以声明自己的匿名类及相应的方法、以及覆盖基类的方法。如:
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
如果枚举类定义任何成员,要使用分号将成员定义中的枚举常量定义分隔开。
- 使用枚举常量
Kotlin 中的枚举类具有合成方法,允许遍历定义的枚举常量,并通过其名称获取枚举常数。
EnumClass.valueOf(value: String): EnumClass
//转换指定name为枚举值,若未匹配成功,会抛出IllegalArgumentException
EnumClass.values(): Array<EnumClass>
//以数组的形式,返回枚举值
获取枚举相关信息:
val name: String //获取枚举名称
val ordinal: Int //获取枚举值在所有枚举数组中定义的顺序
举例:
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE}
fun main(args: Array<String>) {
var color:Color=Color.BLUE
println(Color.values())
println(Color.valueOf("RED"))
println(color.name)
println(color.ordinal)
}
// [LColor;@300ffa5d 数组物理地址
// RED
// BLUE
// 2
泛型
1. 泛型定义
泛型,即 “参数化类型
”,将类型参数化,可以用在类,接口,方法上。与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
// 声明一个泛型类:
class Box<T>(t: T) {
var value = t}
// 创建类的实例时我们需要指定类型参数:
val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1)
// 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。
泛型函数的声明与 Java 相同,类型参数要放在函数名的前面:
fun <T> boxIn(value: T) = Box(value)
在调用泛型函数时,如果可以推断出类型参数,可以省略泛型参数。
2. 泛型约束
我们可以使用泛型约束来设定一个给定参数允许使用的类型。
Kotlin 中使用 : 对泛型的类型上限进行约束。
// 最常见的约束是上界(upper bound):
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
Comparable的子类型可以替代 T
sort(listOf(1, 2, 3)) //成功
// Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) //错误
// HashMap<Int,String> 不是Comparable<HashMap<Int,String>>子类型
对于多个上界约束条件,可以用 where
子句:
fun <T> copyWhenG(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
3. 型变
Kotlin 中没有通配符类型,它有两个其他的东西:声明处型变(declaration-site variance
)与类型投影(type projections
)。
- 声明处型变
声明处的类型变异使用协变注解修饰符:in
、out
,消费者in
, 生产者out
。
使用out
使得一个类型参数协变,协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:
// 定义一个支持协变的类
class Runoob<out A>(val a: A) {
fun foo(): A {
return a
}
}
fun main(args: Array<String>) {
var strCo: Runoob<String> = Runoob("a")
var anyCo: Runoob<Any> = Runoob<Any>("b")
anyCo = strCo
println(anyCo.foo()) // 输出 a
}
in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:
// 定义一个支持逆变的类
class Runoob<in A>(a: A) {
fun foo(a: A) {
}
}
fun main(args: Array<String>) {
var strDCo = Runoob("a")
var anyDCo = Runoob<Any>("b")
strDCo = anyDCo
}
2. 星号投射
有些时候, 你可能想表示你并不知道类型参数的任何信息, 但是仍然希望能够安全地使用它. 这里所谓"安全地使用"是指, 对泛型类型定义一个类型投射, 要求这个泛型类型的所有的实体实例, 都是这个投射的子类型。
对于这个问题, Kotlin 提供了一种语法, 称为 星号投射(star-projection
):
- 假如类型定义为
Foo<out T>
, 其中 T 是一个协变的类型参数, 上界(upper bound)
为TUpper
,Foo<>等价于
Foo. 它表示, 当
T未知时, 你可以安全地从
Foo<>中 读取
TUpper `类型的值. - 假如类型定义为
Foo<in T>
, 其中T
是一个反向协变的类型参数,Foo<>
等价于Foo<inNothing>
. 它表示, 当T
未知时, 你不能安全地向Foo<>
写入 任何东西. - 假如类型定义为
Foo<T>
, 其中T
是一个协变的类型参数, 上界(upper bound)
为TUpper
, 对于读取值的场合,Foo<*>
等价于Foo<out TUpper>
, 对于写入值的场合, 等价于Foo<in Nothing>
.
如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投射. 比如, 如果类型定义为interface Function<in T, out U>
, 那么可以出现以下几种星号投射:
1. Function<*, String> , 代表 Function<in Nothing, String> ;
2. Function<Int, *>, 代表 Function<Int, out Any?> ;
3. Function<, > , 代表 Function<in Nothing, out Any?> .
注意: 星号投射与 Java 的原生类型(raw type
)非常类似, 但可以安全使用。
(补充:raw type
就是定义了泛型的类中,不带任何实际类型参数的那种类型。比如List<E>
的类型参数是E
,那么不带类型参数的List
就是原生类型)
对象表达式和对象声明
1. 对象表达式
通过对象表达式实现一个匿名内部类的对象用于方法的参数中:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
对象可以继承于某个基类,或者实现其他接口:
open class A(x: Int) {
public open val y: Int = x}
interface B {...}
val ab: A = object : A(1), B {
override val y = 15
}
如果超类型有一个构造函数,则必须传递参数给它A(1)
。多个超类型和接口可以用逗号分隔。
通过对象表达式可以越过类的定义直接得到一个对象:
fun main(args: Array<String>) {
val site = object {
var name: String = "菜鸟教程"
var url: String = "www.runoob.com"
}
println(site.name)
println(site.url)
}
请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any
。在匿名对象 中添加的成员将无法访问。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
在对象表达中可以方便的访问到作用域中的其他变量。
2. 对象声明
Kotlin 使用 object
关键字来声明一个对象。
Kotlin 中我们可以方便的通过对象声明来获得一个单例。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
引用该对象,我们直接使用其名称即可:
DataProviderManager.registerDataProvider(...)
当然也可以定义变量来获取这个对象var data1 = DataProviderManager
,当你定义两个不同的变量来获取这个对象时,你会发现你并不能得到两个不同的变量。也就是说通过这种方式,我们获得一个单例。
注意:与对象表达式不同,当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量。
举例:
class Site {
var name = "菜鸟教程"
object DeskTop{
var url = "www.runoob.com"
fun showName(){
print{"desk legs $name"}
// 错误,不能访问到外部类的方法和变量
}
}
}
fun main(args: Array<String>) {
var site = Site()
site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象
Site.DeskTop.url // 正确
}
对象表达式和对象声明之间有一个重要的语义差别:
- 对象表达式是在使用他们的地方立即执行的;
- 对象声明是在第一次被访问到时延迟初始化的;
- 伴生对象的初始化是在相应的类被加载(解析)时执行。 (Java 静态初始化器)
委托
委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by
实现委托。
1. 类委托
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
举例:
// 创建接口
interface Base {
fun print()
}
// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
派生类Derived
继承了接口 Base
所有方法,并且委托一个传入的Base
类的对象来执行这些方法。在 Derived
声明中,by
子句表示,将 b
保存在 Derived
的对象实例内部,而且编译器将会生成继承自 Base
接口的所有方法, 并将调用转发给 b
。
2. 属性委托
属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。
定义:val/var <属性名>: <类型> by <表达式>
var/val
:属性类型(可变/只读)属性名
:属性名称类型
:属性的数据类型表达式
:委托代理类
by
关键字之后的表达式就是委托, 属性的 get()
方法(以及set()
方法)将被委托给这个对象的 getValue()
和 setValue()
方法。属性委托不必实现任何接口, 但必须提供 getValue()
函数(对于 var属性,还需要 setValue()
函数)。
举例:
class Example {
var p: String by Delegate()
}
// 委托的类
class Delegate {
operator fun getValue(thisRef: Any?,
property: KProperty<*>): String {
return "$thisRef, 这里委托了 ${property.name} 属性"
}
operator fun setValue(thisRef: Any?,
property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
}
}
fun main(args: Array<String>) {
val e = Example()
println(e.p) // 访问该属性,调用 getValue() 函数
e.p = "Runoob" // 调用 setValue() 函数
println(e.p)
}
// 输出 (补充:$thisRef 引用当前对象实例)
// Example@433c675d, 这里委托了 p 属性
// Example@433c675d 的 p 属性赋值为 Runoob
// Example@433c675d, 这里委托了 p 属性
3. 标准委托
Kotlin的标准库中已经内置了很多工厂方法来实现属性的委托。
延迟属性Lazy
lazy()
是一个函数, 接受一个 Lambda
表达式作为参数, 返回一个 Lazy <T>
实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用get()
会执行已传递给lazy()
的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
(对比Java:Java Spring IoC
容器会在启动的时候实例化所有单实例 bean
。如果我们想要实现 Spring
在启动的时候延迟加载 bean
,即在首次调用bean的时候再去执行初始化,就可以使用 @Lazy
注解来解决这个问题。)
举例:
val lazyValue: String by lazy {
println("computed!") // 第一次调用输出,第二次调用不执行
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
}
// 输出
// computed!
// Hello
// Hello
4. 可观察属性Observable
observable
可以用于实现观察者模式。
(补充:观察者模式是一种行为设计模式,它用于在对象之间建立一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新。在这种模式中,被观察者(也称为主题)维护一个观察者列表,并提供注册、删除和通知观察者的方法。)
Delegates.observable()
函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler
)。
在属性赋值后会执行事件的响应器(handler
),它有三个参数:被赋值的属性、旧值和新值:
class User {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("旧值:$old -> 新值:$new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "第一次赋值"
user.name = "第二次赋值"
}
// 输出结果
// 旧值:初始值 -> 新值:第一次赋值
// 旧值:第一次赋值 -> 新值:第二次赋值
5. 把属性储存在映射中
一个常见的用例是在一个映射(map
)里存储属性的值。 这经常出现在像解析 JSON
或者做其他"动态
"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
class Site(val map: Map<String, Any?>) {
val name: String by map
val url: String by map}
fun main(args: Array<String>) {
// 构造函数接受一个映射参数
val site = Site(mapOf(
"name" to "菜鸟教程",
"url" to "www.runoob.com"
))
// 读取映射值
println(site.name) //菜鸟教程
println(site.url) //www.runoob.com
}
注意:如果使用 var
属性,需要把 Map
换成 MutableMap
:
(此处MutableMap
类似于Java中的Map
,可以修改键所对应值)
6. Not Null
notNull
适用于那些无法在初始化阶段就确定属性值的场合。
class Foo {
var notNullBar: String by Delegates.notNull<String>()
}
foo.notNullBar = "bar"
println(foo.notNullBar)
需要注意,如果属性在赋值前就被访问的话则会抛出异常。
7. 局部委托属性
你可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:例如本章第三节lazy()
;
8. 属性委托要求
对于只读属性(也就是说val
属性), 它的委托必须提供一个名为getValue()
的函数。该函数接受以下参数:
thisRef
—— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;property
—— 必须是类型KProperty<*>
或其超类型。
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个值可变(mutable)
属性(也就是说,var
属性),除getValue()
函数之外,它的委托还必须 另外再提供一个名为setValue()
的函数, 这个函数接受以下参数:property
—— 必须是类型KProperty<*>
或其超类型;new value
—— 必须和属性同类型或者是它的超类。
总结:Kotlin和Java代码对比用例
1. 打印
// Java
System.out.print("hello world");
System.out.println("hello world");
// Kotlin
print("hello world")
println("hello world")
2. 定义
// Java
String name = "hello world";
final String name = "hello world";
// Kotlin
var name = "hello world"
val name = "hello world"
3. null
// Java
String otherName;
otherName = null;
// Kotlin
var otherName : String?
otherName = null
4. 属性空值判断
// Java
if (text != null) {
int length = text.length();
}
// Kotlin
text?.let {
val length = text.length
}
// 或
val length = text?.length
5. 字符串拼接
// Java
String firstName = "Android";
String lastName = "Architect";
String message = "My name is: " + firstName + " " + lastName;
// Kotlin
val firstName = "Android"
val lastName = "Architect"
val message = "My name is: $firstName $lastName"
6. 多行字符串定义
// Java
String text = "First Line\n" +
"Second Line\n" +
"Third Line";
// Kotlin
val text = """
|First Line
|Second Line
|Third Line
""".trimMargin()
7. 三元表达式
// Java
String text = x > 5 ? "x more than 5" : "x less than 5";
// Kotlin
val text = if (x > 5)
"x more than 5"
else "x less than 5"
8. 操作符
// Java
final int andResult = a & b;
final int orResult = a | b;
final int xorResult = a ^ b;
final int rightShift = a >> 2;
final int leftShift = a << 2;
final int unsignedRightShift = a >>> 2;
// Kotlin
val andResult = a and b
val orResult = a or b
val xorResult = a xor b
val rightShift = a shr 2
val leftShift = a shl 2
val unsignedRightShift = a ushr 2
9. 类型转换
- 声明式
// Java
Car car = (Car) object;
// Kotlin
var car = object as Car
- 隐式
// Java
if (object instanceof Car) {
Car car = (Car) object;
}
// Kotlin
if (object is Car) {
var car = object // 自动识别
}
10. 多重条件
// Java
if (score >= 0 && score <= 300) {...}
// Kotlin
if (score in 0..300) {...}
11. case语句
// Java
int score = // some score;
String grade;
switch (score) {
case 10:
case 9:
grade = "Excellent";
break;
case 8:
case 7:
case 6:
grade = "Good";
break;
case 5:
case 4:
grade = "OK";
break;
case 3:
case 2:
case 1:
grade = "Fail";
break;
default:
grade = "Fail";
}
// Kotlin
var score = // some score
var grade = when (score) {
in 9, 10 -> "Excellent" //此行in可省略
in 6..8 -> "Good"
in 4, 5 -> "OK" //此行in可省略
in 1..3 -> "Fail"
else -> "Fail"
}
12. for循环
// Java
for (int i = 1; i <= 10 ; i++) { }
for (int i = 1; i < 10 ; i++) { }
for (int i = 10; i >= 0 ; i--) { }
for (int i = 1; i <= 10 ; i+=2) { }
for (int i = 10; i >= 0 ; i-=2) { }
for (String item : collection) { }
for (Map.Entry<String, String> entry: map.entrySet()) {...}
// Kotlin
for (i in 1..10) { }
for (i in 1 until 10) { }
for (i in 10 downTo 0) { }
for (i in 1..10 step 2) { }
for (i in 10 downTo 0 step 2) { }
for (item in collection) { }
for ((key, value) in map) {...}
13. 集合操作
// Java
final List<Integer> listOfNumber = Arrays.asList(1, 2, 3, 4);
final Map<Integer, String> keyValue
= new HashMap<Integer, String>();
map.put(1, "Android");
map.put(2, "Ali");
map.put(3, "Mindorks");
// Java 9
final List<Integer> listOfNumber = List.of(1, 2, 3, 4);
final Map<Integer, String> keyValue = Map.of(1, "Android",
2, "Ali",
3, "Mindorks");
// Kotlin
val listOfNumber = listOf(1, 2, 3, 4)
val keyValue = mapOf(1 to "Android",
2 to "Ali",
3 to "Mindorks")
14. 遍历
// Java
// Java 7 and below
for (Car car : cars) {
System.out.println(car.speed);
}
// Java 8+
cars.forEach(car -> System.out.println(car.speed));
// Java 7 and below
for (Car car : cars) {
if (car.speed > 100) {
System.out.println(car.speed);
}
}
// Java 8+
cars.stream().filter(car -> car.speed > 100)
.forEach(car -> System.out.println(car.speed));
// Kotlin
cars.forEach {
println(it.speed)
}
cars.filter { it.speed > 100 } // it代指cars中的每个car对像
.forEach { println(it.speed)}
15. 方法定义
// Java
void doSomething() {
...
}
void doSomething(int... numbers) {
...
}
// Kotlin
fun doSomething() {
...
}
fun doSomething(vararg numbers: Int) {
...
}
16. 带返回值的函数
// Java
int getScore() {
...
return score;
}
// Kotlin
fun getScore(): Int {
...
return score
}
17. 无结束符号
// Java
int getScore(int value) {
...
return 2 * value;
}
// Kotlin
fun getScore(value: Int): Int {
...
return 2 * value
}
18. 构造器
// Java
public class Developer {
private String name;
private int age;
public Developer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// Kotlin
data class Developer(val name: String, val age: Int)
19. List集合
// Java
List<String> list = Arrays.asList("apple", "banana", "orange");
String first = list.get(0); // 获取第一个元素
// Kotlin
val list = listOf("apple", "banana", "orange")
val first = list[0] // 获取第一个元素