Kotlin 类和对象(上)类的分析

923 阅读7分钟

前言

Kotlin 类和对象 系列

Kotlin 类和对象(上)类的分析
Kotlin 类和对象(下)object对象的分析

前面几篇花时间重点分析了Kotlin函数相关知识,本篇将着力于Kotlin 类的分析。
通过本篇文章,你将了解到:

1、类主次构造函数该怎么写
2、类的继承注意事项
3、类实现接口
4、嵌套类和内部类区别与使用

1、类主次构造函数该怎么写

Java 构造方法

public class JavaConTest {
    private String name;
    private int age;
    //构造方法
    public JavaConTest(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

在Java 里,构造方法没有返回值,函数名为类名。若是没有定义构造方法,默认有个无参的构造方法,若是定义了构造方法,那么将会覆盖无参构造方法。
此时构造JavaConTest时:

    //错误
    var javaContest = JavaConTest();
    //正确
    var javaContest = JavaConTest("fish", 18)

可以看出,默认的无参构造方法已经被覆盖。

Kotlin 主构造函数及其初始化

主构造函数写法

class ConTest constructor(name: String, age: Int) {
    var studentName: String? = null
    var studentAge:Int = 0

    //初始化
    init {
        studentName = name
        studentAge = age
    }
}

主构造函数约定如下:

constructor + 参数列表

其中参数列表参数写法/功能和普通函数一样,比如使用具名参数、默认参数等。

初始化
外界调用传入实参后,需要将实参接收下来,此处分别定义了两个变量,在"init" 包含的代码块里进行赋值。

init 包含的代码块在主构造函数调用时被调用

反编译看看结果:

    public ConTest(@NotNull String name, int age) {
        Intrinsics.checkNotNullParameter(name, "name");
        super();
        //init 块
        this.studentName = name;
        this.studentAge = age;
    }

可以看出构造函数里包含了"init"代码块。

Kotlin 次构造函数

假若现在需要在构造的时候传入学生考试分数,而又不破坏原来的结构,用Java 代码展示如下:

    //构造方法1
    public JavaConTest(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //构造方法2
    public JavaConTest(String name, int age, float score) {
        this(name, age);
        this.score = score;
    }

在保留构造方法1的基础上,再新增了构造方法2,这就形成了 构造方法重载

Kotlin 怎么实现构造函数重载的功能呢?
答案是:次构造函数

class ConTest constructor(name: String, age: Int) {
    var studentName: String? = null
    var studentAge: Int = 0
    var studentScore: Float = 0.0f

    //初始化
    init {
        studentName = name
        studentAge = age
        println("studentName:$studentName")
    }
    //次构造函数
    constructor(name: String, age: Int, score: Float) : this(name, age) {
        studentScore = score
        println("studentScore:$studentScore")
    }
}

次构造函数规则:

constructor + 参数列表,与主构造函数不同的是,次构造函数写在类体里。
当主构造函数存在时,次构造函数需要主动直接/间接调用主构造函数(通过this)。
次构造函数之间可以通过this 调用。

主构造函数定义成员变量

由上可知,主构造函数传入实参后(如name),需要在类里定义变量接收(如studentName),而后在init 代码块里做初始化,这个链路有点长。
Kotlin 提供了在主构造函数里声明定义变量的写法:

class ConTest1 constructor(var name: String, var age: Int) {
    var studentScore: Float = 0.0f

    //次构造函数
    constructor(name: String, age: Int, score: Float) : this(name, age) {
        studentScore = score
        println("studentScore:$studentScore")
    }
}

此时,通过加上 "var/val"修饰后,构造函数里声明的"name"和"age"自动转换为ConTest1 的成员变量 "name"和"age",外界可以通过ConTest1对象访问它们:

    //主构造函数声明成员变量
    var conTest1 = ConTest1("fish", 18)
    println("name:${conTest1.name} age:${conTest1.age}")

如此一来,就无需再继续以上繁琐步骤了。
反编译结果如下:

public final class ConTest1 {
    private float studentScore;
    //成员变量
    private String name;
    private int age;
    public ConTest1(@NotNull String name, int age) {
        this.name = name;
        this.age = age;
    }
}

2、类的继承注意事项

Java 继承

老规矩,先看Java 实现:

//基类
public class JavaParent {
    public String name;
    public void setName(String name) {
        this.name = name;
    }
}
class JavaSon extends JavaParent {
    //重写属性
    public String name;
    
    //重写方法
    @Override
    public void setName(String name) {
        super.setName(name);
        System.out.println("name:" + this.name);
    }
}

Kotlin 继承

当我们照着Java 依葫芦画瓢这么写:

//基类
class KtParent {
}
class KtSon : KtParent {
}

KtSon 会报错,原因有俩:

1、KtParent 必须调用构造函数。
2、KtParent 默认是final 类型,不能被继承。

知道原因了,改造如下:

//基类
open class KtParent {
}
class KtSon : KtParent(),KtInter {
}

这样编译就没问题了。
Kotlin 继承规则:

1、Kotlin 类默认为final 类型,若想要被继承,需要添加 open 修饰。
2、子类继承父类时需要调用父类构造函数。

上面只是空类,添加内容后如下:

//基类
open class KtParent {
    open var name: String? = null
    open fun printName() {
        println("name:$name")
    }
}
class KtSon : KtParent() {
    override var name: String? = null
    override fun printName() {
        super.printName()
        println("in KtSon")
    }
}

同样的,若是子类想要重写父类的属性和函数,需要两个条件:

1、父类属性和函数需要 open 修饰。
2、子类重写父类的属性和函数 需要使用 override 修饰。

子类调用父类属性/函数可以通过 super.xx(属性/函数)。

3、类实现接口

Java 接口

interface JavaInter {
    void printInter();
}
class JavaClass implements JavaInter {
    @Override
    public void printInter() {
    }
}

Java 接口使用 implements 实现。

Kotlin 接口

interface KtInter {
    fun printInter();
}
class KotlinClass : KtInter {
    override fun printInter() {
    }
}

Kotlin 类实现接口与类继承写法类似,都是通过":"访问,只是接口没有构造函数,无需在此之后书写"()"。

当类实现多个接口时,写法如下:

class KotlinClassInter : KtInter, KtParent(18) {
    override fun printInter() {
        TODO("Not yet implemented")
    }
}

使用","隔开各个接口。

Java Kotlin 设计为单继承(类),多实现(接口)。

实现多个接口时,可能会遇到方法签名一致的情况:

interface KotlinInterA {
    fun test() {
        println("interface A")
    }
}
interface KotlinInterB {
    fun test() {
        println("interface B")
    }
}
//报错
class TestInter() : KotlinInterA, KotlinInterB {
}

此时编译是不通过的,因为编译器不知道选取哪个函数,因此要求实现类重写test()函数:

class TestInter() : KotlinInterA, KotlinInterB {
    override fun test() {
        //选择调用接口函数
        super<KotlinInterA>.test()
        super<KotlinInterB>.test()
    }
}

如上,在重写的函数里可以选择调用接口的函数,形式为:

super<接口名>.xx()

4、嵌套类和内部类区别与使用

这部分容易搞混,一是涉及到类的类型定义,二是作为参数传递时的区别。同时该小结也是本篇的重点,接下来逐一击破。
嵌套类定义:

在一个类体里定义的类称为嵌套类。

Java 静态/非静态内部类

先看Java 如何构造一个嵌套类:

class Outer {
    //成员属性与方法
    private String name;
    private void printName() {
        System.out.println("name:" + name);
    }
     //静态属性与方法
    private static String staticName;
    static void printStaticName() {
    }

    //非静态内部类
    class Inner {
        public void testInner() {
            //访问外部类方法
            printName();
            //访问外部类属性
            name = "fish";
        }
    }

    //静态内部类
    static class StaticInner {
        public void testInner() {
            //访问静态方法
            printStaticName();
            //访问静态属性
            staticName = "fish";
        }
    }
}

在Java 里,通常不说嵌套类,取而代之的是:

静态内部类与非静态内部类

静态内部类不能访问外部类的实例方法,仅仅只是一个书写在外部类里的类而已。
而非静态内部类则不同,它可以访问外部类的属性和方法。
对于两者,外界构造对象的方式也有所不同:

class Start {
    private void test() {
        //构造非静态内部类
        Outer.Inner inner = new Outer().new Inner();
        inner.testInner();

        //构造静态内部类
        Outer.StaticInner staticInner = new Outer.StaticInner();
        staticInner.testInner();
    }
}

可以看出,非静态内部类需要和实例关联,因此先要创建外部类对象后,再通过外部类对象创建非静态内部类对象。
而静态内部类对象仅仅只是通过外部类进行名字定位而已。

Kotlin 嵌套类和内部类

private val staticName: String? = null
fun printStaticName() {}

class KtOuter {
    private var name: String? = null
    //成员方法
    private fun printName() {
        println("name:$name")
    }

    //嵌套类
    class Inner {
        fun testInner() {
            //访问全局函数
            printStaticName()
        }
    }

    //内部类
    inner class RealInner {
        fun testInner() {
            //访问外部类函数和属性
            printName()
            name = "fish"
        }
    }
}

Kotlin 里没有static 关键字,使用全局函数/属性 替代演示效果。
Kotlin 里使用 inner 修饰类,表示该类为一个内部类,它可以访问外部类的属性和函数。
看看两者构造方式差异:

    //嵌套类构造
    var inner = KtOuter.Inner()
    inner.testInner()
    
    //内部类构造
    var realInner = KtOuter().RealInner()
    realInner.testInner()

同样的,Kotlin 内部类需要先创建外部类对象后才能构造内部类对象。

将Koltin 嵌套类/内部类 反编译:

//Kotlin 嵌套类反编译
public static final class Inner {
    public final void testInner() {
        ...
    }
}
//Kotlin 内部类反编译
public final class RealInner {
    public final void testInner() {
        ...
    }
}

与Java 比对发现:

Kotlin 里嵌套类 与 Java 静态内部类相似。
Kotlin 里内部类 与 Java 非静态内部类相似。

Java/Kotlin 差异

以图示之:

image.png

至此,Kotlin 类的主要内容分析完毕,下篇将开启Kotlin 对象分析。

本文基于Kotlin 1.5.3,文中Demo请点击

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android/Kotlin

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列
19、Kotlin 轻松入门系列