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

一点一滴学习Kotlin之数据类型_第1张图片
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) {
    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) {
    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来定义一个整数的集合,而必须用Collection来定义。
  • Kotlin并不区分基本数据类型和包装类型,使用的永远是同一个类型(如Int):
val i:Int = 1
val list:List = 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) {
    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) {
    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 {...}

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

public interface ClosedRange> {
    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 = arrayOf("I","am","fukq")
    val arrayOfPerson:Array = 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 = 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 = arrayOf(...)
    基本操作:输出print array[i];赋值array[i] = "Hello";长度array.size

12. 后续

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

你可能感兴趣的:(一点一滴学习Kotlin之数据类型)