第二章 Kotlin入门
-
工作原理
编程语言大致分为两类:编译型语言和解释型语言。Java属于解释性语言,Java代码编译生成class文件,这种class文件只有Java虚拟机能识别。Java虚拟机担当解释器角色,在程序运行时将变异后的class文件解释称计算机可识别的二进制数据后再执行。
Kotlin的编译器能将Kotlin代码编译成class文件。Java虚拟机不会关心class文件是从Java编译来的,还是从Kotlin编译来的,只要是符合规格的class文件,它都能识别。这就是Kotlin的工作原理。
-
变量和函数
-
变量
只允许变量前声明两种关键字:val和var
val(value简写)声明一个不可变的变量,永远优先使用val来声明变量
var(variable简写)声明一个可变的变量
val a: Int = 10Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。
shift+ctrl+p 显示变量类型
-
基本类型
Kotlin无包装类型(Byte、Integer...)字节(
Byte)、整型(Short&Int&Long)、浮点型(Float&Long)、字符(Char)、字符串(String)// val c = 12345678910l // compile error. // L需要大写 val c = 12345678910L // okval e: Int = 10 //val f: Long = e // implicitness not allowed // 显式转换,隐式转换编译不过 val f: Long = e.toLong() // implicitness not allowedval k = "Today is a sunny day." val m = String("Today is a sunny day.".toCharArray()) // 比较地址 println(k === m) // compare references. // 值是否相等,Java则比较地址,Java比较内容使用的是equals println(k == m) // compare values. -
无符号类型
字节(
UByte)、整型(UShort&UInt&ULong)val g: UInt = 10u val h: ULong = 100000000000000000u -
字符串模板
val slogan = "To be No.1" println("Value of String 'j' is: $slogan") // no need brackets println("Length of String 'j' is: ${slogan.length}") // need bracketsval sample = """ <!doctype html> <html> <head> <meta charset="UTF-8"/> <title>Hello World</title> </head> <body> <div id="container"> <H1>Hello World</H1> <p>This is a demo page.</p> </div> </body> </html> """.trimIndent() println(sample)
-
-
数组
整型数组:
IntArray,整型装箱数组:Array<Int>Integer是int的包装类型,比int更浪费资源,最为一个优秀的 coder,肯定希望在范围允许内使用int而不是Integer。我们来看看2个方法
fun myList(vararg inner: Int) { println(inner.size) } fun myList2(vararg inner2: Int?) { println(inner2.size) }上述2个方法的区别其实只在于 Int后面有没有
?号。 我们要不先看看反编译的代码中重要的部分Tools->Kotlin->Show Kotlin Bytecode->Decompile
public static final void myList(@NotNull int... inner) { Intrinsics.checkNotNullParameter(inner, "inner"); int var1 = inner.length; boolean var2 = false; System.out.println(var1); } public static final void myList2(@NotNull Integer... inner2) { Intrinsics.checkNotNullParameter(inner2, "inner2"); int var1 = inner2.length; boolean var2 = false; System.out.println(var1); }大家仔细看形参的签名
- myList是int... inner
- myList2是Integer ...inner2
大家可以思考kotlin编译器为何如此编译了? 其实根本的原因就是?,即可为空。 当Kotlin编译器认为形参不可为空的时候,完全可以用非包装的类型来接受参数,更合理的利用资源。
val intArrayOf = intArrayOf(1, 3, 4) myList(*intArrayOf) myList(1, 2, 3) //注意这里的 null val array = arrayOf(1, 3, 4, null) myList2(*array) myList2(1, 2, 3)上述代码需要注意的是:
- 申明
Int类型的数组,Kotlin提供了2个方法,一个是intArrayOf(),一个是arrayOf(); intArrayOf强调的是int,类似Java中的int[],所以其中的参数不可为nullarrayOf类似Java中的Integer[],所以可以在其中赋值null- kotlin中数组不能直接赋值给可变形参
vararg,需要通过关键字* - 反思下,当我们创建
array的时候,如果不需要null,是不是用intArray更优秀了?
val array = arrayOf(1, 3, 4, null) // 打印数组内容 println(array.contentToString()) // 获取长度 println(array.size) // 重新赋值 array[1] = 4 // 取值 println(array[0]) // 遍历 for (i in array) { } // 判断值是否在数组中 if (1 in array) { println("1 exists in variable 'array'") } -
区间
// [0,10] 离散值 val intRange: IntRange = 1..10 // [0,10) val intRange1: IntRange = 1 until 10 // [10,9,...,1] val intProgression = 10 downTo 1 // 步长 val intProgression1 = 1..10 step 2 for (i in intProgression1) { // 1 3 5 7 9 print("$i ") } // 1, 3, 5, 7, 9 println(intProgression1.joinToString())// [1,2] 连续值 val closedFloatingPointRange: ClosedFloatingPointRange<Float> = 1f..2fval intArrayOf = intArrayOf(1, 2, 3, 4, 5) for (i in 0 until intArrayOf.size) { print("${intArrayOf[i]} ") } // intArrayOf.indices [0,intArrayOf.size) for (i in intArrayOf.indices) { print("${intArrayOf[i]} ") } -
集合
-
增加了不可变集合
不可变List
List<T>,可变ListMutableList<T>Map...
Set...
// 不能添加或删除元素 val listOf = listOf(1, 2, 3) // 可以添加或删除元素 val mutableListOf = mutableListOf(1, 2, 3) val mutableMapOf = mutableMapOf("1" to "first", "2" to "Second") for ((x, y) in mutableMapOf) { } val pair = "Hello" to "Kotlin" val pair1 = Pair("Hello", "Kotlin") val first = pair.first val second = pair.second val (x, y) = pair// Java实现 List<Integer> integers = Arrays.asList(1, 2, 3); // 运行时报错 integers.add(1); // 内容可变 integers.set(1, 3); // 内容不可变 Collections.unmodifiableList(integers) ArrayList arrayList = new ArrayList(); arrayList.add(1); -
提供了丰富易用方法
-
运算符级别支持,简化访问集合框架
-
-
函数
-
函数定义
fun(function简写)定义函数的关键字。fun后跟着是函数名。接着是一对括号,括号内是参数。括号后是返回值,无返回值时直接不写或者写为Unit,Unit等价于Java的void函数返回值。fun functionName(arg1: Int, arg2: Int): Int { return 1 }当函数只有一行代码时,Kotlin允许不写函数体,直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。
fun largeNumber(arg1: Int, arg2: Int) = max(arg1, arg2) -
函数 vs 方法
- 方法可以认为是函数的一种特殊类型
- 从形式上,有receiver(Java中对象,句柄)的函数即为方法
-
函数的类型
fun foo() {} // 类型为: () -> Unit val a: () -> Unit = {} // Foo就是bar方法的receiver,Java中使用this,其实是通过函数第一个参数传递了进来没有显示调用 // 函数的引用类似C语言中的函数指针,用于函数传递 // val kFunction: (Foo, String, Long) -> Long = Foo::bar // val kFunction2: Foo.(String, Long) -> Long = Foo::bar // val kFunction3: Function3<Foo, String, Long, Long> = Foo::bar class Foo{ fun bar(p1:String, p2:Long) : Long { return p1.length + p2 } } val x: (Foo, String, Long) -> Any = Foo::bar yy(x) fun yy(p: (Foo, String, Long) -> Any) { p(Foo(), "Hello", 3L) p.invoke(Foo(), "Hello", 3L) } -
程序的逻辑控制
程序的执行语句分为顺序语句、条件语句和循环语句。
-
条件语句
-
if条件语句
if条件语句与Java无异
Kotlin中if语句比Java中多了一个额外功能,if语句可以有返回值
fun largeNumber(num1: Int, num2: Int): Int { val largeInt = if (num1 > num2) { num1 } else { num2 } return largeInt } -
when条件语句
when类似Java中的switch
switch缺陷:只能传入char, byte, short, int, Character, Byte, Short, Integer, String, or an enum变量作为条件。并且每个case需要break,否则执行完当前case后依次执行下面case。
基本示例:
fun getScore(name: String) = when (name) { "Tom" -> 85 "Jim" -> 77 else -> 0 }类型匹配示例:
fun checkNumber(num: Number) = when (num) { is Int -> "Int" is Double -> "Double" else -> "not support type" }is是类型匹配的关键字,相当于java中的instanceof关键字。
when不带参数的示例,Kotlin中判断字串或对象使用**==关键字,不用像Java使用equals()**方法
fun getScoreNoParam(name: String) = when { name.startsWith("lisa") -> 99 name == "Tom" -> 85 name == "Jim" -> 77 else -> 0 }
-
-
循环语句
-
for循环语句
Java中for-i循环已被舍弃,Java中for-each循环被加强变成了for-in循环
Kotlin中区间的写法:
val range = 0..10 // [0,10] val range2 = 0 until 10 // [0,10)for (i in 0..10) { print("$i ") // 输出 0 1 2 3 4 5 6 7 8 9 10 }使用step关键字跳过奇数元素,相当于Java中的i = i + 2操作
for (i in 0 until 10 step 2) { print("$i ") // 输出 0 2 4 6 8 }downTo定义降序区间
for (i in 10 downTo 0) { print("$i ") // 输出 10 9 8 7 6 5 4 3 2 1 0 } -
while循环不管是在语法还是使用技巧上都和Java中的while循环没有任何区别
-
-
-
面向对象编程
-
类
File通常是用于编写Kotlin顶层函数和扩展函数的
Class是用于编写Kotlin的类
类默认是用
public修饰的类示例:
class Person { var name = "Tony" var age = 10 fun introduceSelf() { println("My name is $name. I am $age years old.") } }类示例化:
val person = Person() person.introduceSelf() -
继承和构造函数
-
定义Student类继承自Person:
-
使Person可继承,给Person加上open关键字。在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。
open class Person { ...... } -
使Student继承Person
class Student : Person() { init { ... } ... }Person后面为何加括号:子类构造函数必须调用父类构造函数。
Kotlin中构造函数有:主构造函数和次构造函数。主构造函数没有函数体,Kotlin提供了init结构体。子类的主构造函数调用父类的哪个构造函数,在继承的时候通过括号来指定。
-
-
改造Person
Person改造如下:
open class Person(val name:String, val age:Int) { fun introduceSelf() { println("My name is $name. I am $age years old.") } }Student作出相应调整:
class Student(val number: Int, val grade : Int, name:String, age:Int) : Person(name, age) { init { } }Student构造函数中的name和age不能声明成var或者val,因为声明为
val或者val会成为该类的字段。 -
次构造函数
类只能有一个主构造函数,但是可以有多个次构造函数。所有次构造函数都必须调用主构造函数。
示例如下:
class Student(val number: Int, val grade: Int, name: String, age: Int) : Person(name, age) { constructor(name: String, age: Int) : this(0, 0, name, age) { } constructor() : this("Tom", 0) { } }次构造函数是用constructor定义的。实例化代码如下:
val student1 = Student() val student2 = Student("Jack", 10) val student3 = Student(1527, 2, "Jerry", 21)类只有次构造函数,没有主构造函数:
class Student : Person { constructor(number: Int, grade: Int, name: String, age: Int) : super(name, age) { } constructor(name: String, age: Int) : this(0, 0, name, age) { } constructor() : this("Tom", 0) { } }在次构造函数中参数不能用var或val声明,因为var或者val声明的参数会成为类的字段。次构造函数不是必须调用的,所以存在未调用导致未初始化,所以禁止使用var或者val声明。
-
-
抽象类
abstract class AbsClass { abstract fun absMethod() // 添加open之后才可复写 open fun overridable(){} fun nonOverridable(){} }方法前加
open关键字,方可复写如果继承的父类有个方法可复写,但是不想子类复写该方法,可添加
final关键字open class SimpleClass(var x: Int, val y: String) : AbsClass() { override fun absMethod() {} // 继承SimpleClass的字类不可复写该方法 final override fun overridable(){ } } -
接口
Kotlin中接口和Java中接口几乎一致。
-
接口使用:
-
创建Study接口
interface Study { fun readBooks() fun doHomework() } -
实现接口
class Student(name: String, age: Int) : Person(name, age), Study { override fun readBooks() { } override fun doHomework() { } }熟悉Java的人一定知道,Java中继承使用的关键字是extends,实现接口使用的关键字是implements,而Kotlin中统一使用冒号,中间用逗号进行分隔。
-
示例:
fun main() { val student = Student("Jack", 10) doStudy(student) } fun doStudy(study: Study) { study.doHomework() study.readBooks() }因为Student实现了Study接口,所以Student类可以传递给doStudy函数,接着调用Study接口的方法。这种叫做面向接口编程,也就是多态。
-
-
默认实现
interface Study { fun readBooks() fun doHomework() { println("doHomework default implementation") } }默认实现的函数可自由选择实现或不实现。不实现会使用默认实现逻辑。
-
可见性修饰符
修饰符 Java Kotlin public 所有类可见 所有类可见(默认) private 当前类可见 当前类可见 protected 当前类、子类、同一包下类可见 当前类、子类可见 default 同一包下类可见(默认) 无 internal 无 同一模块中类可见 -
property(Kotlin) vs field(Java)
Kotlin 的
property= Java 的(field+get+set)如果
val修饰,只有get如果
var修饰,get+setclass Person(age: Int, name: String) { var age: Int = age //property get() { return field } set(value) { println("setAge: $value") field = value } var name: String = name get() { return field // backing field } set(value) { } }属性引用
// 未绑定receiver val ageRef = Person::age val person = Person(18, "Bennyhuo") // 绑定receiver val nameRef = person::name ageRef.set(person, 20) nameRef.set("Andyhuo")
-
-
数据类和单例类
-
数据类
数据类通常需要重写equals()、hashCode()、toString()方法
equals() 判断数据是否相等
hashCode() HashMap、HashSet等hash相关类正常工作
toString() 清晰的日志输出
示例:
data class Cellphone(val brand: String, val price: Double)在类前面声明data表示这个类是数据类。Kotlin会自动实现equals()、hashCode()、toString()等固定无实际逻辑意义的方法。
-
单例类
全局只有一个实例
object Singleton { fun singletonTest() { println("SingletonTest is called") } }使用
Singleton.singletonTest()
-
-
-
Lambda编程
-
集合创建和遍历
-
创建List
listOf<String>("Hello", "world", "this", "is", "kotlin") -
遍历List
for (s in listOf) { println(s) } -
listOf()函数创建的是一个不可变的集合,mutableListOf()函数为可变集合
val mutableList = mutableListOf<String>("Hello", "world", "this", "is", "kotlin") -
创建Map
var fruitAndPrice :HashMap<String, Int> = HashMap() fruitAndPrice["Apple"] = 3 val fruitAndIndex = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "pear" to 4, "Grape" to 5) -
遍历Map
for ((fruit, number) in fruitAndIndex) { println("fruit is $fruit, index is $number") }
-
-
集合的函数式API
-
取长度最长的单词
val wordList = listOf<String>("Hello", "world", "this", "is", "kotlin") // 当集合中无元素时,结果会为空 val maxByOrNull = wordList.maxByOrNull { it.length } // 当结果为空时,使用空字串 val result: String = maxByOrNull ?: "" println(result) -
Lambda是一段可以作为参数传递的代码,Lambda语法结构
{参数名1 : 参数类型, 参数名2 : 参数类型 -> 函数体} -
简化推导过程
上述maxByOrNull只是一个简单的函数,此函数的内部实现会遍历集合,并且调用lambda表达式(集合元素传入表达式)获取元素的长度并对比,最终返回最长的集合元素。
-
lambda表达式作为变量
val wordList = listOf<String>("Hello", "world", "this", "is", "kotlin") val function = { fruit: String -> fruit.length } val maxByOrNull = wordList.maxByOrNull(function) -
直接作为参数传递
val maxByOrNull = wordList.maxByOrNull({ fruit: String -> fruit.length }) -
当lambda是函数的最后一个参数时,可以将lambda表达式移到括号外
val maxByOrNull = wordList.maxByOrNull() { fruit: String -> fruit.length } -
如果lambda是函数的唯一参数时,可以将括号省略
val maxByOrNull = wordList.maxByOrNull { fruit: String -> fruit.length } -
Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型
val maxByOrNull = wordList.maxByOrNull { fruit -> fruit.length } -
当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替
val maxByOrNull = wordList.maxByOrNull { it.length }
-
-
Java函数式API使用
限制:在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
以Runnable举例:
-
Java实现:
new Thread(new Runnable() { @Override public void run() { TODO("Not yet implemented") } }); -
Kotlin实现:
Thread(object : Runnable { override fun run() { TODO("Not yet implemented") } })Kotlin舍弃了new关键字,改用object关键字
再次精简,因为Runnable只有一个待实现方法,无需显式地重写run()方法
Thread(Runnable { TODO("Not yet implemented") })如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略
Thread({ TODO("Not yet implemented") })如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略
Thread { TODO("Not yet implemented") }
-
-
-
-
空指针检查
-
可空类型
fun doStudy(study: Study) { study.doHomework() study.readBooks() }上述代码没有空指针风险,因为Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数也一定不会为空。
Kotlin将空指针异常的检查提前到了编译时期,如果我们的程序存在空指针异常的风险,那么在编译的时候会直接报错。
允许为空示例:
fun doStudy(study: Study?) { if (study != null) { study.doHomework() study.readBooks() } } -
判空辅助工具
-
借助?.操作符将if判断语句去掉
fun doStudy(study: Study?) { study?.doHomework() study?.readBooks() } -
?:操作符,这个操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果
一般写法:
val a:String = "a" val c = if (a == null) a else ""?:操作符写法:
val c = a ?: ""示例获取字串的长度
fun getWordLength(word : String) : Int { if (word == null) { return 0 } else { return word.length } }简化后:
fun getWordLength(word : String?) : Int { return word?.length ?: 0 } -
*非空断言工具,不推荐,这是有风险的写法
fun getWordLength(word : String?) : Int { return word!!.length } -
辅助工具——let
fun doStudy(study: Study?) { study?.let { stu -> stu.readBooks() stu.doHomework() } }let函数可以处理全局判空问题,而if判断语句无法做到这点。
我们将doStudy()函数中的参数变成一个全局变量,使用let函数仍然可以正常工作,但使用if判断语句则会提示错误。
之所以这里会报错,是因为全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if语句中的study变量没有空指针风险。
var student: Student? = Student("Lucy", 19) fun study() { if (student != null) { student.doHomework() student.readBooks() } }
-
-
Kotlin中小魔术
-
字符串内嵌表达式
class Student(name: String, age: Int) : Person(name, age), Study { override fun readBooks() { println("My name is $name, I'm ${age} years old. I'm reading!") } } -
函数默认参数
fun sing(songName:String = "Pretty Girl", artist:String) { }调用:
sing("hello", "jams") // 通过键值对赋值 sing(artist = "lucy")主构造函数中参数添加默认值,避免多个构造函数
class Student(val number: Int = 0, val grade: Int = 0, name: String="Tom", age: Int=10) : Person(name, age), Study { }