Kotlin核心语法(一):kotlin介绍、类型系统

博客主页java

Kotlin核心语法(二):kotlin程序结构、函数
Kotlin核心语法(三):kotlin类、对象、接口
Kotlin核心语法(四):kotlin Lambda编程git

Kotlin就是一门能够运行在Java虚拟机Android浏览器上的静态语言,它与JAVA 100%兼容,若是你对Java很是熟悉,那么你就会发现Kotlin除了本身的标准库以外,大多仍然使用经典的JAVA集合框架。github

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主要特征

  1. 能够运行在服务器端、Android、及任何Java运行的地方
  2. Kotlin和Java同样是一种静态类型的编程语言,意味着全部表达式的类型在编译期已经肯定了,编译器就能验证对象是否包含了想访问的方法或者字段。与动态语言(如:Groovy)不一样,它在编译期不能发现名称拼写错误这样问题,致使运行时错误。

Kotlin具备类型推导能力,在源代码中也不用显示地声明每一个变量的类型,会根据上下文自动判断,val x = 1, 会自动判断变量x的类型是Int
Kotlin对可空类型的支持,在编译期检测可能存在的空指针异常编程

  1. 函数式编程和面向对象
  2. 免费并开源
http://github.com/jetbrains/k...

Kotlin团队打造的库Anko(http://github.com/kotlin/anko)给不少标准Android API 添加了Kotlin友好的适配器。segmentfault

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变量。

几个注意点:

  1. 使用 val 定义变量,虽然只能进行惟一 一次初始化,但若是编译器可以确保只有惟一 一次初始化语句会被执行,可使用不一样的值来初始化
val message: String
    if (条件) {
        message = "Success"
    } else {
        message = "Failed"
    }
  1. val 引用自身是不可变的,可是它指向的对象多是可变的
val books = arrayListOf("Kotlin")
   books.add("Java")
  1. var 关键字容许变量改变本身的值,可是它的类型是改变不了的
var age = 23
    // 错误:类型不匹配
    age = "32"

Kotlin中,变量声明以关键字(val、var)开始,而后变量名。最后加上变量类型(也能够不加,编译器会自动类型推导)

val a: Int = 12

// 也能够省略变量类型,编译器会分析初始化表达式的值,并把它的类型做为变量的类型
val a = 12

// 若是变量没有初始化,须要显示地指定它的类型
val b: Int
b = 13

Java类型系统


基本数据类型与引用数据类型在建立时,内存存储方式区别:

  1. 基本数据类型在被建立时,在栈上给其划分一块内存,将数值直接存储在栈上(性能高)
  2. 引用数据类型在被建立时,首先在栈上给其引用分配一块内存,而对象的具体信息存储在堆内存上,而后由栈上面的引用指向堆中对象的地址

Java中每个基本数据类型都引入了对应的包装类型(wrapper class),如:int 的包装类型就是 Integer,从Java 5 开始引入自动装箱、拆箱机制。

  1. 原始类型:boolean、char、byte、short、int、long、float、double
  2. 对应包装类型:Boolean、Char、Byte、Short、Integer、Long、Float、Double

Kotlin类型系统

Kotlin中去掉了原始类型,只有包装类型,编译器在编译代码的时候,会自动优化性能,把对应的包装类型拆箱为原始类型。

  1. Kotlin 对可空类型的支持,能够帮助在编译器,检测潜在的NullPointerException错误
  2. Kotlin提供了像安全调用(?.) 、Elvis运算符(?:)、非空断言(!!)、let函数等工具来简洁的处理可空类型
  3. as? 运算符提供了一种简单的方式来把值转成一个类型,以及处理它拥有不一样类型的状况
  4. 可空的基本数据类型(如Int)对应Java中的装箱基本数据类型(如java.lang.Integer)
  5. 表示基本数字的类型(如Int)一般会被编译成Java基本数据类型
  6. Any类型是全部其余类型的超类型,相似Java的Object,而Unit类比于void
  7. 不会正常终止的函数使用Nothing类型做为返回类型
  8. Kotlin使用标准Java集合类,并经过区分只读和可变集合来加强他们
  9. 在Kotlin中继承Java类或者实现Java接口时,须要考虑参数的可控性和可变性
  10. Kotlin的Array类像普通的泛型类,会被编译成Java数组
  11. 基本数据类型的数组使用像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等),若是转换失败抛出 NumberFormatException
println("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类都包含下面三个方法:equalshashCodetoString。这三个方法定义在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引用

安全调用运算符:"?."

安全调用运算符:?. 容许把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运算符用其余值代替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
安全转换运算符尝试把值转换成给定的类型,若是类型不合适就返回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值作非空断言,则会抛出异常。
经过使用非空断言,若是值为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只在表达式不为null是执行lambda

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支持类型参数的可空性。可是要当心决定什么是可空的:集合的元素仍是集合自己

  1. 列表自己始终不为null,但列表中的每一个值均可觉得null
  2. 类型的变量可能包含空引用而不是列表实例,但列表中的元素保证是非空的
// 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中建立数组:

  1. arrayOf 函数建立一个数组,它包含的元素是指定为该函数的实参
  2. arrayOfNulls 建立一个给定大小的数组,包含的是null元素。
  3. 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建立基本数据类型的数组:

  1. 该类型的构造方法接收size参数并返回一个使用对应基本数据类型的默认值(一般为0)初始化好的数组
  2. 工厂函数(IntArray的intArrayOf)接收变长参数的值并建立存储这些值的数组
  3. 另外一种构造方法,接收一个大小和一个用来初始化每一个元素的lambda
// 建立存储5个0的整数数组
一、 val arr1 = IntArray(5)
二、 val arr2 = intArrayOf(0, 0, 0, 0, 0)
三、 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)
}

若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)

相关文章
相关标签/搜索