Kotlin 类和对象 系列
Kotlin 类和对象(上)类的分析
Kotlin 类和对象(下)object对象的分析
前面几篇花时间重点分析了Kotlin函数相关知识,本篇将着力于Kotlin 类的分析。
通过本篇文章,你将了解到:
1、类主次构造函数该怎么写
2、类的继承注意事项
3、类实现接口
4、嵌套类和内部类区别与使用
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)
可以看出,默认的无参构造方法已经被覆盖。
主构造函数写法
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"代码块。
假若现在需要在构造的时候传入学生考试分数,而又不破坏原来的结构,用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;
}
}
老规矩,先看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);
}
}
当我们照着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(属性/函数)。
interface JavaInter {
void printInter();
}
class JavaClass implements JavaInter {
@Override
public void printInter() {
}
}
Java 接口使用 implements 实现。
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()
这部分容易搞混,一是涉及到类的类型定义,二是作为参数传递时的区别。同时该小结也是本篇的重点,接下来逐一击破。
嵌套类定义:
在一个类体里定义的类称为嵌套类。
先看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();
}
}
可以看出,非静态内部类需要和实例关联,因此先要创建外部类对象后,再通过外部类对象创建非静态内部类对象。
而静态内部类对象仅仅只是通过外部类进行名字定位而已。
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 非静态内部类相似。
至此,Kotlin 类的主要内容分析完毕,下篇将开启Kotlin 对象分析。
本文基于Kotlin 1.5.3,文中Demo请点击
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 轻松入门系列