Kotlin 类和对象
从这篇文章开始我们一起正式进入 Kotlin 面向对象的世界,Kotlin 实际上也是一门面向对象的语言但同时又兼顾了函数式编程语言。只不过函数在 Kotlin 中的地位被提升至一等公民。但是在 Kotlin 中也是有类、对象、属性、方法等。
1. Kotlin 中的类
在 Kotlin 中类和 Java 中概念基本是一致的,都是使用 class
关键字来声明一个类,一个类中可以用属性表示一个类的状态,可以用方法来表示一个类的行为。但是与 Java 不同的是 Kotlin 中的类声明默认就是 final
和 public
, 所以在 Kotlin 中不能直接继承一个类,因为默认类是 final 的,此外也不需要像 Java 中一样显式使用 public
修饰符。
//Student.java
public class Student {//public修饰符
private String name;
private String nickName;
private int age;
public Student(String name, String nickName, int age) {
this.name = name;
this.nickName = nickName;
this.age = age;
}
}
//SeniorStudent.java
public class SeniorStudent extends Student {//直接继承Student类
public SeniorStudent(String name, String nickName, int age) {
super(name, nickName, age);
}
}
而在 Kotlin 中不能直接继承一个类,如果需要继承一个类则需要在基类上加 open
关键字修饰。
open class Student(
private val name: String,
private val nickName: String,
private val age: Int
)//Student类被继承需要加open关键字,此外Kotlin中构造器初始化也省去了很多模版代码
class SeniorStudent(
private val name: String,
private val nickName: String,
private val age: Int
) : Student(name, nickName, age)//在Kotlin中继承不再使用extends关键字而是使用:来替代
2. 类的定义
在 Kotlin 中和 Java 一样都是使用 class 关键字修饰对应类的名称即可。在类中会有属性描述类的对象状态,方法描述类的对象方法。
class Bird {
val color: String = "green"//类的属性描述类的对象的状态
val age: Int = 3
fun fly() {//类的方法描述类的对象的行为
println("I can fly!")
}
}
我们可以上述 Kotlin 代码反编译成 Java 代码,会发现虽然 Kotlin 和 Java 声明方法基本类似,但是还是存在一些不同的
public final class Bird {//可以看到java中自动加上public,进一步证明了在Kotlin默认是public访问,而java默认是包可见。
//此外还可看到Bird使用了final修饰,所以也就进一步证明Kotlin中默认所有都是final修饰,也就意味这个类默认是不能被继承的。
@NotNull
private final String color = "green";//final修饰,是因为在Kotlin中使用的是val修饰成员变量,所以可以看到kotlin val就是使用Java中的final实现的。那么如果使用var修饰就不需要final了。
private final int age = 3;
@NotNull
public final String getColor() {//由于是val修饰,所以color属性只会有对应getter方法,没有setter方法
return this.color;
}
public final int getAge() {
return this.age;
}
public final void fly() {//可以看到fly函数是final修饰,也就进一步证明Kotlin中默认所有都是final修饰,那么这个fly是不能被子类重写的
String var1 = "I can fly!";
boolean var2 = false;
System.out.println(var1);
}
}
3. 更简单构造类的对象
在 Kotlin 中构造对象不再需要 new
关键字了,而是直接调用类的构造器方法就可以创建一个对象了。例如以下代码:
val bird = Bird() // 省略了new关键字,直接创建Bird对象
当然也可以创建带参数的对象,Kotlin 只需要将上述 Bird
类修改为带默认参数的构造器即可,而在 Java 中则需要增加一个重载构造器函数,但是相比你会发现 Kotlin 更为方便和简洁。
Java 实现:
class Bird {
private String color;
private int age;
public Bird(String color, int age) {
this.color = color;
this.age = age;
}
public void fly() {
println("I can fly!");
}
}
Bird brid = new Bird("blue", 7);//java创建一个带参数Bird对象
Kotlin 实现:
class Bird(val color: String = "green", val age: Int = 3) {
fun fly() {
println("I can fly!")
}
}
val brid = Bird(color = "blue", age = 7)//创建一个带参数Bird对象
4. 类的构造器函数
在 Kotlin 中构造器函数是存在 “主从” 关系,这点是 Java 中不存在的,也就是常说的主构造器函数和从构造器函数。比如在上述 Bird
类中需要新增一个带类型 (type) 属性的构造器,就可以定义从构造器,从构造器是利用 constructor
关键字声明。
class Bird(val color: String = "green", val age: Int = 3) { //主构造器
constructor(
color: String = "green",
age: Int = 3,
type: String
) : this(color, age) {//使用constructor声明从构造器,:this(color, age)从构造器直接委托调用主构造器函数
//do logical
}
fun fly() {
println("I can fly!")
}
}
fun main() {
val smallBird = Bird(color = "blue", age = 8, type = "small")
}
需要注意的是,在 Kotlin 中默认类都会存在一个无参主构造器函数,除非我们手动指定。此外如果一个存在主构造器,那么从构造器函数就会直接或间接委托调用主构造器,直接委托给主构造器就类似上述例子中的 : this(color, age)
,当然可以通过从构造器 A 委托从构造器 B,然后从构造器 B 委托给主构造器,从而达到间接委托作用。
class CustomView : View {
constructor(context: Context) : this(context, null)//从构造器A委托调用从构造器B
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)//从构造器B委托调用从构造器C
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {//从构造器C委托调用主构造器
}
}
5. init 初始化块
与 Java 不同的是在 Kotlin 中还存在 init
初始化块的概念,它属于构造器函数一部分,只是在代码形式看似两者是分离的。如果我们需要在初始化时进行其他的额外操作时,这时候就需要 init
语句块来执行,有个有趣的点需要注意的是,在 init
初始化块中,是可以直接访问构造器函数中参数的。
class Bird(val color: String = "green", val age: Int = 3) {
//...
}
//上述代码实际上等同于下面代码
class Bird(color: String = "green", age: Int = 3) {
val color: String = color
val age: String = age
}
//所以针对没有val修饰构造器函数参数,只能在init初始化块中访问,而一般成员函数是无法访问的
class Bird(color: String = "green", age: Int = 3) {//当color没有val修饰
init {
println("color: $color")//可以看到在init块中使用构造器函数中的color参数
}
fun printInfo() {
println(color)//非法访问
}
}
对于 init
初始化块,是可以存在多个的,它们执行顺序是从上到下依次执行
class Bird(color: String = "green", age: Int = 3) {
init {
println("color: $color")//init块1
}
init {
println("age: $age")//init块2
}
}
//执行的顺序是,先输出init块1中日志再输出init块2中的日志
对于 init
初始化块和从构造器同时存在,它们的执行顺序是怎么样的呢?是先执行完所有的 init 初始化块,再执行从构造器函数中代码。
可以上述例子修改一下即可:
class Bird(color: String = "green", age: Int = 3) {
init {
println("color: $color")//init块1
}
init {
println("age: $age")//init块2
}
constructor(color: String, age: Int, type: String) : this(color, age) {
println("constructor executed")
}
}
fun main() {
val smallBird = Bird(color = "blue", age = 8, type = "small")
}
//输出结果
color: blue
age: 8
constructor executed
Process finished with exit code 0
6. 类的 setter,getter 访问器
与 Java 不同的是,需要手动创建 setter,getter 方法;即使现在很多 IDEA 插件工具可以自动生成,但是从语言层面来说还是比较啰嗦的。所以 Kotlin 直接在语言的层面省去了。先来对比一下:
public class Bird {
private String color;
private int age;
private String type;
public Bird(String color, int age, String type) {
this.color = color;
this.age = age;
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
而对于 Kotlin 只需要简单一行即可达到以上实现:
class Bird(var color: String, var age: Int, var type: String)//var修饰则表示color属性会自动生成setter,getter方法,如果是val修饰表示只读,那么只会生成getter方法
为了进一步验证,看看这一行简单声明是否反编译成 java 代码是怎么样的
public final class Bird {
@NotNull
private String color;
private int age;
@NotNull
private String type;
@NotNull
public final String getColor() {
return this.color;
}
public final void setColor(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.color = var1;
}
public final int getAge() {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
@NotNull
public final String getType() {
return this.type;
}
public final void setType(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.type = var1;
}
public Bird(@NotNull String color, int age, @NotNull String type) {
Intrinsics.checkParameterIsNotNull(color, "color");
Intrinsics.checkParameterIsNotNull(type, "type");
super();
this.color = color;
this.age = age;
this.type = type;
}
}
7. 不同访问控制规则
7.1 自带默认的 final 修饰
在 Java 中我们经常会控制一个类不被修改或继承,则需要 final
修饰符修饰;而在 Kotlin 中不要手动添加 final
而是默认就是 final
,如果需要让这个类或方法被继承和修改,就需要手动添加 open
关键解除这个禁忌。
open class Animal(color: String, age: Int) {//open关键字打开final禁忌,使得Animal可以被继承
open fun printInfo() {//open关键字打开final禁忌,使得printInfo可以被子类重写
println("this is animal!")
}
}
class Dog(color: String, age: Int) : Animal(color, age) {
override fun printInfo() {
println("this is dog!")
}
}
我们也可以通过编译上述代码,看 Animal 类是否还存在 final 修饰符,来进一步证明我们结论。
public class Animal {//没有final可以被继承
public void printInfo() {//没有final可以被子类重写
String var1 = "this is animal!";
boolean var2 = false;
System.out.println(var1);
}
public Animal(@NotNull String color, int age) {
Intrinsics.checkParameterIsNotNull(color, "color");
super();
}
}
7.2 可见性修饰符
在 Kotlin 中默认修饰符与 Java 则不一样,在 Kotlin 默认是 public
而 Java 则默认是 default
(包级可见性)。此外 Kotlin 中还存在独有的 internal
访问可见修饰符。下面列出一张对应表格
修饰符 | 表示含义 | 与 Java 比较 |
public | Kotlin 默认修饰符,全局可见 | 与 Java 中显式指定的 public 效果一致 |
protected | 受保护修饰符,类和子类可见 | 与 Java 一致,除了类和子类可见,其包内也可见 |
private | 私有修饰符,只有本类可见,类外文件内可见 | 只能类内可见 |
internal | 模块内可见 | 无该修饰符 |
8. 总结
到这里有关 Kotlin 中面向对象的第一站就结束,回顾一下本篇文章主要介绍了 Kotlin 中类和对象定义和创建,以及类的构造函数、init 初始化块、可见性修饰符,并把这些特性语言一一和 Java 进行对比,帮助快速掌握和理解。