Kotlin从入门到精通 | 第四章 类型初步

382 阅读4分钟

本章节主要介绍Kotlin的类型定义和简单使用

类的定义

Kotlin类默认为public,类内无内容可省略

20211117104518

Kotlin类成员变量,方法同Java类似

20211117104610

Kotlin类默认带无参构造器,如果需要定义其他构造器,使用constructor关键字创建

20211117104737

也可以直接定义到类上 20211117104843

类的实例化

直观感受是,省略了new关键字,获得对象再也不需要new

Java

SimpleClass simpleClass = new SimpleClass(9);
System.out.println(simpleClass.x);
simpleClass.y();

Kotlin

val simpleClass = SimpleClass(9)
println(simpleClass.x)
simpleClass.y()

接口的定义

基本和Java一致

20211117105205

接口的实现

  • implements关键字换成了:
  • @override注解换成了override关键字

20211117105309

抽象类的定义

由于Kotlin的类默认是final,所以需要添加open关键字,使之可以被继承

20211117105425

类的继承

  • extends关键字换成了:,跟接口实现保持了一致
  • 需要调用被继承方的构造器(默认为无参构造器)

20211117105546

类的属性(成员变量)

  • var:默认带gettersetter
  • val:默认带getter

也可以自己定义gettersetter方法

class Person(age: Int, name: String) {
  var age: Int = age
    get() {
      return field
    }
    set(value) {
      field = value
    }
  var name: String = name
}

属性引用

fun main() {
    val ageRef = Person::age // 未绑定 Receiver
    val person = Person(18, "Bennyhuo")
    val nameRef = person::name // 绑定 Receiver
    // 属性引用
    ageRef.set(person, 20)
    nameRef.set("Andyhuo")
}

接口属性

接口可以定义属性,但是不能赋值

interface Guy {
    var moneyLeft: Double
        get() {
            return 0.0
        }
        set(value) {
        }

    fun noMoney() {
        println("no money called.")
    }
}

接口属性没有backing field

我们尝试跟上面的类一样定义gettersetter方法

interface Guy {
    var moneyLeft: Double
        get() {
            return field
        }
        set(value) {
            field = value
        }

    fun noMoney() {
        println("no money called.")
    }
}

将会获得编译器提示"Property in an interface cannot have a backing field" 20211117111756

扩展方法

这是Kotlin的大杀器,非常好用,一些工具类完全可以用扩展方法来替代,并且使用起来非常方便

可以为现存的类定义新的方法

operator fun String.minus(right: Any?) = this.replaceFirst(right.toString(), "")

operator fun String.times(right: Int): String {
    return (1..right).joinToString("") { this }
}

operator fun String.div(right: Any?): Int {
    val right = right.toString()
    return this.windowed(right.length, 1, transform = {
        it == right
    }) // [false, false, false, false ... false, true, ..., true]
        .count { it }
}

fun main() {
    val value = "HelloWorld World"

    println(value - "World")
    println(value * 2)

    val star = "*"
    println("*" * 20)

    println(value / 3)
    println(value / "l")
    println(value / "ld")
}

空指针安全特性

空类型安全

注意:String和String?不是一个类型

var nonNull: String = "Hello"
nonNull = null // 编译器报错
var nullable: String? = "Hello"
nonNull = null // 编译通过

强转为不可空类型

var nullable: String? = "Hello"
val length = nullable!!.length

安全访问

var nullable: String? = "Hello"
nullable = null
val length = nullable?.length

?.的语法结构,我最早是在TypeScript里面看到的,基本逻辑就是,如果为空,则不会继续往下执行,一定程度上杜绝了NPE异常,而现在Kotlin把它引入了,非常棒

elvis运算符(?:)

var nullable: String? = "Hello"
nullable = null
val length: Int = nullable?.length ?: 0
  • nullable == null 时,length = 0
  • nullable != null 时,length = nullable!!.length

平台类型

Kotlin对于Java类库,是百分百支持的,但是Java里面没有String?这种类型,Kotlin怎么处理呢?是当成非空还是可空类型? 答案是:Kotlin编译器不判断,有用户自己来判断是否可空还是非空 比如在Java里面定义了一个Person

public class Person {
  public String getTitle() {
    // ...
  }
}

那么在Kotlin里面调用时,person.title的类型是String!,这个String!就是平台类型,可以非空也可以可空

val person = Person()
val title: String! = person.title

智能类型转换

自动转换为子类型

val kotliner: Kotliner = Person("benny", 20) // Person是Kotliner的子类
if(kotliner is Person) {
  println(kotliner.name) // 自动转换类型为 Person,不用向Java一样进行强转
}

可空转非空

var value: String? = null
value = "benny"
if(value != null) {
  println(value.length) // 可空转非空,所以我们也可以说,非空类型是对应的可空类型的子类
}

不支持智能类型转换的场景

在线程不安全的调用下,智能类型转换失效

var tag: String? = null
fun main() {
  if(tag != null) {
    println(tag.length) // 虽然判断不为空,但是其他线程可能对它进行修改
  }
}

类型的安全转换(as?)

val kotliner: Kotliner = ...
println(kotliner as? Person).name) // 安全转换,失败返回null

针对类型的智能转换,有几个建议

  • 尽可能使用val来声明不可变引用,让程序的含义更加清晰确定
  • 尽可能减少函数对外部变量的访问,也为函数式编程提供基础(函数式编程的精髓就是拒绝副作用)
  • 必要时创建局部变量指向外部变量,避免因它变化引起程序错误