Kotlin语言基础(一)

3.1 包(package)

我们先来举个例子。比如说,程序员A写了一个类叫 JSON , 程序员B也写了一个类叫 JSON。然后,我们在写代码的时候,想要同时使用这两个类,该怎么区分呢?

一个答案是使用目录命名空间。对应在Java中,就是使用package来组织类,以确保类名的唯一性。上面说的例子,A写的类放到package com.abc.fastjson 中, B写的类就放到 package com.bbc.jackjson中。这样我们在代码中,就可以根据命名空间来分别使用这两个类。调用示例如下

com.abc.fastjson.JSON.toJSONString()
com.bbc.jackjson.JSON.parseJSONObject()

在Kotlin中也沿袭了Java的 package 这个概念,同时做了一些扩展。

我们可以在*.kt文件开头声明package命名空间。例如在PackageDemo.kt源代码中,我们按照如下方式声明包

package com.easy.kotlin

/**
 * Created by jack on 2017/6/8.
 */

fun what(){
    println("This is WHAT ?")
}

class Motorbike{
    fun drive(){
        println("Drive The Motorbike ...")
    }
}

fun main(args:Array<String>){
    println("Hello,World!")
}

包的声明处于源文件顶部。这里,我们声明了包 com.easy.kotlin , 里面定义了包级函数 what() , 同时定义了一个类 Motorbike 。另外,目录与包的结构无需匹配:源代码可以在文件系统的任意位置。

我们怎么使用这些类和函数呢?我们写一个Junit 测试类来示例说明。

首先,我们使用标准Gradle工程目录,对应的测试代码放在test目录下。具体目录结构如下

我们在测试源代码目录 src/test/kotlin下面新建一个包,跟src/main/kotlin在同一个 package com.easy.kotlin。然后,在此包下面新建一个测试类PackageDemoTest

package com.easy.kotlin

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */

@RunWith(JUnit4::class)
class PackageDemoTest {

    @Test
    fun testWhat() {
        what()
    }

    @Test
    fun testDriveMotorbike(){
        val motorbike = Motorbike()
        motorbike.drive()
    }

}

其中,what() 函数跟PackageDemoTest类在同一个包命名空间下,可以直接调用,不需要 importMotorbike类跟PackageDemoTest类也是同理分析。

如果不在同一个package下面,我们就需要import对应的类和函数。例如,我们在 src/test/kotlin目录下新建一个package com.easy.kotlin.test, 使用package com.easy.kotlin 下面的类和函数,示例如下

package com.easy.kotlin.test

import com.easy.kotlin.Motorbike
import com.easy.kotlin.what
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */

@RunWith(JUnit4::class)
class PackageDemoTest {

    @Test
    fun testWhat() {
        what()
    }

    @Test
    fun testDriveMotorbike() {
        val motorbike = Motorbike()
        motorbike.drive()
    }

}

我们使用import com.easy.kotlin.Motorbike导入类,直接使用import com.easy.kotlin.what导入包级函数。

上面我们使用JUnit4测试框架。在build.gradle中的依赖是

testCompile group: 'junit', name: 'junit', version: '4.12'

右击测试类,点击执行


运行结果

另外,如果我们不定义package命令空间,则默认在根级目录。例如直接在src/main/kotlin 源代码目录下面新建 DefaultPackageDemo.kt 类

import java.util.*

/**
 * Created by jack on 2017/6/8.
 */

fun now() {
    println("Now Date is: " + Date())
}

class Car{
    fun drive(){
        println("Drive The Car ... ")
    }

}

如果,我们同样在src/test/kotlin 目录下面新建测试类DefaultPackageDemoTest

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */

@RunWith(JUnit4::class)
class DefaultPackageDemoTest {

    @Test
    fun testDefaultPackage() {
        now()
        val car = Car()
        car.drive()
    }

}

我们不需要import now() 函数和 Car类,可以直接调用。如果我们在 src/test/kotlin/com/easy/kotlin/PackageDemoTest.kt 测试类里面调用now() 函数和 Car类, 我们按照下面的方式import

import now
import Car

PackageDemoTest.kt完整测试代码如下

package com.easy.kotlin

import now
import Car
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */

@RunWith(JUnit4::class)
class PackageDemoTest {

    @Test
    fun testWhat() {
        what()
    }

    @Test
    fun testDriveMotorbike(){
        val motorbike = Motorbike()
        motorbike.drive()
    }

    @Test
    fun testDefaultPackage() {
        now()
        val car = Car()
        car.drive()
    }

}

另外, Kotlin会会默认导入一些基础包到每个 Kotlin 文件中:

kotlin.*

kotlin.annotation.*

kotlin.collections.*

kotlin.comparisons.* (自 1.1 起)

kotlin.io.*

kotlin.ranges.*

kotlin.sequences.*

kotlin.text.*

根据目标平台还会导入额外的包:
JVM:

java.lang.*

kotlin.jvm.*

JS:

kotlin.js.*

本小节示例工程源代码:https://github.com/EasyKotlin/chapter3_kotlin_basics/tree/package_demo

3.2 声明变量和值

首先,在Kotlin中, 一切都是对象。所以,所有变量也都是对象(也就是说,任何变量都是根据引用类型来使用的)。

Kotlin的变量分为 var (可变的) 和 val (不可变的)。

可以简单理解为:

var 是可写的,在它生命周期中可以被多次赋值;

val 是只读的,仅能一次赋值,后面就不能被重新赋值。

代码示例

package com.easy.kotlin

import java.util.*

/**
 * Created by jack on 2017/6/8.
 */

class VariableVSValue {
    fun declareVar() {
        var a = 1
        a = 2
        println(a)
        println(a::class)
        println(a::class.java)

        var x = 5 // 自动推断出 `Int` 类型
        x += 1

        println("x = $x")
    }

    fun declareVal() {
        val b = "a"
        //b  = "b" //编译器会报错: Val cannot be reassigned
        println(b)
        println(b::class)
        println(b::class.java)

        val c: Int = 1  // 立即赋值
        val d = 2   // 自动推断出 `Int` 类型
        val e: Int  // 如果没有初始值类型不能省略
        e = 3       // 明确赋值
        println("c = $c, d = $d, e = $e")
    }
}

我们知道,在Java中也分可变与不可变(final)。在Kotlin中,更简洁的、更常用的场景是:只要可能,尽量在Kotlin中首选使用val不变值。因为事实上在程序中大部分地方使用不可变的变量,可带来很多益处,如:可预测的行为和线程安全。

3.3 变量类型推断

3.3.1 省去变量类型

在Kotlin中大部分情况你不需要说明你使用对象的类型,编译器可以直接推断出它的类型。代码示例

    fun typeInference(){
        val str = "abc"
        println(str)
        println(str is String)
        println(str::class)
        println(str::class.java)

//        abc
//        true
//        class java.lang.String (Kotlin reflection is not available)
//        class java.lang.String

        val d = Date()
        println(d)
        println(d is Date)
        println(d::class)
        println(d::class.java)

//        Fri Jun 09 00:06:33 CST 2017
//        true
//        class java.util.Date (Kotlin reflection is not available)
//        class java.util.Date

        val bool = true
        println(bool)
        println(bool::class)
        println(bool::class.java)

//        true
//        boolean (Kotlin reflection is not available)
//        boolean

        val array = arrayOf(1,2,3)
        println(array)
        println(array is Array)
        println(array::class)
        println(array::class.java)

//        [Ljava.lang.Integer;@7b5eadd8
//        true
//        class [Ljava.lang.Integer; (Kotlin reflection is not available)
//        class [Ljava.lang.Integer;
    }

所以,我们只需要依据要产生的变量类型填写var或val,其类型通常能够被推断出来。编译器能够检测到其类型,自动地完成类型转换。当然,我们也可以明确地指定变量类型。

但是,类型推断不是所有的。例如,整型变量Int不能赋值Long变量。下面的代码不能通过编译:

    fun Int2Long(){
        val x:Int = 10
        val y:Long = x // Type mismatch
    }

我们需要显式地调用对应的类型转换函数进行转换:

 fun Int2Long(){
        val x:Int = 10
//        val y:Long = x // Type mismatch
        val y: Long = x.toLong()
    }

3.3.2 使用is运算符进行类型检测

is 运算符检测一个表达式是否某类型的一个实例。

如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换:

    fun getLength(obj: Any): Int? {
        var result = 0
        if (obj is String) {
            // `obj` 在该条件分支内自动转换成 `String`
            println(obj::class) //class java.lang.String
            result = obj.length
            println(result)
        }
        // 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
        println(obj::class) // class java.lang.Object
        return result
    }

测试类如下

    @Test
    fun testGetLength() {
        val obj = "abcdef"
        val len = variableVSValue.getLength(obj)
        Assert.assertTrue(len == 6)

        val obj2:Any = Any()
        variableVSValue.getLength(obj2)

    }

3.4 字符串与其模板表达式

原始字符串(raw string)由三重引号(""")分隔(这个跟python一样)。原始字符串可以包含换行符和任何其他字符。

package com.easy.kotlin

/**
 * Created by jack on 2017/6/9.
 */

fun main(args: Array<String>) {
    val rawString = """
fun helloWorld(val name : String) {
   println("Hello, world!")
}
"""
    println(rawString)

}

字符串可以包含模板表达式。模板表达式以美元符号($)开始。

    val fooTemplateString = "$rawString has ${rawString.length} characters"
    println(fooTemplateString)

3.5 流程控制语句

流程控制语句是编程语言中的核心之一。可分为:

分支语句(ifwhen)

循环语句(forwhile )和

跳转语句 (returnbreakcontinuethrow)等。

3.5.1 if表达式

if-else语句是控制程序流程的最基本的形式,其中else是可选的。

在 Kotlin 中,if 是一个表达式,即它会返回一个值(跟Scala一样)。

代码示例:
package com.easy.kotlin

/**

  • Created by jack on 2017/6/9.
    */

fun main(args: Array<String>) {
println(max(1, 2))
}

fun max(a: Int, b: Int): Int {

// 作为表达式
val max = if (a > b) a else b
return max // return if (a > b) a else b
}

fun max1(a: Int, b: Int): Int {
// 传统用法
var max1 = a
if (a < b) max1 = b
return max1

}

fun max2(a: Int, b: Int): Int {

// With else
var max2: Int
if (a > b) {
max2 = a
} else {
max2 = b
}
return max2
}

另外,if 的分支可以是代码块,最后的表达式作为该块的值:
fun max3(a: Int, b: Int): Int {
val max = if (a > b) {
print("Max is a")
a
} else {
print("Max is b")
b
}
return max
}
if作为代码块时,最后一行为其返回值。
另外,在Kotlin中没有类似true? 1: 0这样的三元表达式。对应的写法是使用if else语句:
if(true) 1 else 0
如果 if 表达式只有一个分支, 或者分支的结果是 Unit , 它的值就是 Unit 。
示例代码

val x = if(1==1) true
x
kotlin.Unit
val y = if(1==1) true else false
y
true
if-else语句规则:

if后的括号不能省略,括号里表达式的值须是布尔型
代码反例:

if("a") 1
error: type mismatch: inferred type is String but Boolean was expected
if("a") 1
^

if(1) println(1)
error: the integer literal does not conform to the expected type Boolean
if(1)
^

如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格建议加上大括号。

if(true) println(1) else println(0)
1
if(true) { println(1)} else{ println(0)}
1
对于给定的if,else语句是可选的,else if 语句也是可选的。
else和else if同时出现时,else必须出现在else if 之后。
如果有多条else if语句同时出现,那么如果有一条else if语句的表达式测试成功,那么会忽略掉其他所有else if和else分支。
如果出现多个if,只有一个else的情形,else子句归属于最内层的if语句。
以上规则跟Java、C语言基本相同。

3.5.2 when表达式

when表达式类似于 switch-case 表达式。when会对所有的分支进行检查直到有一个条件满足。但相比switch而言,when语句要更加的强大,灵活。

Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单直接:

fun cases(obj: Any) {
    when (obj) {
        1 -> print("第一项")
        "hello" -> print("这个是字符串hello")
        is Long -> print("这是一个Long类型数据")
        !is String -> print("这不是String类型的数据")
        else -> print("else类似于Java中的default")
    }
}

像 if 一样,when 的每一个分支也可以是一个代码块,它的值是块中最后的表达式的值。

如果其他分支都不满足条件会到 else 分支(类似default)。

如果我们有很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

fun switch(x: Any) {
    when (x) {
        -1, 0 -> print("x == -1 or x == 0")
        1 -> print("x == 1")
        2 -> print("x == 2")
        else -> { // 注意这个块
            print("x is neither 1 nor 2")
        }
    }
}

我们可以用任意表达式(而不只是常量)作为分支条件

fun switch(x: Int) {
    val s = "123"
    when (x) {
        -1, 0 -> print("x == -1 or x == 0")
        1 -> print("x == 1")
        2 -> print("x == 2")
        8 -> print("x is 8")
        parseInt(s) -> println("x is 123")
        else -> { // 注意这个块
            print("x is neither 1 nor 2")
        }
    }
}

我们也可以检测一个值在 in 或者不在 !in 一个区间或者集合中:

    val x = 1
    val validNumbers = arrayOf(1, 2, 3)
    when (x) {
        in 1..10 -> print("x is in the range")
        in validNumbers -> print("x is valid")
        !in 10..20 -> print("x is outside the range")
        else -> print("none of the above")
    }

3.5.3 for循环

Kotlin的for循环跟现代的程序设计语言基本相同。

for 循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:

for (item in collection) {
    print(item)
}

循环体可以是一个代码块。

for (i in intArray) {
    ...
}

代码示例


/**
 * For loop iterates through anything that provides an iterator.
 * See http://kotlinlang.org/docs/reference/control-flow.html#for-loops
 */
fun main(args: Array<String>) {
    for (arg in args)
        println(arg)
    // or
    println()
    for (i in args.indices)
        println(args[i])
}

如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:

for (i in array.indices) {
    print(array[i])
}

或者你可以用库函数 withIndex

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

3.5.4 while循环

while 和 do .. while使用方式跟C、Java语言基本一致。

代码示例

package com.easy.kotlin

/**
 * Created by jack on 2017/6/9.
 */

fun main(args: Array<String>) {
    var x = 10
    while (x > 0) {
        x--
        println(x)
    }

    var y = 10
    do {
        y = y + 1
        println(y)
    } while (y < 20) // y的作用域包含此处
}

3.5.5 break 和 continue

breakcontinue都是用来控制循环结构的,主要是用来停止循环(中断跳转)。

1.break

我们在写代码的时候,经常会遇到在某种条件出现的时候,就直接提前终止循环。而不是等到循环条件为false时才终止。这个时候,我们就可以使用break结束循环。break用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。

问题场景:

打印数字1~10,只要遇到偶数,就结束打印。

代码示例:

fun breakDemo_1() {
    for (i in 1..10) {
        println(i)
        if (i % 2 == 0) {
            break
        }
    } // break to here
}

测试代码:

 @Test
    fun testBreakDemo_1(){
        breakDemo_1()
    }

输出:

1
2

2.continue

continue是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为,直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break则是完全终止循环,跳转到循环出口。

问题场景:

打印数字0~10,但是不打印偶数。

代码示例:

fun continueDemo() {
    for (i in 1..10) {
        if (i % 2 == 0) {
            continue
        }
        println(i)
    }
}

测试代码

    @Test
    fun testContinueDemo() {
        continueDemo()
    }

输出

1
3
5
7
9

3.5.6 return返回

在Java、C语言中,return语句使我们再常见不过的了。虽然在Scala,Groovy这样的语言中,函数的返回值可以不需要显示用return来指定,但是我们仍然认为,使用return的编码风格更加容易阅读理解。

在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return来返回其值。

代码示例

fun sum(a: Int,b: Int): Int{
    return a+b
}

fun max(a: Int, b: Int): Int { if (a > b) return a else return b}

我们在Kotlin中,可以直接使用=符号来直接返回一个函数的值。

代码示例

>>> fun sum(a: Int,b: Int) = a + b
>>> fun max(a: Int, b: Int) = if (a > b) a else b

>>> sum(1,10)
11

>>> max(1,2)
2

>>> val sum=fun(a:Int, b:Int) = a+b
>>> sum
(kotlin.Int, kotlin.Int) -> kotlin.Int
>>> sum(1,1)
2

>>> val sumf = fun(a:Int, b:Int) = {a+b}
>>> sumf
(kotlin.Int, kotlin.Int) -> () -> kotlin.Int
>>> sumf(1,1)
() -> kotlin.Int
>>> sumf(1,1).invoke()
2

上述代码示例中,我们可以看到,后面的函数体语句有没有大括号 {} 意思完全不同。加了大括号,意义就完全不一样了。我们再通过下面的代码示例清晰的看出:

>>> fun sumf(a:Int,b:Int) = {a+b}
>>> sumf(1,1)
() -> kotlin.Int
>>> sumf(1,1).invoke
error: function invocation 'invoke()' expected
sumf(1,1).invoke
          ^
>>> sumf(1,1).invoke()
2
>>> fun maxf(a:Int, b:Int) = {if(a>b) a else b}
>>> maxf(1,2)
() -> kotlin.Int
>>> maxf(1,2).invoke()
2

可以看出,sumfmaxf的返回值是函数类型:

() -> kotlin.Int
() -> kotlin.Int

这点跟Scala是不同的。在Scala中,带不带大括号{},意思一样:

scala> def maxf(x:Int, y:Int) = { if(x>y) x else y }
maxf: (x: Int, y: Int)Int

scala> def maxv(x:Int, y:Int) = if(x>y) x else y
maxv: (x: Int, y: Int)Int

scala> maxf(1,2)
res4: Int = 2

scala> maxv(1,2)
res6: Int = 2

我们可以看出maxf: (x: Int, y: Int)Intmaxv: (x: Int, y: Int)Int签名是一样的。在这里,Kotlin跟Scala在大括号的使用上,是完全不同的。

然后,调用方式是直接调用invoke()函数。通过REPL的编译错误提示信息,我们也可以看出,在Kotlin中,调用无参函数也是要加上括号()的。

kotlin 中 return 语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return,则直接返回最近的外层函数。例如下面两个函数是不同的:

fun returnDemo_1() {
    println(" START " + ::returnDemo_1.name)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach {
        if (it == 3) return
        println(it)
    }
    println(" END " + ::returnDemo_2.name)
}

//1
//2

fun returnDemo_2() {
    println(" START " + ::returnDemo_2.name)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach(fun(a: Int) {
        if (a == 3) return
        println(a)
    })
    println(" END " + ::returnDemo_2.name)
}

//1
//2
//4
//5

returnDemo_1 在遇到 3 时会直接返回(有点类似循环体中的break行为)。最后输出

1
2

returnDemo_2 遇到 3 时会跳过它继续执行(有点类似循环体中的continue行为)。最后输出

1
2
4
5

returnDemo_2 中,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回。

在Kotlin中,这是匿名函数和 lambda 表达式行为不一致的地方。当然,为了显式的指明 return 返回的地址,为此 kotlin 还提供了 @Label (标签) 来控制返回语句,且看下节分解。

3.5.7 标签(label)

在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@jarOfLove@ 都是有效的标签。我们可以用Label标签来控制 returnbreakcontinue的跳转(jump)行为。

Kotlin 的函数是可以被嵌套的。它有函数字面量、局部函数等。 有了标签限制的 return,我们就可以从外层函数返回了。例如,从 lambda 表达式中返回,returnDemo_2() 我们可以显示指定lambda 表达式中的return地址是其入口处。

代码示例:

fun returnDemo_3() {
    println(" START " + ::returnDemo_3.name)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach here@ {
        if (it == 3) return@here // 指令跳转到 lambda 表达式标签 here@ 处。继续下一个it=4的遍历循环
        println(it)
    }
    println(" END " + ::returnDemo_3.name)
}

//1
//2
//4
//5

我们在 lambda 表达式开头处添加了标签here@ ,我们可以这么理解:该标签相当于是记录了Lambda表达式的指令执行入口地址, 然后在表达式内部我们使用return@here 来跳转至Lambda表达式该地址处。
另外,我们也可以使用隐式标签更方便。 该标签与接收该 lambda 的函数同名。
代码示例
fun returnDemo_4() {
println(" START " + ::returnDemo_4.name)
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach {
if (it == 3) return@forEach // 从 lambda 表达式 @forEach 中返回。
println(it)
}

println(" END " + ::returnDemo_4.name)

}

接收该Lambda表达式的函数是forEach, 所以我们可以直接使用 return@forEach ,来跳转到此处执行下一轮循环。
通常当我们在循环体中使用break,是跳出最近外层的循环:
fun breakDemo_1() {
println("--------------- breakDemo_1 ---------------")
for (outer in 1..5) {
println("outer=" + outer)
for (inner in 1..10) {
println("inner=" + inner)
if (inner % 2 == 0) {
break
}
}
}
}
输出
--------------- breakDemo_1 ---------------
outer=1
inner=1
inner=2
outer=2
inner=1
inner=2
outer=3
inner=1
inner=2
outer=4
inner=1
inner=2
outer=5
inner=1
inner=2

当我们想直接跳转到外层for循环,这个时候我们就可以使用标签了。
代码示例
fun breakDemo_2() {
println("--------------- breakDemo_2 ---------------")
outer@ for (outer in 1..5)
for (inner in 1..10) {
println("inner=" + inner)
println("outer=" + outer)
if (inner % 2 == 0) {
break@outer
}
}
}
输出
--------------- breakDemo_2 ---------------
inner=1
outer=1
inner=2
outer=1
有时候,为了代码可读性,我们可以用标签来显式地指出循环体的跳转地址,比如说在breakDemo_1()中,我们可以用标签来指明内层循环的跳转地址:
fun breakDemo_3() {
println("--------------- breakDemo_3 ---------------")
for (outer in 1..5)
inner@ for (inner in 1..10) {
println("inner=" + inner)
println("outer=" + outer)
if (inner % 2 == 0) {
break@inner
}
}
}

3.5.8 throw表达式

在 Kotlin 中 throw 是表达式,它的类型是特殊类型 Nothing。 该类型没有值。跟C、Java中的void 意思一样。

>>> Nothing::class
class java.lang.Void

我们在代码中,用 Nothing 来标记无返回的函数:

>>> fun fail(msg:String):Nothing{ throw IllegalArgumentException(msg) }
>>> fail("XXXX")
java.lang.IllegalArgumentException: XXXX
    at Line57.fail(Unknown Source)

另外,如果把一个throw表达式的值赋值给一个变量,需要显式声明类型为Nothing , 代码示例如下

>>> val ex = throw Exception("YYYYYYYY")
error: 'Nothing' property type needs to be specified explicitly
val ex = throw Exception("YYYYYYYY")
    ^

>>> val ex:Nothing = throw Exception("YYYYYYYY")
java.lang.Exception: YYYYYYYY

另外,因为ex变量是Nothing类型,没有任何值,所以无法当做参数传给函数:

>>> println(ex)
error: overload resolution ambiguity: 
@InlineOnly public inline fun println(message: Any?): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Boolean): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Byte): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Char): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: CharArray): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Double): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Float): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Int): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Long): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Short): Unit defined in kotlin.io
println(ex)
^

>>> ex
exception: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Unregistered script: class Line62
Cause: Unregistered script: class Line62
File being compiled and position: (1,1) in /line64.kts
PsiElement: ex
The root cause was thrown at: ScriptContext.java:86
...

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容