美文网首页
一点一滴学习Kotlin之数据类型

一点一滴学习Kotlin之数据类型

作者: 付凯强 | 来源:发表于2018-12-10 19:39 被阅读0次
    Kotlin.jpg

    0. 序言

    • IDE:IntelliJ Idea。
    • 目标:认识基本类型;认识类及其相关概念;认识区间和数组
    • Kotlin版本:Version: 1.2.51-release-IJ2018.2-1
    • 主要内容:
      • Boolean
      • Number类型
      • 拆箱、装箱
      • 字符串
      • 转换
      • 类和对象
      • 空类型
      • 类型转换
      • 区间
      • 数组

    1. Boolean

    val aBoolean:Boolean = true // aBoolean 变量的名称 Boolean 变量的类型 true 变量的值
    val anotherBoolean:Boolean = false
    
    fun main(args: Array<String>) {
        println(aBoolean)
        println(anotherBoolean)
    }
    

    说明:在java中有boolean和装箱类型Boolean,而Kotlin中只有Boolean,装箱的操作Kotlin会帮忙处理。

    2. Number类型

    包括Int、Long、Float、Double

    val aBoolean:Boolean = true // aBoolean 变量的名称 Boolean 变量的类型 true 变量的值
    val anotherBoolean:Boolean = false
    
    val aInt:Int = 8
    val anotherInt:Int = 0XFF
    val moreInt:Int = 0b00000011
    val maxInt:Int = Int.MAX_VALUE
    val minInt:Int = Int.MIN_VALUE
    
    val aLong:Long = 12324567899545
    val anotherLong:Long = 123
    val maxLong:Long = Long.MAX_VALUE
    val minLong:Long = Long.MIN_VALUE
    
    val aFloat:Float = 2.0F
    val anotherFloat = 1E3F
    val maxFloat:Float = Float.MAX_VALUE
    val minFloat = - Float.MAX_VALUE
    
    val aDouble:Double = 2.0
    val anotherDouble:Double = 3.1415926
    val maxDouble:Double = Double.MAX_VALUE
    val minDouble:Double  =  Double.MIN_VALUE
    
    fun main(args: Array<String>) {
        println(aBoolean)
        println(anotherBoolean)
    
        println(aInt)
        println(anotherInt)
        println(moreInt)
        println(maxInt)
        println(minInt)
    
        println(aLong)
        println(anotherLong)
        println(maxLong)
        println(minLong)
    
        println(aFloat)
        println(anotherFloat)
        println(maxFloat)
        println(minFloat)
    
        println(0.0F/0.0F)
    }
    
    true
    false
    8
    255
    3
    2147483647
    -2147483648
    12324567899545
    123
    9223372036854775807
    -9223372036854775808
    2.0
    1000.0
    3.4028235E38
    -3.4028235E38
    NaN
    

    说明:NaN指不是数的数,和谁相比都是false,因为没有意义

    3. 拆箱、装箱

    • 一个基本数据类型(如Int)的变量直接存储了它的值,而一个引用类型(如String)的变量存储的是指向包含该对象的内存地址的引用。
    • Java对基本数据类型和引用数据类型做了区分。基本数据类型的值能够更高效地存储和传递,但是不能对这些值调用方法,或是把它们存放在集合中。Java提供了特殊的包装类型(如Integer),在你需要对象的时候对基本数据类型进行封装,就像你不能用Collection<int>来定义一个整数的集合,而必须用Collection<Integer>来定义。
    • Kotlin并不区分基本数据类型和包装类型,使用的永远是同一个类型(如Int):
    val i:Int = 1
    val list:List<Int> = listOf(1,2,3)
    1
    [1, 2, 3]
    

    说明:Kotlin实际上是做了拆装箱的封装,对于变量、属性、参数和返回类型——Kotlin的Int类型会被编译程Java基本数据类型Int;对于用作泛型类型参数的基本数据类型会被编译成对应的Java包装类型

    • Kotlin中能对一个数字类型的值调用方法,如下面这段代码,使用了标准库的函数coerceIn来把值限制在特定范围中:
    fun showProgress(progress:Int){
        val percent =progress.coerceIn(0,100)
        println("We are ${percent}% done!")
    }
    showProgress(150)
    We are 100% done!
    
    • Kotlin对应到Java基本数据类型的类型完整列表如下:
    ① 整数类型:Byte、Short、Int、Long
    ② 浮点数类型:Float、Double
    ③ 字符类型:Char
    ④ 布尔类型:Boolean
    

    4. 字符串

    • 值和地址值比较
    val string:String = "HelloWorld"
    val fromChars:String = String(charArrayOf('H','e','l','l','o','W','o','r','l','d'))
    
    println(string == fromChars)
    println(string === fromChars)
    
    true
    false
    

    说明:两个等号"==",在Kotlin中比较的是值。而三个等号"==="比较的是地址值。

    • 模板
    val arg1:Int = 0
    val arg2:Int = 1
    println("$arg1 +$arg2 = ${arg1 +arg2}")
    

    说明;用符号来引入变量,用{}来引入表达式。

    • 转义
    val sayHello = "Hello \"MAOZHUXI\""
    Hello "MAOZHUXI"
    
    • 三引号用来打印原始字符串(相当于转移以后的字符串):
    val rawString:String = """
            \t
            \n
    """
    
        \t
        \n
    
    • 字符串长度
        println(sayHello.length)
        16
    

    5. 转换

    • Kotlin不支持隐式转换:
    val A = 1
    val B:Long = A
    

    说明:这里IDE会告知错误。所以必须显示转换

    val A = 1
    val B:Long = A.toLong()
    

    说明:Kotlin为每一种基本数据类型(Boolean除外)都定义有转换函数:toByte()、toShort()、toChar()等,并且支持双向转换,即Long.toInt()或Int.toLong().

    • 注意:当你书写数字字面值的时候,一般不需要使用转换函数:有以下四种情况。

    ① 用特殊的语法来显示地标记常量的类型,比如42L或者42.0f。
    ② 及时没有用以上特殊语法,当你使用数字字面值去初始化一个类型已知的变量时:

    val b:Byte = 1
    

    ③ 把字面值作为实参传给函数时,必要的转换会自动地发生:

    fun foo(l:Long) = println(l)
    foo(42)
    
    42
    

    ④ 算术运算符被重载了,可以接收所有适当的数字类型:

    val b:Byte = 1
    val l = b +1L
    println(l)
    
    2
    

    6. 类和对象

    • 利用构造方法初始化成员变量
    class Man constructor(var name: String, var age: Int){
    
    }
    

    说明:只有一个构造方法的话,constructor也是可以省略的

    class Man (var name: String, var age: Int){
    
    }
    

    说明:如果类中没有内容,大括号也是可以省略的

    class Man (var name: String, var age: Int)
    
    • 初始化类对象不用new
    val mPerson:Man = Man("fukq",28)
    

    说明:当然这里我们可以省略第一个Person类型:

    val mPerson = Man("fukq",28)
    
    • init方法:实际上就是构造方法的方法体,每次创建一个对象调用构造方法的时候都会调用init方法
    class Man (var name: String, var age: Int){
        init {
            println("my name is $name ,my age is $age")
        }
    }
    
    my name is fukq ,my age is 28
    
    • 继承

    ① 首先定义一个父类

    class People(var name:String,var age: Int)
    

    ② 让子类继承父类:借助符号":"

    class Man (var name: String, var age: Int):People(name,age){
        init {
            println("my name is $name ,my age is $age")
        }
    }
    

    说明:用":"继承父类,并把父类的构造方法写出来,并且子类构造方法中的参数类型要省略,因为已经在父类中存在了对应的参数:

    class Man(name: String, age: Int) : People(name, age) {
        init {
            println("my name is $name ,my age is $age")
        }
    }
    

    说明:除此之外,类默认是final的,所以这个时候我们要声明父类为open:

    open class People(var name:String, var age: Int)
    
    • 类名的调用:
    open class People(var name: String, var age: Int) {
        init {
            println("new 了一个${this.javaClass.simpleName}, my name is $name ,my age is $age")
        }
    }
    
    • 任何类都是Any类的子类:
      ① 和Object作为Java类层级结构的根差不多,Any类型是Kotlin所有非空类型的超类型(非空类型的根)
      ② 但是在Java中,Object只是所有引用类型的超类型(引用类型的根),基本数据类型并不是类层级结构的一部分。当你需要Object的时候,不得不把基本数据类型包装成java.lang.Integer这样的包装类型来表示基本数据类型的值。
      ③ 与Java不同,Kotlin中,Any是所有类型的超类型(所有类型的根),包括像Int这样的基本数据类型。
      当然,把基本数据类型的值赋给Any类型的变量时会自动装箱:
    var answer:Any = 42
    

    ④ Any类型是非空类型的根,所以Any类型的变量不可以持有null值。如果需要持有任何可能值的变量,包括null,必须使用Any?类型。

    var answer:Any? = null
    

    说明:如果你不添加?IDE会提示报错
    ⑤ 在底层:Any类型对应java.lang.Object,Kotlin把Java方法参数和返回类型中用到的Object类型都看做Any类型,所以当Kotlin使用Any类型的时候,它会编译成Java字节码中的Object。
    ⑥ 所有Kotlin类都包含三个方法:toString、equals和hashCode:这些方法都继承自Any,Any并不能使用其他java.lang.Object的方法(比如wait和notify),但是可以通过手动把值转换程java.lang.Object来调用这些方法。因为Any定义了这三个方法:

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

    7. 空类型

    • 先看看Java的判空
    public class Test_Java {
        public  static void main(String[] args){
            System.out.println(getName().length());
        }
    
        private static String getName(){
            return null;
        }
    }
    

    说明:Java中当你调用可能是null的值的方法或者属性的时候,容易因为值是null而报运行时异常:空指针。如何防止呢:

    public class Test_Java {
        public  static void main(String[] args){
            String name = getName();
            if (name == null){
                System.out.println("name不能为空");
            }else {
                System.out.println(getName().length());
            }
        }
    
        private static String getName(){
            return null;
        }
    }
    

    说明:Java中你是可以随意的return内容,包括null。

    • 再看看Kotlin的判空
    fun getName():String{
        return null
    }
    

    说明:当你在方法中返回一个null值的时候编译器会报错,所以在Kotlin中不能随意return内容,与Java不同。

    fun getName():String{
        return "fukq"
    }
    println(getName().length)
    

    说明:在编译期就防止了如空指针这样的运行时异常,让程序更加安全,不会因运行时异常崩溃。

    • Kotlin方法返回空类型

    ① 运用"?"符号来指明这个字符串,即方法返回的结果可以是null

    fun getName():String?{
        return null
    }
    

    ② 当方法返回可能是null的时候,我们就要对返回的数据做处理,不然编译会报错:

     val name = getName()
        if (name ==null){
            println("name不能为空")
        }else{
            println(name.length)
        }
    

    说明:进行了空和非空的判断,程序编译无报错,但是Kotlin提供了更简洁的写法:

    println(name?.length)
    

    说明:利用安全调用运算符"?.",它允许你把一次null的检查和一次方法调用合并成一个操作。编译期不会报错,它的意思是非空的时候调用方法length,空的时候打印null,不会报空指针异常。很安全,就算给方法返回null,程序也不会crash。
    ③ 当方法返回是null的时候,执行return或者其他

        val name = getName()
        if (name ==null) return
        println(name.length)
    

    说明:以上是常规写法;Kotlin有方便的Elvis运算符"?:"来处理这种情况

        val name = getName() ?: return
        println(name.length)
    
        val name = getName() ?: ""
        println(name.length)
    

    ④ 当你确认可空类型不为null的时候,可使用非空断言“!!"符号:

    fun hello(s:String?){
        val sNotNUll:String = s!!
        println(sNotNUll.length)
    }
    

    说明:示例中的s是一个可空类型,但是你确信你传进来的是非空类型,这时候可以使用非空断言"!!"告诉编译器不用检查是否是null。另外你要注意的是假如你判断错误,运行时会抛出空指针异常NullPointerException。所以非空断言告诉编译器:"我知道这个值不为null,如果我错了我准备好了接收这个异常“。

    • 综上:
      val notNull:String = null //错误,不能为空
      val nullable:String?=null //正确,可以为空
      notNull.length //正确,不为空的值可以直接使用
      nullable.length // 错误,可能为空,不能直接获取长度
      nullable!!.length //正确,强制认定nullable不可空,认定错误会报空指针
      nullable?.length //正确,若nullable为空,返回空

    8. 类型转换安全

    • Java 类型转换:
      ① 定义Parent类:
    public class Parent {
    }
    

    ② 定义Child类:

    public class Child extends Parent {
    
        public String getName(){
            return "fukq";
        }
    }
    

    ③ 定义类型转换测试类TypeCast:

    public class TypeCast {
        public static void main(String[] args){
            Parent parent = new Child();
            ((Child) parent).getName();
    
            if (parent instanceof Child){
                ((Child) parent).getName();
            }
        }
    }
    

    说明:你会发现已经判断parent是Child实例了,但是后面的代码还是要强转才行。

    • Kotlin类型智能转换:
        val parent:Parent = Child()
        if (parent is Child){
            println(parent.name)
        }
    

    说明:你会发现Kotlin是智能转换,不用强转。

    • Kotlin类型利用"as"强转:
        val parent = Parent()
        val child:Child = parent as Child
        println(child.name)
    
    Exception in thread "main" java.lang.ClassCastException: Parent cannot be cast to Child
        at Test_Kotlin_01Kt.main(Test_Kotlin_01.kt:135)
    

    说明:那我强转失败,不想转换抛出异常:

        val parent = Parent()
        val child:Child? = parent as? Child
        println(child)
    
    null
    

    说明:这个时候我们收到的就是null,而不是异常。

    • 综上:
      一旦认定类型,就不用再次强转。
      利用as?,类型转换失败返回null而不是异常。

    9. 包

    这里讲解包主要讲解如何给包指定其他名称,即修改包的命名空间

    • 创建包Beijing和Tianjin,里面都有XiaoMing类:
    class XiaoMing(var age:Int){
    
    }
    
    • 在Tianjin包里面创建Test类:
    package TianJin
    
    fun main(args: Array<String>) {
        val xiaoming_tj:XiaoMing = XiaoMing(50)
        val xiaoming_bj:Beijing.XiaoMing = Beijing.XiaoMing(60)
    }
    

    说明:会发现在Tianjin的包里面,Beijing的XiaoMing要展示全路径代码,因为示例的包结构层次不是很深,实践中的包结构层次如果深,那Beijing的XiaoMing显示全路径的话,代码会变得很长,所以这里我们利用“as"修改它的命名空间:

    package TianJin
    
    import Beijing.XiaoMing as BJ_XiaoMing
    
    fun main(args: Array<String>) {
        val xiaoming_tj:XiaoMing = XiaoMing(50)
        val xiaoming_bj:BJ_XiaoMing =BJ_XiaoMing(60)
    }
    

    10. 区间

    • 利用".."生成闭区间,返回值类型是IntRange
        val range:IntRange = 0..1024 // 闭区间 [0,1024]
    
    • 利用”util“生成右开区间,返回值是IntRange
    val range_exclusive:IntRange = 0 until 1024 //右开区间[0,1024)
    

    说明:IntRange继承的是ClosedRange

    public class IntRange(start: Int, endInclusive: Int) : 
    IntProgression(start, endInclusive, 1), ClosedRange<Int> {...}
    

    说明:ClosedRange接口中有两个方法

    public interface ClosedRange<T: Comparable<T>> {
        public val start: T
        public val endInclusive: T
        public operator fun contains(value: T): Boolean = value >= start && value <= endInclusive
        public fun isEmpty(): Boolean = start > endInclusive
    }
    

    说明:
    ① contains运用:

      val range:IntRange = 0..1024 // 闭区间 [0,1024]
     println(range.contains(50)) 
     true
    

    ② isEmpty()运用

    val emptyRange:IntRange = 0..-1
    println(emptyRange.isEmpty())
    true
    

    ③ contains返回一个operator,这个operator对应的运算符号是"in"

        println(range.contains(50))
        println(50 in range)
    

    说明:以上两句话是等价的。

    • 迭代
        for (i in range){
            println("$i ")
        }
    
    • 综上:
      区间是ClosedRange的子类,其中IntRange最常用
      0..100 表示[0,100]
      0 until 100 表示[0,100)
      i in 0..100 判断i是否在区间[0,100]中

    11. 数组

    • 基本数据类型的数组
        val arrayOfInt:IntArray = intArrayOf(1,3,5,7)
        val arrayOfChar:CharArray = charArrayOf('H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd')
    

    说明:为了避免拆箱装箱带来的性能开销,特设数组定制版

    • 引用数据类型的数据
        val arrayOfString:Array<String> = arrayOf("I","am","fukq")
        val arrayOfPerson:Array<Child> = arrayOf(Child(),Child(),Child())
    
    • 长度
    println(arrayOfInt.size) //长度
    
    • 迭代
    for(i in arrayOfInt){
            println("$i ")
        }
    
    • 索引获取元素
    println(arrayOfPerson[1])
    

    说明:这样打印出来的内存地址值,想打印字段都信息,可以在类中复写toString方法

    class XiaoMing(var age:Int){
    
        override fun toString(): String {
            return "XiaoMing的年龄是$age"
        }
    }
    val arrayOfPerson:Array<XiaoMing> = arrayOf(XiaoMing(50),XiaoMing(60),XiaoMing(70))
    
    XiaoMing的年龄是60
    
    • 更改元素值
    arrayOfPerson[1] = XiaoMing(100)
    
    • 把字符拼接为字符串
    rrayOfChar.joinToString()
    H, e, l, l, o, W, o, r, l, d
    

    说明:看下源码

    public fun CharArray.joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((Char) -> CharSequence)? = null): String {
        return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
    }
    

    说明:其实是一个StringBuilder拼接,默认分隔符是", ",改为空

    println(arrayOfChar.joinToString(""))
    
    • 切片
    println(arrayOfInt.slice(1..2))
    [3, 5]
    
    • 综上:
      基本写法:val array:Array<String> = arrayOf(...)
      基本操作:输出print array[i];赋值array[i] = "Hello";长度array.size

    12. 后续

    如果大家喜欢这篇文章,欢迎点赞!
    如果想看更多 Kotlin 方面的技术,欢迎关注!

    相关文章

      网友评论

          本文标题:一点一滴学习Kotlin之数据类型

          本文链接:https://www.haomeiwen.com/subject/sigcoftx.html