Kotlin就是一门可以运行在 Java虚拟机 、 Android 、 浏览器 上的静态语言,它与 JAVA 100%兼容 ,如果你对Java非常熟悉,那么你就会发现Kotlin除了自己的标准库之外,大多仍然使用经典的JAVA集合框架。
先来体验一下Kotlin代码。Book类包含两个属性:name 和 price。 price属性默认值为null,
// 可空类型(Float?) price的实参默认值为null data class Book(val name: String, val price: Float? = null) // 入口函数 fun main(args: Array<String>) { val books = listOf(Book("Kotlin"), Book("Java", 13.9f)) // lambda表达式, maxBy查找books列表中价格最贵的书 // 如果price属性值为null,Elvis云算法(?:)会返回零 val book = books.maxBy { it.price ?: 0.0f } println("the book is :$book") } // 自动生成Book的toString方法 // the book is :Book(name=Java, price=13.9)
Kotlin具有 类型推导 能力,在源代码中也不用显示地声明每个变量的类型,会根据上下文自动判断, val x = 1 , 会自动判断变量x的类型是Int
Kotlin对可空类型的支持,在编译期检测可能存在的空指针异常
http://github.com/jetbrains/k...
Kotlin团队打造的库Anko( http://github.com/kotlin/anko )给很多标准Android API 添加了Kotlin友好的适配器。
Kotlin 和 Java 都是编译型语言,必须先编译,然后才能执行代码。
Kotlin官方网站
https://kotlinlang.org/docs/t...Kotlin源代码存放在后缀名为 .kt 的文件中。Kotlin编译器会分析源代码并生成 .class 文件,然后执行生成的 .class 文件
$ kotlinc hello.kt -include-runtime -d hello.jar $ java -jar hello.jar
// HelloWord.kt // 1. 使用关键字fun声明一个函数 // 2. 参数的类型写在参数名后面,args: Array<String> // 3. 函数可以定义在文件的最外层,不需要把它放在类中 // 4. 数组就是类,在Kotlin中没有声明数组类型的特殊语法,如:Array<String> // 5. 使用println代替System.out.println。Kotlin标准库给JAVA标准库函数提供很多语法更简洁的包装 // 6. 可以省略每行代码结尾的分号 fun main(args: Array<String>) { println("Hello, Word!") }
声明一个 有返回值的函数 ,参数列表的后面跟着返回值类型,用 冒号(:) 隔开
// 声明格式: fun 函数名(参数列表) : 返回类型 { 函数体 } fun max(a: Int, b: Int): Int { // 在Kotlin中,if 是表达式,而不是语句。表达式有值,且能作为另一个表达式的一部分是使用 return if (a > b) a else b } // 如果函数体是单个表达式,这个表达式可以作为完整的函数体,且可省略花括号 和 return 语句 fun max2(a: Int, b: Int): Int = if (a > b) a else b // 也可以省略 返回值类型。注意:只有表达式体函数的返回类型可以省略 fun max3(a: Int, b: Int) = if (a > b) a else b
变量标识一个对象的地址,称为标识符。在Kotlin中,所有变量类型都是引用类型。
Kotlin中,变量又分为 可变变量(var) 和 不可变量(val)
默认情况下,尽可能使用 val 关键字声明所有的Kotlin变量。
几个注意点:
val message: String if (条件) { message = "Success" } else { message = "Failed" }
val books = arrayListOf("Kotlin") books.add("Java")
var age = 23 // 错误:类型不匹配 age = "32"
Kotlin中,变量声明以关键字(val、var)开始,然后变量名。最后加上变量类型(也可以不加,编译器会自动类型推导)
val a: Int = 12 // 也可以省略变量类型,编译器会分析初始化表达式的值,并把它的类型作为变量的类型 val a = 12 // 如果变量没有初始化,需要显示地指定它的类型 val b: Int b = 13
基本数据类型与引用数据类型在创建时,内存存储方式区别:
Java中每一个基本数据类型都引入了对应的包装类型(wrapper class),如:int 的包装类型就是 Integer,从Java 5 开始引入自动装箱、拆箱机制。
Kotlin中去掉了原始类型,只有包装类型,编译器在编译代码的时候,会自动优化性能,把对应的包装类型拆箱为原始类型。
Kotlin是不区分基本数据类型和它们的包装类。如:Int
val a: Int = 12 val list: List<Int> = listOf(11, 12, 13)
Kotlin还可以对数字类型的值调用方法,coerceIn是标准库函数,把值限制在特定范围内
val a: Int = 110 val newValue = a.coerceIn(0, 100) println("把值限制在0到100之间, 限制前的值: $a, 限制后的值: $newValue") // 把值限制在0到100之间, 限制前的值: 110, 限制后的值: 100
Koltin的Int类型会被编译成Java基本数据类型int,除泛型类外,泛型类型参数的基本数据类型会被编译成对应的Java包装类型,如Int类型编译成java.lang.Integer
在Kotlin中,不可空基本数据类型与Java中的原始的数字类型对应,如:Kotlin中Int,对应Java中的int;
可空的基本数据类型与Java中的装箱类型对应,如:Kotlin中 Int? ,对应Java中Integer
null只能被存储在Java引用类型的变量中,Kotlin中可空数基本据类型不能用Java的基本数据类型表示。
// 使用可空基本数据类型(Int?)变量a,会被编译成java.lang.Integer类型 fun isGreaterThan5(a: Int?): Boolean? { if (a == null) return null return a > 5 }
Kotlin不会自动把数字从一种类型转换成另一种类型,如:Int类型不会自动转换为Long类型
val a: Int = 12 // 这行代码报错:类型不匹配 val b: Long = a // 需要显示的进行转换 val b: Long = a.toLong()
Koltin要求转换必须是显示的,尤其在比较装箱值的时。比较两个装箱值的equals方法不仅会检查它们存储的值,还要比较装箱类型。在Java中 new Integer(12).equals(new Long(12)) 返回false。
val a: Int = 12 val list = listOf(12L, 13L, 14L) // 这行代码编译器不会编译,要求显示的转换类型 a in list // 显示的将 Int 转换 Long 才可以比较 a.toLong() in list
Kotlin 标准库提供很多扩展方法,如:字符串转换成基本数据类型(toInt,toByte,toBoolean等),如果转换失败抛出 NumberFormatException
println("12".toInt())
Java的超类型是Object,Kotlin的所有非空类型的超类型是 Any 类型。但是:在Java中,Object只是所有引用类型的超类型(引用类型的根),而基本数据类型并不是。在Kotlin中,Any是所有类型的超类型,包括Int基本数据类型。
// 基本数据类型的值赋值给Any类型的变量时会自动装箱 // Any是非空类型,不能持有null值,如果持有任何可能值,包括null,必须使用 Any? 类型 val a: Any = 12 // Kotlin中使用Any类型会编译转换成java.lang.Object
所有Kotlin类都包含下面三个方法: equals 、 hashCode 、 toString 。这三个方法定义在 Any 类中,但是 Any 类不能使用其他java.lang.Object的方法(如:wait、notify),可以转换成java.lang.Object来调用这些方法。
Kotlin中如果函数没有返回值时,可以使用 Unit 作为函数返回类型
fun f(): Unit { ... } // Unit 可以省略 fun f(){ ... }
Kotlin的Unit 和 Java的 void区别:Unit可以作为类型参数,而void不行。当只存在一个值是Unit类型,这个值也叫作Unit,且在函数中会被隐式的返回。
interface Processor<T> { fun process(): T } class NoResultProcessor : Processor<Unit> { // 返回Unit类型,但是可以省略 override fun process() { // 不需要显式的return,编译器会隐式地加上return Unit } }
在Java中,为了解决使用 没有值 作为类型参数,给出的方案没有Kotlin好。一种是选择使用分开的接口定义来分别表示需要和不需要返回值的接口(如:Callable 和 Runnable),另一种是用特殊的 java.lang.Void 类型作为类型参数,但还是需要加入一个return null;语句
Nothing类型没有任何值,只有被当作函数返回值使用,或者被当作泛型函数返回值的类型参数使用才有意义。
Kotlin对 可空类型 是显示支持的。如: String? 、 Int? ,可以存储 null 引用。没有问号的类型表示这种类型不能存储null引用,说明默认都是非空的类型,除非显示的把它标记为可空类型。
// 下面是一段java代码 // 如果调用函数时传入null,将抛出NullPointerException int strLen(String s) { return s.length(); } // 下面是一段Kotlin代码 // 如果调用函数时传入null,kotlin编译器是不允许的,保证了strLen函数永不会抛出NullPointerException // 编译期会标记成错误:Null can not be a value of a non-null type String fun strLen(s: String): Int = s.length
上例中,如果在调用strLen函数的时允许传入null,需要显示的在参数类型后面加上 问号(?) 标记
// ? 可以加在任何类型的后面来表示这个类型的变量可以存储null引用 fun strLenSafe(s: String?): Int = ... // 下面这段Kotlin代码,s.length 编译器是不允许的 // ERROR: only safe (?) or non null asserted (!!.) calls are allowed // on a nullable receiver of type kotlin.String? // 可以使用 if 检查处理可控性,但是代码就会变冗长。但Kotlin提供了更简洁的方式处理可空值 fun strLenSafe(s: String?): Int = s.length // 下面这段Kotlin代码,编译器也是不允许的,不能赋值给非空类型的变量 // ERROR: Type mismatch:Required String , Found String? val a: String? = null val b: String = a
安全调用运算符:?.允许把null的检查和方法调用合并一个操作。
string?.length() 等价于 if (string != null) string.length() else null
安全调用运算符:?.调用的结果类型也是可空的,下面例子中,s?.toUpperCase()结果类型是 String?
fun printAllCaps(s: String?) { // allCaps 可能是null val allCaps: String? = s?.toUpperCase() println(allCaps) } >>> printAllCaps(null) null
多个安全调用运算符可以链接调用
class Address(val name: String) class Company(val name: String, val address: Address?) class Person(val name: String, val company: Company?) fun Person.printAddress(): String? { // 多个安全调用运算符链接调用 val addressName = this.company?.address?.name // Elvis运算符:?: addressName?:"Unknown" return if (addressName != null) addressName else "Unknown" } >>> val person = Person("kerwin", null) >>> println(person.printAddress()) Unknown
Kotlin使用Elvis运算符(或者null合并运算符)来提供代替 null 的默认值。
Elvis运算符 ?: 和 安全调用运算符 ?. 一起使用
// 当s==null, s?.length 返回null // s?.length == null 返回 0 fun strLenSafe(s: String?): Int? = s?.length ?: 0 >>> println(strLenSafe("abc")) 3 >>> println(strLenSafe(null)) 0
as?运算符尝试把值转换成指定的类型,如果值不是合适的类型就返回null
class Person(val firstName: String, val lastName: String) { override fun equals(other: Any?): Boolean { // 如果other不是Person类型,other as? Person 返回null,就会直接返回false val otherPerson = other as? Person ?: return false return otherPerson.firstName == firstName && otherPerson.lastName == lastName } } >>> val p1 = Person("Kerwin", "Tom") >>> val p2 = Person("Kerwin", "Tom") >>> println(p1 == p2) true
非空断言 使用 双感叹号(!!) 表示,可以把任何值转换成非空类型,如果对null值做非空断言,则会抛出异常。
fun ignoreNulls(s: String?) { // 如果s == null, 抛出KotlinNullPointerException val sNotNull = s!! println(sNotNull.length) } >>> ignoreNulls(null) Exception in thread "main" kotlin.KotlinNullPointerException
和 安全调用运算符 一起使用,允许对表达式求值,检查求值结果是否为null,并把结果保存为一个变量。
let函数只在值非空时才被调用
fun sendMessage(message: String) { println(message) } // 当 message != null时,才会执行lambda表达式 >>> val message: String? = null >>> message?.let { msg -> sendMessage(msg) } >>> // it 默认变量名,可以简写 message?.let { sendMessage(it) }
很多框架会对对象实例创建之后用专门的方法来初始化对象。如:Activity的初始化发生在onCreate方法中,JUnit要求把初始化逻辑放在用 @Before注解的方法中。
class MyService{ fun performAction(): String = "test" } class MyTest { // 声明一个可空类型的属性并初始化为null private var myService: MyService? = null @Before fun setup() { // 在setup方法中提供真正的初始化器 myService = MyService() } @Test fun testAction() { // 必须注意可空性:要么用 !!,要么用 ?. Assert.assertEquals("test", myService!!.performAction()) } }
上例kotlin代码中,对属性myService每一次访问都需要可空性判断,Kotlin为了解决这个问题,可以把属性myService声明成可以 延迟初始化 ,使用 lateinit 修饰符
class MyService{ fun performAction(): String = "test" } class MyTest { // 使用 lateinit 声明一个不需要初始化器的非空类型的属性 // 延迟初始化的属性都是var // 如果在属性初始化之前就访问了它,抛出异常:lateinit property myService has not been initialized private lateinit var myService: MyService @Before fun setup() { myService = MyService() } @Test fun testAction() { // 不需要 null 检查直接访问属性 Assert.assertEquals("test", myService.performAction()) } }
为可空类型定义扩展函数是一种更强大的处理null值的方式。Kotlin标准库中定义的 String 的两个扩展函数 isEmpty 和 isBlank 。函数isEmptyOrNull 和 isNullOrBlank 就可以由 String? 类型的接受者调用
fun verifyUserInput(input: String?) { // 可空类型的值.可空类型的扩展 if (input.isNullOrBlank()){ // 不需要安全调用 println("input is null or blank.") } } >>> verifyUserInput(null) input is null or blank. // 函数isNullOrBlank实现,可空字符串的扩展 // return this == null || this.isBlank()
Kotlin中所有泛型类和泛型函数的类型参数默认都是可空的。任何类型,包括可空类型在内,都可以替换类型参数。使用类型参数作为类型的声明都允许为 null,尽管类型参数T并没有问号结尾
fun <T> printHashCode(t: T) { // 因为 t 可能为null,所以必须使用安全调用,尽管没有问号结尾,实参t仍允许持有null println(t?.hashCode()) } // T 被推导成 Any? >>> printHashCode(null) null
要使类型参数非空,必须要为它指定一个非空的上界
// 现在 T 就不是可空的 fun <T: Any> printHashCode(t: T) { println(t.hashCode()) } // 编译器不允许的,不能传递null,因为期望值是非空值 >>> printHashCode(null) Null can not be a value of a non-null type TypeVariable(T)
Java中使用注解表达可空性,如 @Nullable String 被Kotlin当作 String?,而 @NotNull String被Kotlin当作 String
Kotlin中的集合库是已Java为基础构建的,并通过扩展函数增加的特性来增强它。
Kotlin支持类型参数的可空性。但是要小心决定什么是可空的:集合的元素还是集合本身
// List<Int?>能持有Int?类型值的列表,也就是说持有 Int 或者 null fun readNumbers(reader: BufferedReader): List<Int?> { // 创建包含可空Int值的列表 val result = ArrayList<Int?>() for (line in reader.lineSequence()) { println("line: $line") try { // 向列表添加非空值整数 val number = line.toInt() result.add(number) } catch (e: NumberFormatException) { // 解析失败,向列表中添加null值 result.add(null) } } return result }
在使用可空值的集合时,需要使用null检查
// List<Int?>? 声明一个变量持有可空的列表,且包含空的数字 // List<Int?> 声明一个变量不为null的列表,且包含空的数字 fun addValidNumbers(numbers: List<Int?>) { var sumOfValidNumbers = 0 var invalidNumbers = 0 for (number in numbers) { // 检查是否为null if (number != null) { sumOfValidNumbers += number } else { invalidNumbers++ } } println("sum of valid numbers:$sumOfValidNumbers") println("Invalid numbers:$invalidNumbers") } // 可以使用Kotlin提供的标准库函数filterNotNull()来完成的,遍历一个包含可空值的集合并过滤掉null // 但是filterNotNull()返回的集合类型,不会在包含任何为null的元素,所以返回集合类型如:List<Int>
Kotlin中把访问集合数据的接口和修改集合数据的接口分开了。一般规则:在代码的任何地方都应该使用只读接口,在代码需要修改集合的地方使用可变接口的变体。 但是不能把只读集合类型的变量赋值给可变的集合变量。
从 kotlin.collections.Collection 接口中可以看出:可以遍历集合中的元素、获取集合大小、判断集合中是否包含某个元素。这个接口没有任何添加或者移除元素的方法。
public interface Collection<out E> : Iterable<E> { public val size: Int public fun isEmpty(): Boolean public operator fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean }
kotlin.collections.MutableCollection接口可以修改集合中的数据。
public interface MutableCollection<E> : Collection<E>, MutableIterable<E> { override fun iterator(): MutableIterator<E> public fun add(element: E): Boolean public fun remove(element: E): Boolean public fun addAll(elements: Collection<E>): Boolean public fun removeAll(elements: Collection<E>): Boolean public fun retainAll(elements: Collection<E>): Boolean public fun clear(): Unit }
集合创建函数
集合类型 | 只读 | 可变 |
---|---|---|
List | listOf | mutableListOf、arrayListOf |
Set | setOf | mutableSetOf、 hashSetOf、linkedSetOf、sortedSetOf |
Map | mapOf | mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf |
默认情况下,应该优先使用集合,而不是数组。
Kotlin中数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数
fun printArray(args: Array<String>) { // 使用扩展属性args.indices,在下标的范围内迭代 for (i in args.indices) { // 通过下标使用array[index]访问元素 println("Argument $i is ${args[i]}") } }
在Kotlin中创建数组:
// 使用Array构造函数创建数组,可以省略数组元素的类型 val letters = Array<String>(26) { // lambda表达式接收数组元素的下标并返回放在数组下标位置的值 i -> ('a' + i).toString() } println(letters.joinToString("")) //abcdefghijklmnopqrstuvwxyz
Kotlin最常见的创建数组的情况之一是调用参数为数组的Java方法,或者调用带有 vararg 参数的Kotlin函数。
// 向vararg方法传递集合 val strings = listOf("a", "b", "c") // fun String.format(vararg args: Any?): String // 期望vararg参数时,使用展开运算符(*)传递数组 // 使用toTypedArray方法将集合转换为数组 println("%s/%s/%s".format(*strings.toTypedArray())) // a/b/c
Kotlin提供了若干独立的类表示基本数据类型的数组,如:Int类型值的数组叫作IntArray,还提供ByteArray、CharArray、BooleanArray等。他们对应Java基本数据类型数组,如:int[]、byte[]、char[]。这些数组中值存储时没有装箱,最高效。
在Kotlin创建基本数据类型的数组:
// 创建存储5个0的整数数组 1、 val arr1 = IntArray(5) 2、 val arr2 = intArrayOf(0, 0, 0, 0, 0) 3、 val arr3 = IntArray(5) { i -> 0 } public fun intArrayOf(vararg elements: Int): IntArray public class IntArray(size: Int) { public inline constructor(size: Int, init: (Int) -> Int) }