Kotlin基本数据类型与类型系统


一、什么是类型

  一切皆是映射。
  在计算机中,任何数值都是以一组比特(01)组成的,硬件无法区分内存地址,脚本,字符,整数以及浮点数。这个时候,我们使用类型赋予一组比特以特定的意思。
  类型(type)本质上就是内存中的数值或变量对象的逻辑映射
  到目前为止,我们已经了解了Kotlin的基本符号以及基础语法。我们可以看出,使用Kotlin写的代码更加简洁,可读性更好,更富有生产力。本章我们来学习一下啊Kotlin的基本数据类型与类型系统。
  在计算机科学中,最早的类型系统用来区别数字里面的整数和浮点数。在20世纪五六十年代,这种分类扩展到了结构化的数据和高阶函数中。70年代,引入了几个更为丰富的概念,例如:参数化类型,抽象数据类型,模块系统,子类型等等,类型系统作为一个独立的领域形成了。
  在每一门编程语言中,都有一个特定的类型系统(Type System).类型系统时一门编程语言最核心也是最基础的部分。我们这里说的类型系统可以简单理解为以下两个部分:
  • 一组基本类型构成的PTS(Primary Type Set,基本类型集合)
  • PTS上定义的一系列组合,运算,转换规则等
    这一经典单优雅而又惊人的世界构成观,贯穿了人类现实世界和计算机编程语言所定义的虚拟世界。或许语言的设计者也没有料想到,但是最终的结果确实是有限的设计导出了无限的可能性。
    类型系统用于定义如何将编程语言中的数值和表达式归类为许多不同的类型,如何操作这些类型,这些类型如何互相作用等。
    类型系统提供的主要功能有:安全性,最优化,可读性,抽象化。下面我们针对其中的三个功能解释一下。

    1.安全性

    编辑器可以使用类型来检查无意义的,或者可能无效的代码。例如,在强类型的语言中,如果没有对字符串+进行重载,那么如下表达式:
    "Hellow,World"  + 3
    
就会被编译器检测出来,因为不能对字符串加上一个整数。
但是在Kotlin中,由于Int类型没有对+实现重载,所以情况是这样的:
      ```c
   >>> "Hello,World"+1
   Hello,World1
   >>> 1+"Hello,World"
   >error:none of the following functions can be called  with the arguments supplied:

2.最优化

静态类型检查可提供有用的信息给编译器,编译器可以使用更有效率的机器指令,实现编译器优化

3.抽象化

类型本质上是对较低层次的逻辑单元进行高层次的逻辑抽象。这样我们就可以直接使用类型较高层次的方式思考,而不是繁重的低层次实现。例如我们可以将字符串想成一个值,以此取代仅仅是字节的数组。字符串就是一个抽象数据类型。
从01到类型,从类型到借口API,再到软件服务,都可以看做是广义的“类型”范畴。
程序中的变量在程序执行期间,可能会有不同的取值范围,我们可以把变了可取值的最大范围称为这个变量的类型。例如:具有类型Boolean的变量x,在程序执行期间只能取布尔值。指定变量类型的程序设计语言,称为类型化的语言(typed language)
如果一个语言不限制变量的取值,称为无类型语言(untyped language),我们既可以说它不具有类型,也可以说它具有一个通用类型,这个类型的取值范围是程序中所有可能的值。
类型系统时类型化语言的一个组成部分,它用来计算和跟踪程序中所有表达式的类型,从而判断某段程序是否表现良好(well behaved)。
如果程序语言的语法中含有类型标记,就称该语言时显示类型化的(explicitly typed),否则就称为隐式类型化的(implicitly typed).
像C,C++,Java等语言,都是显式类型化的,而像ML,Haskell,Groovy等语言可以省略声明,他们的类型系统会自动推断出程序等类型。

二、编译时类型与运行时类型

Kotlin是一门强类型的,静态类型,支持隐士类型的显式类型的语言。

1.弱类型与强类型

类型系统最主要的作用是,通过检查类型的运算和转换过程,来减少类型错误的发生。如果一个语言的编译器引入越多的类型检查的限制,就可以称这个语言的类型检查越强,反之越弱。根据类型检查的强弱,我们可将编程语言分为:

  • 弱类型语言(Weakly checked language)
  • 强类型语言(Strongly checked language)
    弱类型语言在运行时会隐式做数据类型转换,强类型语言在运行时会确保不会发生未经明确转换(显式调用)的类型转换。但是,强和弱只是相对的
    Kotlin是强类型语言

2.静态类型与动态类型

类型检查可发生在编译时期(静态检查)或运行时期(动态检查),这样我们可以将编程语言分为:

  • 静态类型语言(Statically checked language)
  • 动态类型语言(Dynamically Checked language)
    静态类型检查是基于编译器来分析源码本身,从而确保类型安全。静态类型检查能让很多bug在编码早期被捕捉到,并且它也能优化运行。因为如果编译器在编译时已经证明程序类型是安全的,就不用在运行时进行动态的类型检查,编译过后的代码会更优化,运行更快。
    动态类型语言在运行时期进行类型标记的检查,因为变量所约束的值,可经由运行路径获得不同的标记
    Kotlin是静态类型语言

3.显示类型与隐式类型

还有一种区分方法是,根据变量名是否需要显式给出类型的声明,将语言分为:

  • 显示类型语言(Explicitly typed language)
  • 隐式类型语言(Implicitly typed language)
    前者需要在定义变量时显式给出变量的类型,而后者可以使用类型推论来确定变量的类型。
    大多数镜头类型语言,例如 Java ,C/C++ 都是显式类型语言,但是有些则不是,如 Haskell XML等,它们可以基于变量的操作来推断其类型;Scala是静态类型语言,它使用类型推断累支持隐式类型。Kotlin与Scala类似,它也使用类型推断支持隐式类型。但是,在一些场景下也需要显式声明变量的类型,所以我们可以说,Kotlin同时也是显式类型语言。显示类型语言与隐式类型语言之间的界限很多时候并不是非显即隐的。当然事物的本身也往往如此。

三、根类型Any

Kotlin中所有类都有一个共同的超类Any,如果类声明时没有指定超类,则默认为Any,我们来看一段代码

    >>> val any =Any()
    >>> any
    java.lang.Object@2e377400
    >>>any::class
    class kotlin.Any
    >>> any::class.java
    class java.lang.Object

也就是说,Any在运行时,其类型自动映射成java.lang.Object。我们知道,在Java中Object类就是所有引用类的父亲。但是不包括基本类型。但是在Kotlin中,直接统一,即所有类型都是引用类型,统一继承父类Any.
Any是java的等价Object类。但是与Java不同的是,Kotlin语言内部的类型和用户定义类型之间,并没有像Java那样划清界限。它们是同一类型层次结构的一部分。
Any只有equals(),hashCode()和toString()三个方法。其源码是:

public open class Any{
        public open operator fun equals(other:Any?):Boolean
        public open fun hashCode():Int
        public open fun toString():String
}

从Any的源码注释中,我们可以看到,判断两个对象是否相等,需要满足以下条件:

  • 自反性:对于任何非空引用值x,x.equals(x)应返回true
  • 对称性:对于任何非空引用值x和y,x.equals(y) 应返回true,y.equals(z)返回true ,那么x.equals(z)也应返回true
  • 一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false
    另外,在Kotlin中,操作符==会被编译器翻译成调用equals函数。

四、基本类型

Kotlin的基础类型(primitive types): 数字,字符,布尔河数组等。
在Kotlin中,一切皆是对象。所有类型都是引用类型。没有类似Java中的基本类型。但是,可以把Kotlin中对应的这几种基本数据类型,理解为Java的基本类型的装箱类
比如 Integer.java----> Kotlin中的Int

1.数字类型

Kotlin提供了如下的内置类型类表示数字(Number)(与java很接近)
类型 宽度(Bit)
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8
Kotlin的数字类型与Java基本相同。有一点不同的是,Kotlin对于数字没有隐式拓宽转换(如Java中int 可以隐式转换为long).
注意,在Kotlin中字符Char不是数字。这些基本数据类型,会在运行时自动优化为Java的double,float,long,int,short,byte.

.字面常量值

数值常量字面值(literal constant values)有以下几种:

  • 十进制:123
  • Long类型用大写L标记:123L
  • 十六进制:0x0F
  • 二进制:0b00001011
    注意:不支持八进制,当我们赋值超过变量的取值范围时,编译器会直接报错。

.显示转换

由于不同的表示方式,值范围较小类型并不是较大类型的子类型,是不能隐式转换的代码示例如下:

>>> val a:Int? =1
>>> val b:Long?=a
error: type mismatch: interred type is Int? but Long? was expected
val b : Long? =a 

>>> val b:Byte  = 1
>>> val i:Int = b
error: type mismatch: interred type is Byte but Int was expected
val i: Int = b

这意味着在不进行显式转换的情况下,不能把Int型值赋给一个Long变量,也不能把Byte型值赋给一个Int变量。
我们可以通过显示转换来拓宽数字:

>>> val i: Int = b.toInt()  //OK:显式拓宽

每个数字类型抖继承Number抽象类,其中定义了如下的转换函数:
toDouble(): Double
toFloat(): Float
toLong(): Long
toInt(): Int
toChar(): Char
toShort(): Short
toByte(): Byte
所以,在数字之间的转换,直接调用上面的这些转换函数即可。

.运算符+重载

缺乏隐式类型转换并不显著,因为类型会从上下文推断出来,而算术运算会有重载做适当转换,例如:

val l =  1L + 3  //Long+Int =>Long

这是通过运算符+重载实现的。我们可以在Long类的源代码中看到这个plus运算符函数的定义:

public operator fun plus(other: Byte):Long
public operator fun plus(other: Short):Long
public operator fun plus(other: Int):Long
public operator fun plus(other: Long):Long
public operator fun plus(other: Float):Float
public operator fun plus(other: Double):Double

也就是说,编译器会把1L + 3翻译成lL.plus(3),然后这个传入的参数类型必须是Byte,Short,Int,Long,Float,Double中的一种,如果传入一个字符Char就会报错。

.运算

Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)。
对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数(infix fun),例如:

val x =  (1 sh1 2)  and 0x000FF000

这是完整的位运算列表(只用于Int和Long)

  • shl(bits)-------有符号左移(Java的<<)
  • shr(bits)-------有符号右移(Java的>>)
  • ushr(bits)------无符号右移(Java的>>>)
  • and(bits)-------位与
  • or(bits)---------位或
  • xor(bits)-------位异或
  • inv()------------位非

2.字符类型与转义符

字符(Character)用Char类型表示。它们不能直接当作数字
字符字面值用单引号括起来: ‘1’
特殊字符可以用反斜杠转义。Kotlin支持如下转义字符(Escape character):
\t , \b , \n ,\r ,\‘ , " , \ $
编码其他字符要用Unicode转义序列语法,例如: ‘\uFF00’

3.布尔类型

Kotlin的布尔类型 用Boolean类来表示,有两个值:true 和 false

4.字符串类型

Kotlin的字符串用String类型表示,对应Java中的java.lang.String()。 字符串是不可变的,代码示例如下:

>>> "abc"::class
class kotlin.String
>>> "abc"::class.java
>class java.lang.String

另外,在Kotlin中,String同样是final不可继承的,代码示例如下:

>>> class MyString:String
error: this type is final ,so it cannot be inherited from 
class MyString:String

.索引运算符s[i]

字符串的元素------字符可以使用索引运算符s[i]来访问:

>>> val s="abc"
>>> s
abc
>>>s[0]
a

当下标越界时,会抛越界错误:

>>>s[-1]
>java.lang.StringIndexOutOfboundsException:String index out of range:-1 at java.lang.String.charAt(String.java:646)

从出错的信息我们可以看出,索引运算符s[i]会被翻译成java.lang.String.charAt(),背后调用的是Java的String类,其调用的方法是:

public char charAt(int index){
    if((index < 0) || (index >= value.length)){
        throw new StringIndexOutOfBoundsException(index);
    }
    return value(index);
}

.for循环迭代字符串

我们可以用for循环迭代字符串:

>>> for(c in "abc"){ println(c)}
a
b
c

.重载+操作符

字符串类重载了+操作符,作用对象可以是任何对象,包括空引用:

>>> "abc".plus(true)
abctrue
>>> "abc"+false
abcfalse
>>> "abc"+1
abc1
>>> "abc"+1.20
>abc1.2
>>> "abc"+100L
abc100
>>> "abc"+"cdef"
abccdef
>>> "abc"+null
abcnull
>>> "abc"+'z'
abcz
>>> "abc"+arrayOf(1,2,3,4,5)
abc[Ljava.lang.Integer;@3d6f0054
截取字符串的字串:
>>> "abc".subSequence(0,1)
a
>>> "abc".subSequence(0,2)
ab
>>> "abc".subSequence(0,3)
abc
>>> "abc".subSequence(0,4)
java.lang.StringIndexOutOfBoundsException:String index out of range:4
    at java.lang.String.substring(Stirng.java:1951)
    at java.lang.String subSequence(String.java:1991)

.字符串字面值

字符串的字面值可以包含原生字符串,可以包含换行符和任意文本,也可以是带有转义字符(escape charactor)的转义字符串,示例代码如下:

>>> val s = "Hello,World!\n\n\n"
>>> s
Hello,World!



转义采用传统的反斜杠方式。
原生字符串使用三个引号(""")分界符括起来,内部没有转义并且可以包含换行和任何其他字符:

>>> val text = """
...   for(c in "abc")
...       print(c)
...   """
>>> text

      for(c in "foo")
          print(c)
>>>

另外,在package kotlin.text下面的Indent.kt代码中,Kotlin还定义了String类的扩展函数:

fun String.trimMargin(marginPrefix: String = "|"):String
fun String.trimIndent():String

我们可以使用trimMargin(),trimIndent()裁剪函数来去除前导空格。可以看出trimMargin()函数默认使用"|“来作为边界字符
默认使用|作为边界前缀,但你可以选择其他字符并作为参数传入,比如trimMargin(”>")。
还有一个trimIndent()函数,则是把字符串的左边空白对齐切割

.字符串模版

字符串可以包含模版表达式,即一些小段代码,会求值并把结果合并到字符串中。
模版表达式以美元符($)开头,由一个简单的名字组成,示例代码如下:

>>> val h=100
>>> val str = "A hundred is $h"
>>> str
A hundred is 100
或者用花括号括起来的任意表达式:
>>> val s = "abc"
>>> val str = "$s.length is ${s.length}"
>>> str
abc.length is 3
原生字符串和转义字符串内部都支持模版:
>>> val price =9.9
>>> val str="""Price is $$price"""
>>> str
Price is $9.9
>>> val str = "Price is $$price"
>>> str
Price is $9.9


>>> val quantity=100
>>> val str="Quantity is $quantity"
str
Quantity is 100
>>> val str="""Quantity is $quantity"""
>>> str
Quantity is 100

5.数组类型

数组在Kotlin中使用Array类来表示,它定义了get和set函数(映射到重载运算符[])和size属性,以及用一个用于变量数组的iterator()函数:

class Array<T> private constructor(){
  val size: Int
  operator fun get(index: Int): T
  operator fun set(index: Int, value: T) : Unit
  operator fun iterator(): Iteratory<T>
  //...
}

可以使用函数arrayOf()来创建一个数组并传递元素值给它。这个函数签名如下:

public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>

其中,vararg表示是一个参数个数是一个变量。
例如,arrayOf(1,2,3)创建了array[1,2,3]:

>>> arrayOf(1,2,3)
[Ljava.lang.Integer;@4a37191a
>>> arrayOf(1,2,3)::class
class kotlin.Array
>>> arrayOf(1,2,3)::class.java
class [Ljava.lang.Integer;

另外,Kotlin还允许不同类型元素放到一个数组中,例如:

>>> val arr = arrayOf(1,"2",true)
>>> arr
[Ljava.lang.Object;@61af1510
>>> arr.forEach{println(it)}
1
2
true
>>> arr.forEach{ println(it::class)}
class kotlin.Int
class kotlin.String
class kotlin.Boolean

Kotlin自动把这个数组元素的类型升级为java.lang.Object,同时,由于Kotlin拥有类型推断的功能,我们仍然可以看到每个数组元素对应的类型。
函数arrayOfNulls()可以用于创建一个指定大小,元素都为空的数组。这个特殊的空数组在创建时,需要指定元素的类型。如果不指定,会报错。
正确写法:arrayofNulls(10)
错误写法:arrayOfNulls(10)

数组Array类还提供了一个构造函数:

public inline constructor(size: Int,init: (Int) -> T)

第一个参数时数组的大小,第二个参数是一个初始化函数类型的参数(关于函数类型,后面会介绍)。
代码示例如下:

>>> val square = Array{10,{i -> (i*i)}}
>>> square
[Ljava.lang.Integer;@6f9e08d4
>>> square.forEach{ println(it)}
0
1
4
9
16
25
36
49
64
81

如上所述,[]运算符代表调用成员函数get()和set()
代码示例如下:

>>> square[3]
9

>>> square[3]=1000
>>> square.forEach{ println(it)}
0
1
4
9
1000
16
25
36
49
64
81

与Java不同的是,Kotlin中数组不是型变的(invariant).Kotlin中,我们不能把Array赋值给Array。这地方Kotlin类型检查的限制强于Java的数组类型。代码示例如下:

>>> val arrstr = arrayOf<String>("1","2","3")
>>> arrstr
[Ljava.lang.String;@39374689
>>> var arrany = arrayOf<Any>(Any(),Any(),Any())
>>> arrany
[Ljava.lang.Object;@156324b
>>> arrany == arrstr
error: type mismatch: inferred type is Array<String> but Array<Any> was expected array == arrstr

原生数组类型
Kotlin也有无装箱开销的专门的类来表示原生类型数组,这些原生数组类如下所示:
BoolleanArray
ByteArray
CharArray
ShortArray
IntArray
LongArray
FloatArray
DoubleArray
BooleanArray
这些类和Any没有继承关系,但它们有同样的函数和属性集,也都有相应的工厂方法

public fun doubleArrayOf(vararg elements: Double):DoubleArray
public fun floatArrayOf(vararg elements: Float):FloatArray
public fun longArrayOf(vararg elements: Long):LongArray
public fun intArrayOf(vararg elements: Int):IntArray
public fun charArrayOf(vararg elements: Char):CharArray
public fun shortArrayOf(vararg elements: Short)ShortArray
public fun byteArrayOf(vararg elements: Byte)ByteArray
public fun booleanArrayOf(vararg elements:Boolean)BooleanArray

代码示例:

>>> val x : IntArray = intArrayOf(1,2,3)
>>> x[0]
1

6.可空类型

.Kotlin中的null

可空类型(Nullable Types)Any?是Kotlin类型系统中的一个特性,主要是为了解决Java中令人头疼的NullPointerException问题。Kotlin把可空性(nullability)作为类型系统的一部分,Kotlin编译器可以直接在编译过程中发现许多可能的错误,并减少在运行时抛出异常的可能性。Kotlin的类型系统和Java相比,首要的区别就是Kotlin对可空类型的显式支持。
在Kotlin中,针对Java中的null的杂乱局面进行了整顿,作了清晰的界定,并在编译器级别强制规范了可空null变量类型的使用。
我们来看一下Kotlin中关于null的一些有趣的运算。
null和null是相等的:

>>> null == null
true
>>> null!=null
false
null这个值比较特殊,null不是Any类型
>>> null is Any
false
但是,null是Any?类型:
>>> null is Any?
true
我们来看null对应的类型到底是什么:
>>> var a=null
>>> a
null
>>> a=1
error: the integer literal does not conform to the expected type Nothing?
a=1
从报错信息可以看出,null的类型是Nothing?。关于Nothing?我们将在后面介绍.
我们可以对null进行加法运算:
>>> "1"+null
1null
>>> null+20
null20
对应的重载运算符的函数定义在kotlin/Library.kt里面
package kotlin
import kotlin.internal.PureReifiable
public fun Any?.toString(): String
public operator fun String?.plus(other: Any?): String
但是反过来就不行了
>>> 1+null
error:none of the following functions can be called with the arguments supplied:
public final operator fun plus(other: Byte): Int defined in kotlin.kt
1+null

这是因为Int没有重载传入null参数的plus()函数

.可空类型String?与安全调用?.

我们来看一个例子,下面是计算字符串长度的简单Java方法:

public static int getLength1(String str){
         return str.length;
}
我们已经习惯了在这样的Java代码中,加上这样的空判断处理:
public static int getLength2(String str) throws Exception{
         if(null == str){
                throw new Exception("str is null");
         }
          return str.length();
}
而在Kotlin中,当同样写一个可能为null参数的函数时:
fun getLength1(str: String): Int{
          return str.length
}
当传入一个null参数时:
@Test fun testGetLength1(){
       val StringUtilKt = StringUtilKt()
       StringUtilKt.getLength1(null)
}
编译器就会直接编译失败

Kotlin就是这样通过编译时强制排除空指针的错误,大大减少了出现NPE的可能。
另外,如果我们确实需要传入一个可空的参数,可以使用可空类型String?来声明一个可以指向空指针的变量。
可空类型可以用来标记任何一个变量,来表明这个变量时可空的(Nullable)。例如: Char?,Int?,MineType?(自定义类型)等等。
用示例代码更加简洁的说明:

>>> var x:String="x"
>>> x=null
error: null can not be a value of a non-null type String
x=null

>>> var y:String?="y"
>>> y=null
>>> y
null
可以看出:普通String类型时不允许指向null的,而可空String?类可以指向null.



下面我们来尝试使用一个可空变量来调用函数:
>>> fun getLength2(str: String?): Int? = str.length
error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable
      receiver of tye String?
fun getLenght2(str: String?): Int? = str.length        
编译器直接报错,告诉我们,变量str:String?是可空的类型,调用只能通过安全调用(?.)或者非空断言调用(!!.)

另外,如果不需要捕获异常来处理,我们可以使用Kotlin里面的安全调用符(?.) 代码示例

fun getLength2(str: String?): Int? {
    return str?.length
}
测试代码:
@Test fun testGetLength2(){
     val StringUtilKt = StringUtilKt()
     println(StringUtilKt.getLength2(null))//null
     Assert.assertTrue(3 == StringUtilKt.getLength2("abc"))
}

如果我们确实想写一个出现空针针异常的代码,那就使用可能出现空指针的断言调用符(!!.)。
代码示例:

fun getLength3(str: String?): Int?{
      return str!!.length
}
测试代码:
@Test fun testGetLength3(){
val StringUtilKt = StringUtilKt()
println(StringUtilKt.getLength3())
Assert.assertTrue(3 == StringUtilKt.getLength3("abc"))
}
上面的代码就与Java里面的差不多了,运行会抛出空指针异常
kotlin.KotlinNullPointerException
     at com.easy.kotlin.StringUtilKt.getLength3(StringUtilKt.kt:16)
     at com.easy.kotlin.StringUtilKtTest.testGetLength3(StringUtilKtTest.kt:28)

当然如果需要捕获异常 try-catch依然可以

可空性的实现原理

我们来看一段Kotlin的可空类型的示例代码如下:

fun testNullable1(x: String, y: String?): Int{
  return x.length
}
fun testNullable2(x:String, y: String?): Int{
 return y?.length
}
fun testNullable3(x: String,y: String?): Int{
 return y!!.length
}
我们使用AndroidStudio的kotlin插件来看一下可空类型的安全调用的Java代码
经过转换后的java代码如下
public final class NullableTypesKt{
public static final int testNullable1(@NotNull String x, @Nullable String y){
   Intrinsics.checkParameterIsNotNull(x,"x")
   return x.length();
}
@Nullable
public static final Integer testNullable2(@NotNull String x, @Nullable String y){
    Intrinsics.checkParameterIsNotNull(x,"x")
    return y != null ? Integer.valueOf(y.length()):null;
}
@Nullable
public static final Integer testNullable3(@NotNull String x, @Nullable String y){
    Intrinsics.checkParameterIsNotNull(x,"x")
    if(y == null){
            Intrinsics.throwNpe();
     }
     return Integer.valueOf(y.length());
}
}

在不可空变量调用函数之前,都检查了是否为空,使用的是kotlin.jvm.internal.Intrinsics 这个Java类里面的checkParameterIsNotNull方法。如果是null就抛出异常。

public static void checkParameterIsNotNull(Object value,String paramName){
    if(value == null){
        throwParameterIsNullException(paramName);
    }
}

.可空类型层次体系

就像Any是在非空类型层次结构的根,Any?是可空类型层次的根。由于Any?是Any的超集,所以,Any?是Kotlin的类型层次结构的最顶端
代码示例如下:

>>> 1 is Any
true

>>> 1 is Any?
true

>>> null is Any
false

>>> null is Any?
true

>>> Any() is Any?
true

7.kotlin.Unit类型

Kotlin也是面向表达式的语言。在Kotlin中所有控制流语句都是表达式(除了变量赋值,异常等)。
Kotlin中的Unit类型实现了与Java中的void一样的功能。不同的是,当一个函数没有返回值时,我们用Unit来表示这个特征,而不是null。
大多数时候,我们并不需要显示地返回Unit,或者声明一个函数的返回类型为Unit。编译器会推断出它。
代码示例:

>>> fun unitExample() {println("Hello,Unit")}
>>> val helloUnit = unitExample()
Hello,Unit
>>> helloUnit
kotlin.Unit
>>>println(helloUnit)
kotlin.Unit

下面几种写法是等价的

fun unitReturn1(){
   //什么都不写
}
fun unitReturn2(){
  return Unit //显示地返回Unit
}
fun unitReturn3(): Unit{ //函数声明的时候返回Unit类型
}

总的来说 Unit并没有什么特别之处

8.kotlin.Nothing类型

Kotlin中没有Java和C中的相类似函数没有返回值的标记void,但是拥有一个对应Nothing。在Java中,返回void的方法,其返回值void是无法被访问到的
在Java中,void不能是变量的类型。也不能被当做值打印输出。但是,在Java中有个包装类Void和void的自动装箱类型。如果你想让一个方法返回类型永远是null的话,可以把返回类型置为这个大写的V的void类型

public Void voidDemo(){
  System.out.println("Hello,Void");
  return null;
}

这个Void就是Kotlin中的Nothing? 它的唯一可被访问到的返回值也是null.
在Kotlin类型层次结构最底层就是类型Nothing.
正如它的名字Nothing所暗示的,Nothing是没有实例的类型

>>> Nothing()  is Any
error: cannot access '<init>': it is private in 'Nothing'
Nothing() is Any

Nothing?可以只包含一个值: null 。代码示例:

>>> var nul:Nothibng?=null
>>> nul = 1
error: the integer literal does not conform to the expected type Nothing?
nul = 1

>>> nul = true
error: the boolean literal does not conform to the expected type Nothing? 
nul = true

>>> nul = null
>>> nul
null

从上面的代码示例中,我们可以看出:Nothing?唯一允许的值是null,被用作任何可空类型的空引用
综上所述,我们可以看出Kotlin有一个简单而一致的类型系统。Any?是整个类型体系的顶部,Nothinbg是底部

9.类型检测与类型转换

.is 与 !is运算符

is运算符可以检查对象是否与特定的类型兼容("兼容"的意思是:此对象是该类型或者派生于该类型)
is 运算符用来检查对象(变量)是否属于某种数据类型(如Int  String Boolean等)
is 运算符类似于Java的instanceof

在Kotlin中,我们可以在运行时使用is操作符或其否定形式!is来检查对象是否符合给定类型

>>> "abc" is String
true
>>> "abc" !is String
false

>>> null is Any
false
>>> null is Any?
true

代码示例:

@RunWith(JUnit4::class)
class ASOperatorTest{
    @Test fun testAS(){
       val foo = Foo()
       val goo = Goo()
       println(foo is Foo)//true 自己
       println(goo is Foo)//子类 is 父类 = true
       println(foo is Goo)//父类 is 子类 = false
       println(goo is Goo)//true 自己
   }
}
open class Foo
class Goo : Foo()

类型自动转换
在Java代码中,当我们使用 str instanceof String来判断其值的时候,要想使用str变量,还需要显式的强制转换类型
而大多数 情况下,我们不需要在Kotlin中使用显式转换操作符,因为编译器会跟踪不可变值的is检查,并在需要时自动插入(安全的)转换:

@Test fun testIS(){
     val len = strlen("abc")
     println(len)//3
     val lens = strlen(1)
     println(lens)//1
}

fun strlen(ani: Any): Int{
  if(ani is String){
      return ani.length
  } else if(ani is Number){
      return ani.toString().length
  } else if(ani is Char){
      return 1
  } else if(ani is Boolean){
      return 1
  }
   println("Not A String)
   return -1
}

.as运算符

as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;如果类型不兼容,使用as?运算符就会返回null
代码示例:

>>> open class Foo
>>> class Goo : Foo()
>>> val foo = Foo()
>>> val goo = Goo()

>>> foo as Goo
java.lang.ClassCastException: Line69$Foo cannot be cast to Line7$Goo

>>> foo as? Goo
null

>>> goo as Foo
Line71$Goo@73dce0e6

我们可以看出,在Kotlin中,子类是禁止转换为父类型的。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐