和Java一样,kotlin也使用class声明类,我们声明一个Student类并给它增加一些属性:

class Student {
    var age = 0
    var name = ""
}
//创建对象并修改属性
val stu = Student()
stu.name = "lxj"

我们给Student类增加了非私有属性,Kotlin会自动生成属性的setter和getter,通过IDEA提供的工具Show Kotlin Bytecode查看生成的字节码即可得知。

构造函数

既然是类一定少不了构造函数,想通过构造函数给一个类的对象传参可以这样写:

class Student (age: Int, name: String) {
    var age = 0
    var name = ""
}

在类名后面加个小括号就是构造函数了,但是类名后面的构造函数不能包含代码,那么如何将传入的agename赋值给自己的属性呢?

Kotlin提供了init代码块用来初始化一些字段和逻辑,init代码块是在创建对象时调用,我们可以在这里对属性进行赋值:

class Student (age: Int, name: String){
    var age = 0
    var name = ""
	//在init代码块中来初始化字段
    init {
        this.age = age
        this.name = name
    }
}

但是这样的写法略显啰嗦,Kotlin作为一种现代化的编程语言,肯定有更简洁的写法。其实一个类的属性定义和构造传参通常可以简写为这样:

class Student (var age: Int, var name: String) //加个var关键字即可,如果你不想属性被更改就用val

上面的写法要求我们创建对象时必须传递2个参数,如果希望传参是可选的,那么可以给属性设置默认值:

class Student (var age: Int = 0, var name: String = "")
val stu = Student() //不用传参也可以
val stu1 = Student(name = "lxj")
val stu2 = Student(age = 20, name = "lxj")

TIP

像上面那样,如果给构造函数的所有参数都设置了默认值,Kotlin会额外生成一个无参构造,所有的字段将使用默认值。这对于有些需要通过类的无参构造来创建实例的框架非常有用。

一般来说,一个类是可以有多个构造函数的,那么Kotlin的类如何编写多个构造函数呢?

像上面那样直接在类名后面写的构造被成为主构造函数,它的完整语法其实要加个constructor关键字:

class Student constructor(var age: Int = 0, var name: String = "")

不过Kotlin规定,如果主构造函数没有注解或者可见性修饰符(private/public)来修饰,constructor可以省略。

一个类除了可以有主构造函数,也可以有多个次构造函数。使用constructor关键字给Student类添加次构造函数:

class Student {
    var age = 0
    var name = ""
    //次构造函数不能通过var的方式去声明属性
    constructor(name: String){
        this.name = name
    }
    constructor(age: Int){
        this.age = age
    }
}

如果一个类同时拥有主构造和次构造,那么次构造函数必须要调用主构造:

class Student (var age: Int, var name: String){
    constructor(name: String) : this(0, name) {
        //do something
    }
}

继承

默认情况下,Kotlin的类是不能被继承的。如果希望一个类可以被其他类继承,需要用open来修饰,继承用冒号:表示:

open class People
class Student : People()

如果子类和父类都有主构造,则子类必须调用父类的构造进行初始化:

open class People (var name: String)
class Student(name: String) : People(name)

如果子类没有主构造,那么次构造必须使用super关键字来初始化父类:

open class People (var name: String)
class Student : People{
    constructor(name: String): super(name)
}

既然是继承,那么就可能遇到属性覆盖和方法覆盖。

如果想对父类的属性覆盖,首先父类的属性要用open修饰,然后子类的属性要用override修饰:

open class People (open var name: String)
class Student : People{
    constructor(name: String): super(name)
    override var name: String = "lxj"
}

如果想对父类的方法覆盖,那么道理是一样的:

open class People (open var name: String){
    open fun say(){
        println("i am a people.")
    }
}
class Student : People{
    constructor(name: String): super(name)
    override fun say() {
        println("i am a student.")
    }
}

抽象类

使用abstract来声明一个抽象类,抽象类的抽象方法无需添加open即可被覆盖:

abstract class People{
    abstract fun say()
}
class Student : People() {
    override fun say() {
        println("i am a student.")
    }
}

Getters 与 Setters

对于一个类的非私有属性,Kotlin都会生成默认的settergetter。当我们对一个对象的属性进行获取和赋值,就会调用默认的settergetter

class Student {
    var name: String = ""
}
val stu = Student()
stu.name = "lxj" //会调用name属性的setter方法
println(stu.name) //会调用name属性的getter方法

我们可以这样自定义一个字段的settergetter

class Student {
    var name: String = ""
        //field是个特殊标识符,专门用在setter和getter中,表示当前字段
        get() = if(field.isEmpty()) "LXJ" else field
        set(value) {
            field = value.toLowerCase() //将名字变成小写
        }
}

延迟初始化

你可能注意到我在定义类的属性时,经常给属性设置默认值:

class Student {
    var name: String = "" //如果不赋值,会编译报错
}

这是因为Kotlin要求显式地对属性进行赋值,但很多时候我们不想一上来就给默认值,希望感情到了待会儿再初始化这个属性。那么可以使用lateinit来声明这个属性:

class Student {
    lateinit var name: String //告诉编译器待会儿初始化这个变量
}

但是lateinit声明的变量有个不爽的地方,就是当你用到这个变量的时候,如果这个变量还没有被初始化,你将会收获一个异常:

kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized

也许你希望的是,当你用到这个变量时,如果变量还没有被初始化,那应该得到一个null,而不应该报异常。

这个想法很美好,但是和Kotlin的类型系统相互冲突了。Kotlin中增加了可空类型和非空类型的定义,像上面那样我们声明一个name属性为String类型,是在告诉编译器name是非空类型,所以如果没有初始化,Kotlin不会给你一个null,而是直接GG

至于可空类型如何定义,你现在只需要简单的知道String?就是可空类型,后面我们会专门讨论可空类型的使用。

为了避免你在初始化一个变量之前就使用它而导致GG,Kotlin给每个变量增加了一个属性isInitialized来判断这个变量是否初始化过:

fun printName(){
    if(this::name.isInitialized){ //如果初始化过再使用,可避免GG
        println(name)
    }
}

数据类

Java中有一个著名的名词叫JavaBean,就是一个用来描述数据的类。我们定义一个类Person用来描述人的信息,这个类就是一个JavaBean,Kotlin叫数据类。

先来定义一个普通的类:

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

上面的写法Kotlin会生成字段的settergetter;数据类还要求这个类有hashCode()equals()toString()方法,只需添加一个data关键字就变成数据类了:

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

就是这么简洁,通过Show Kotlin Bytecode工具可以查看生成的字节码。

来一个Java版本的对比一下,就能感受到Kotlin的data class有多强大:

public class People {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        People people = (People) o;
        return age == people.age &&
                name.equals(people.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

嵌套类和内部类

嵌套类就是一个嵌套在类中的类,但并不能访问外部类的任何成员,在实际开发中用的很少:

class Student {
    var name: String = ""

    class AddressInfo{
        var city: String = ""
        var province: String = ""
        fun printAddress(){
            //
        }
    }
}
//调用嵌套类对象的方法
Student.AddressInfo().printAddress()

内部类使用inner class声明,可以访问外部类的成员:

class Student {
    var name: String = ""

    inner class AddressInfo{
        var city: String = ""
        var province: String = ""
        fun printAddress(){
            println(name)
        }
    }
}
//调用内部类对象的方法
Student().AddressInfo().printAddress()

内部类在Android中用的比较多,比如在Activity中创建Adapter类,此时Adapter可以直接访问Activity的数据,非常方便。

枚举类

Kotlin的枚举和Java很像。直接看例子:

enum class Position{
    Left, Top, Right, Bottom
}
val position = Position.Left

自定义枚举的值:

enum class Position(var posi: String){
    Left("left"), Top("top"), Right("right"), Bottom("bottom")
}
更新时间: 6/1/2019, 11:25:02 AM