Java声明构造类Person:
//java
public class Person{
private final String name;
public Person(String name){
this.name = name;
}
public String getName(){
return name;
}
}
Kotlin声明构造类Person:
//kotlin class Person(val name: String)
类似于上述Kotlin代码中这种只有数据(如 name)没有其他代码的类,通常被叫做值对象。在 Kotlin 中 public 是默认的可见性,所以可以省略它。
二、构造方法构造方法:类的一种特殊方法,负责新创建对象的初始化工作,为实例变量赋初值。
Java中类的构造方法:
类的构造方法不是要求必须定义的。
① 如果在类中没有定义任何一个构造方法,则 Java 会自动为该类生成一个默认的构造方法(默认构造方法不包含任何参数,且方法体为空);
② 如果在类中显式地定义了一个或多个构造方法,则 Java 不再提供默认构造方法。
Kotlin 和 Java 一样,一个类可以声明一个或多个构造方法,但Kotlin的不同点为,区分了主构造方法和从构造方法:
- 主构造方法:主要而简洁的初始化类的方法,并且在类体外部声明
- 从构造方法:在类体内部声明
特点:在类体外部声明,且一般主构造方法在类头部的类名后面有括号
1、写法
声明一个简单类:
class User(val nickname:String)
其中,被括号围起来的语句块就叫作 主构造方法。
括号的目的:表明构造方法的参数,以及定义使用这些参数初始化的属性。
1) 主构造方法写法1:表示方式更为明确的构造方法
//代码片段1 表示方式更为明确的代码,nickname是属性
class User constructor(_nickname:String){
val nickname:String
init{ //初始化语句块
nickname = _nickname
}
}
- constructor关键字 用来开始一个主构造方法或从构造方法的声明;
- init关键字 用来引入一个初始化语句块。
初始化语句块:在Kotlin中,主构造方法有语法限制,不能包含初始化代码。 可以在一个类中声明多个初始化语句块。
错误写法:
class User constructor(_nickname:String){
val nickname = _nickname
println("init") //!!!语法错误,因为主构造方法中不能包含类的初始化代码
}
正确写法:
class User constructor(_nickname:String){
val nickname = _nickname
init{
println("init")
}
}
2) 主构造方法写法2:去掉constructor关键字
//代码片段2 功能和代码片段1相同
class User(_nickname:String){
val nickname = _nickname //用参数来初始化属性
}
-
初始化执行语句块中的代码可以与nickname属性的声明相结合
-
主构造方法没有注解或可见性修饰符,可去掉constructor关键字;当有修饰符时,constructor关键字位于修饰符后面
3) 主构造方法写法3:将val关键字加在方法的参数前
//代码片段3 功能和代码片段1和2相同 最简洁 class User(val nickname:String)
- 将val关键字加在参数前:属性使用构造方法参数来初始化,这种方式可以替换类中的属性定义
- 省略花括号:如果类不包含其他操作函数,可省略花括号
2、 为构造方法声明默认值
构造函数的重载
函数重载
https://cloud.tencent.com/developer/article/1854365
在Java中:
//Clickable.java
public interface Clickable {
void click(); //Java中的接口是一种特殊的抽象类,接口里面的所有方法都是抽象方法
}
//Button.java
public class Button implements Test11{
@Override
public void click() {
System.out.println(("I am Button, i am clicked"));
}
}
相对应的代码 在Kotlin中:
//Test33.kt
interface Clickable{
fun click()
}
class Button:Clickable{
override fun click() = println("I am Button, i am clicked")
}
(1) Kotlin使用 冒号 来代替了 Java 中的 extends 和 implements 关键字
(2) Kotlin用 override 修饰符来标注被重写的父类或接口的方法和属性,与Java中的注解 @Override 不同的是,override 修饰符是强制要求 的
(3) 接口中的默认方法
Java8中,接口中可包含默认方法,接口的实现类可重写也可不重写接口中的默认方法:
default void methodDefault() {
System.out.println("这是接口中定义的默认方法...");
}
Kotlin的接口中的方法也可以有一个默认实现,与Java8不同的是,Java8中需要你在这样的实现上标注 default 关键字,但是 Kotlin 没有特殊的注解,只需要提供一个方法体:
interface clickable{
fun click()
fun showOff() = println("I am clickable") //接口中的默认方法,接口的实现类可重写,也可不重写接口中的默认方法
}
注意,如果一个类实现的两个接口中都包含了带默认实现的方法,那么该类关于该方法的一个实现都不会使用,而强制要求你自己在类中提供自己的实现,Java 和 Kotlin 都是这样。
(4) 调用父类的方法
Kotlin 和 Java 中一样,调用 “super.方法名()”。
但是如果两个父类有同样的方法实现,在子类中调用时,需提供显式实现:
//kotlin
interface Clickable{
fun click()
fun showoff() = println("i am clickable")
}
interface Interfaceaa{
fun click()
fun showoff() = println("i am Interfaceaa")
}
class Button:Clickable,Interfaceaa{
override fun click() = println("I am Button, i am clicked")
override fun showoff(){
super.showoff() //显式地表示调用父类Clickable的方法。在Java中对应的是Clickable.super.showOff()
super.showoff() //显式地表示调用父类Interfaceaa的方法
}
}
(5) 创建子类
Java允许创建任意类的子类并重写任意方法,除非该类显式地使用 final 关键字进行标注
但是这样的话,一旦修改基类,子类可能出现预期之外的行为改变
《Effective Java》的作者建议"要么为继承做好设计并记录文档,要么禁止这么做"
所以,所有的没有特别需要在子类中被重写的类和方法应该被显式地标注为 final
Kotlin中的类和方法默认都是final的
Kotlin中,如果想要允许创建一个类的子类,需要使用 open 修饰符来标示这个类;如果想要某个属性或方法可以被重写,需要使用open修饰符来标示这个属性或方法。
例:声明一个可被继承的类:
open class Button:Clickable{
fun disable(){} //默认为final,不可被子类重写
open fun animate(){} //open,可以在子类中重写
override fun click(){} //重写了一个open函数,并且它本身也是open的,即可以在子类中重写。如果想要阻止本类的子类重写此实现,可以显式地将重写的成员标注为 final,如下行代码
//final override fun click(){}
}
三、抽象类
在Java中,抽象类和抽象方法必须要有 “abstract” 关键字修饰,且抽象类中不一定只有抽象方法,抽象方法的目的是为了限定子类必须完成父类的某些动作。抽象类不能被实例化。
//java 抽象类
public abstract class Test{}
在Kotlin中,关于抽象类同Java一样,使用 abstract 来修饰。抽象成员始终是open的,不需要显式地使用 open 修饰符。抽象类中的非抽象成员不是默认open,需要标注为open才可被重写。
//kotlin 抽象类
abstract class Test{ //抽象类
abstract fun fun22() //抽象方法,必须被子类重写
open fun fun33(){} //可被子类重写
fun fun44(){} //非抽象方法 不可被子类重写 final类型
}
class Test2: Test() { //注意,这里的类名后面有括号
override fun fun22() {
println("Test2::fun22")
}
override fun fun33() {
super.fun33()
println("Test2::fun33")
}
}
四、修饰符总结
| 修饰符 | 说明 |
|---|---|
| final | 修饰的类或类成员不能被重写【类和类的成员默认使用】 |
| open | 修饰的类可以被继承,修饰的类成员可以被重写【待使用时需明确标识】 |
| abstract | 修饰的类为抽象类,类成员为抽象成员,必须被重写【定义抽象类和抽象成员】 |
| override | 修饰对象为类成员【重写的成员默认是open的,即可被重写】 |
内部类:定义在另一个类中的类。
| 类A在另一个类B中声明 | 在Java中 | 在Kotlin中 |
|---|---|---|
| 嵌套类(不存储外部类的引用) | static class A | class A |
| 内部类(存储外部类的引用) | class A | inner class A |
sealed修饰符:为父类添加一个sealed修饰符,对可能创建的子类作出严格的限制,所有的直接子类必须嵌套在父类中。
使用sealed之前创建子类:
interface Expr
class Num(val value:Int):Expr
class Sum(val left:Expr, val right:Expr):Expr
fun eval(e:Expr):Int=
when(e){
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknow expression")
}
}
在上述例子中,如果添加了一个新的子类,但忘记在when中添加一个新分支,编译器会走else,出现潜在的bug。
设置密封类后,可以将else分支去掉:
sealed class Expr{ //注意,此类不是open的,不能被重写
class Num(val value:Int):Expr
class Sum(val left:Expr, val right:Expr):Expr
}
fun eval(e:Expr):Int=
when(e){
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
}
}
注意:
1)不能声明一个sealed接口,因为Kotlin编译器不能保证其他人是否在Java代码中实现这个接口。
2)Kotlin1.0中,sealed中的所有子类都必须是嵌套的,并且子类不能创建为data类;Kotlin1.1中允许在同一文件的任何位置定义 sealed 类的子类
参考:
Java中的接口都是抽象方法吗?_Morning sunshine的博客-CSDN博客_接口中的方法都是抽象方法
https://qa.1r1g.com/sf/ask/4051149231/



