Kotlin核心语法(二):kotlin程序结构、函数
Kotlin核心语法(三):kotlin类、对象、接口
Kotlin核心语法(四):kotlin Lambda编程
Kotlin就是一门可以运行在Java虚拟机、Android、浏览器上的静态语言,它与JAVA 100%兼容,如果你对Java非常熟悉,那么你就会发现Kotlin除了自己的标准库之外,大多仍然使用经典的JAVA集合框架。
kotlin介绍
先来体验一下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主要特征
- 可以运行在服务器端、Android、及任何Java运行的地方
- Kotlin和Java一样是一种静态类型的编程语言,意味着所有表达式的类型在编译期已经确定了,编译器就能验证对象是否包含了想访问的方法或者字段。与动态语言(如:Groovy)不同,它在编译期不能发现名称拼写错误这样问题,导致运行时错误。
Kotlin具有类型推导能力,在源代码中也不用显示地声明每个变量的类型,会根据上下文自动判断,val x = 1, 会自动判断变量x的类型是Int
Kotlin对可空类型的支持,在编译期检测可能存在的空指针异常
- 函数式编程和面向对象
- 免费并开源
http://github.com/jetbrains/k...
Kotlin团队打造的库Anko(http://github.com/kotlin/anko)给很多标准Android API 添加了Kotlin友好的适配器。
Kotlin工具
Kotlin 和 Java 都是编译型语言,必须先编译,然后才能执行代码。
Kotlin官方网站
https://kotlinlang.org/docs/t...
编译Kotlin代码
Kotlin源代码存放在后缀名为.kt的文件中。Kotlin编译器会分析源代码并生成.class文件,然后执行生成的.class文件
$ kotlinc hello.kt -include-runtime -d hello.jar
$ java -jar hello.jar
Kotlin基础
函数与变量
函数
// 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)
- var(variable)可变引用,变量的值可以被改变,对应的是普通(非final)的Java变量
- val(value)不可变引用,不能在初始化之后再次赋值,对应的是Java的final变量
默认情况下,尽可能使用 val 关键字声明所有的Kotlin变量。
几个注意点:
- 使用 val 定义变量,虽然只能进行唯一 一次初始化,但如果编译器能够确保只有唯一 一次初始化语句会被执行,可以使用不同的值来初始化
val message: String
if (条件) {
message = "Success"
} else {
message = "Failed"
}
- 虽 val 引用自身是不可变的,但是它指向的对象可能是可变的
val books = arrayListOf("Kotlin")
books.add("Java")
- var 关键字允许变量改变自己的值,但是它的类型是改变不了的
var age = 23
// 错误:类型不匹配
age = "32"
Kotlin中,变量声明以关键字(val、var)开始,然后变量名。最后加上变量类型(也可以不加,编译器会自动类型推导)
val a: Int = 12
// 也可以省略变量类型,编译器会分析初始化表达式的值,并把它的类型作为变量的类型
val a = 12
// 如果变量没有初始化,需要显示地指定它的类型
val b: Int
b = 13
Java类型系统
基本数据类型与引用数据类型在创建时,内存存储方式区别:
- 基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上(性能高)
- 引用数据类型在被创建时,首先在栈上给其引用分配一块内存,而对象的具体信息存储在堆内存上,然后由栈上面的引用指向堆中对象的地址
Java中每一个基本数据类型都引入了对应的包装类型(wrapper class),如:int 的包装类型就是 Integer,从Java 5 开始引入自动装箱、拆箱机制。
- 原始类型:boolean、char、byte、short、int、long、float、double
- 对应包装类型:Boolean、Char、Byte、Short、Integer、Long、Float、Double
Kotlin类型系统
Kotlin中去掉了原始类型,只有包装类型,编译器在编译代码的时候,会自动优化性能,把对应的包装类型拆箱为原始类型。
- Kotlin 对可空类型的支持,可以帮助在编译器,检测潜在的NullPointerException错误
- Kotlin提供了像安全调用(?.) 、Elvis运算符(?:)、非空断言(!!)、let函数等工具来简洁的处理可空类型
- as? 运算符提供了一种简单的方式来把值转成一个类型,以及处理它拥有不同类型的情况
- 可空的基本数据类型(如Int)对应Java中的装箱基本数据类型(如java.lang.Integer)
- 表示基本数字的类型(如Int)通常会被编译成Java基本数据类型
- Any类型是所有其他类型的超类型,类似Java的Object,而Unit类比于void
- 不会正常终止的函数使用Nothing类型作为返回类型
- Kotlin使用标准Java集合类,并通过区分只读和可变集合来增强他们
- 在Kotlin中继承Java类或者实现Java接口时,需要考虑参数的可控性和可变性
- Kotlin的Array类像普通的泛型类,会被编译成Java数组
- 基本数据类型的数组使用像IntArray这样的特殊类来表示
1.基本数据类型
基本数据类型:Int、Boolean
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
可空的基本数据类型:Int?、Boolean?
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等),如果转换失败抛出 NumberFormatExceptionprintln("12".toInt())
根类型:Any 和 Any?
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来调用这些方法。
Unit类型:Kotlin的"void"
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类型:"这个函数永不返回"
Nothing类型没有任何值,只有被当作函数返回值使用,或者被当作泛型函数返回值的类型参数使用才有意义。
2.可空性
可空类型
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
Elvis运算符:"?:"
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?"
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
"let" 函数
和安全调用运算符一起使用,允许对表达式求值,检查求值结果是否为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
Java中使用注解表达可空性,如 @Nullable String 被Kotlin当作 String?,而 @NotNull String被Kotlin当作 String
3.集合和数组
Kotlin中的集合库是已Java为基础构建的,并通过扩展函数增加的特性来增强它。
可空性和集合
Kotlin支持类型参数的可空性。但是要小心决定什么是可空的:集合的元素还是集合本身
- 列表本身始终不为null,但列表中的每个值都可以为null
- 类型的变量可能包含空引用而不是列表实例,但列表中的元素保证是非空的
// 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中创建数组:
- arrayOf 函数创建一个数组,它包含的元素是指定为该函数的实参
- arrayOfNulls 创建一个给定大小的数组,包含的是null元素。
- Array构造方法接收数组的大小和一个lambda表达式,调用lambda表达式来创建每一个数组元素。就是使用非空元素类型来初始化数组,但不用显式的传递每个元素的方式。
// 使用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创建基本数据类型的数组:
- 该类型的构造方法接收size参数并返回一个使用对应基本数据类型的默认值(通常为0)初始化好的数组
- 工厂函数(IntArray的intArrayOf)接收变长参数的值并创建存储这些值的数组
- 另一种构造方法,接收一个大小和一个用来初始化每个元素的lambda
// 创建存储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)
}
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。