Kotlin的类和接口与Java的类和接口存在较大区别,本次主要概括Kotlin的接口和类如何定义、继承以及其一些具体细节,同时查看其对应的Java层实现。android
Kotlin接口能够包含抽象方法以及非抽象方法的实现(相似Java 8的默认方法)编程
interface MyInterface {
//抽象方法
fun daqi()
//非抽象方法(即提供默认实现方法)
fun defaultMethod() {
}
}
复制代码
接口也能够定义属性。声明的属性能够是抽象的,也能够是提供具体访问器实现的(即不算抽象的)。安全
interface MyInterface {
//抽象属性
var length:Int
//提供访问器的属性
val name:String
get() = ""
//抽象方法
fun daqi()
//非抽象方法(即提供默认实现方法)
fun defaultMethod() {
}
}
复制代码
接口中声明的属性不能有幕后字段。由于接口是无状态的,所以接口中声明的访问器不能引用它们。(简单说就是接口没有具体的属性,不能用幕后字段对属性进行赋值)bash
Kotlin使用 : 替代Java中的extends 和 implements 关键字。Kotlin和Java同样,一个类能够实现任意多个接口,可是只能继承一个类。ide
接口中抽象的方法和抽象属性,实现接口的类必须对其提供具体的实现。函数
对于在接口中提供默认实现的接口方法和提供具体访问器的属性,能够对其进行覆盖,从新实现方法和提供新的访问器实现。post
class MyClass:MyInterface{
//原抽象属性,提供具体访问器
//不提供具体访问器,提供初始值,使用默认访问器也是没有问题的
override var length: Int = 0
/*override var length: Int
get() = 0
set(value) {}*/
//覆盖提供好访问器的接口属性
override val name: String
//super.name 实际上是调用接口中定义的访问器
get() = super.name
//原抽象方法,提供具体实现
override fun daqi() {
}
//覆盖默认方法
override fun defaultMethod() {
super.defaultMethod()
}
}
复制代码
不管是从接口中获取的属性仍是方法,前面都带有一个override关键字。该关键字与Java的@Override注解相似,重写父类或接口的方法属性时,都 强制 须要用override修饰符进行修饰。由于这样能够避免先写出实现方法,再添加抽象方法形成的意外重写。学习
接口也能够从其余接口中派生出来,从而既提供基类成员的实现,也能够声明新的方法和属性。ui
interface Name {
val name:String
}
interface Person :Name{
fun learn()
}
class daqi:Person{
//为父接口的属性提供具体的访问器
override val name: String
get() = "daqi"
//为子接口的方法提供具体的实现
override fun learn() {
}
}
复制代码
在C++中,存在菱形继承的问题,即一个类同时继承具备相同函数签名的两个方法,到底该选择哪个实现呢?因为Kotlin的接口支持默认方法,当一个类实现多个接口,同时拥有两个具备相同函数签名的默认方法时,到底选择哪个实现呢?this
主要根据如下3条规则进行判断:
一、类中带override修饰的方法优先级最高。 类或者父类中带override修饰的方法的优先级高于任何声明为默认方法的优先级。(Kotlin编译器强制要求,当类中存在和父类或实现的接口有相同函数签名的方法存在时,须要在前面添加override关键字修饰。)
二、当第一条没法判断时,子接口的优先级更高。优先选择拥有最具体实现的默认方法的接口,由于从继承角度理解,能够认为子接口的默认方法覆盖重写了父接口的默认方法,子接口比父接口具体。
三、最后仍是没法判断时,继承多个接口的类须要显示覆盖重写该方法,并选择调用指望的默认方法。
Java继承自Language,二者都对use方法提供了默认实现。而Java比Language更具体。
interface Language{
fun use() = println("使用语言")
}
interface Java:Language{
override fun use() = println("使用Java语言编程")
}
复制代码
而实现这两个接口的类中,并没有覆盖重写该方法,只能选择更具体的默认方法做为其方法实现。
class Person:Java,Language{
}
//执行结果是输出:使用Java语言编程
val daqi = Person()
daqi.use()
复制代码
接口Java和Kotlin都提供对learn方法提供了具体的默认实现,且二者并没有明确的继承关系。
interface Java {
fun learn() = println("学习Java")
}
interface Kotlin{
fun learn() = println("学习Kotlin")
}
复制代码
当某类都实现Java和Kotlin接口时,此时就会产生覆盖冲突的问题,这个时候编译器会强制要求你提供本身的实现:
惟一的解决办法就是显示覆盖该方法,若是想沿用接口的默认实现,能够super关键字,并将具体的接口名放在super的尖括号中进行调用。
class Person:Java,Kotlin{
override fun learn() {
super<Java>.learn()
super<Kotlin>.learn()
}
}
复制代码
Java 8中也同样能够为接口提供默认实现,但须要使用default关键字进行标识。(Kotlin只须要提供具体的方法实现,即提供函数体)
public interface Java8 {
default void defaultMethod() {
System.out.println("我是Java8的默认方法");
}
}
复制代码
面对覆盖冲突,Java8的和处理和Kotlin的基本类似,在语法上显示调用接口的默认方法时有些不一样:
//Java8 显示调用覆盖冲突的方法
Java8.super.defaultMethod()
//Kotlin 显示调用覆盖冲突的方法
super<Kotlin>.learn()
复制代码
众所周知,Java8以前接口没有默认方法,Kotlin是如何兼容的呢?定义以下两个接口,再查看看一下反编译的结果:
interface Language{
//默认方法
fun use() = println("使用语言编程")
}
interface Java:Language{
//抽象属性
var className:String
//提供访问器的属性
val field:String
get() = ""
//默认方法
override fun use() = println("使用Java语言编程")
//抽象方法
fun absMethod()
}
复制代码
先查看父接口的源码:
public interface Language {
void use();
public static final class DefaultImpls {
public static void use(Language $this) {
String var1 = "使用语言编程";
System.out.println(var1);
}
}
}
复制代码
Language接口中的默认方法转换为抽象方法保留在接口中。其内部定义了一个名为DefaultImpls的静态内部类,该内部类中拥有和默认方法相同名称的静态方法,而该静态方法的实现就是其同名默认函数的具体实现。也就是说,Kotlin的默认方法转换为静态内部类DefaultImpls的同名静态函数。
因此,若是想在Java中调用Kotlin接口的默认方法,须要加多一层DefaultImpls
public class daqiJava implements Language {
@Override
public void use() {
Language.DefaultImpls.use(this);
}
}
复制代码
再继续查看子接口的源码
public interface Java extends Language {
//抽象属性的访问器
@NotNull
String getClassName();
void setClassName(@NotNull String var1);
//提供具体访问器的属性
@NotNull
String getField();
//默认方法
void use();
//抽象方法
void absMethod();
public static final class DefaultImpls {
@NotNull
public static String getField(Java $this) {
return "";
}
public static void use(Java $this) {
String var1 = "使用Java语言编程";
System.out.println(var1);
}
}
}
复制代码
经过源码观察到,不管是抽象属性仍是拥有具体访问器的属性,都没有在接口中定义任何属性,只是声明了对应的访问器方法。(和扩展属性类似)
抽象属性和提供具体访问器的属性区别是:
Java定义的接口,Kotlin继承后能为其父接口的方法提供默认实现吗?固然是能够啦:
//Java接口
public interface daqiInterface {
String name = "";
void absMethod();
}
//Kotlin接口
interface daqi: daqiInterface {
override fun absMethod() {
}
}
复制代码
Java接口中定义的属性都是默认public static final,对于Java的静态属性,在Kotlin中能够像顶层属性同样,直接对其进行使用:
fun main(args: Array<String>) {
println("Java接口中的静态属性name = $name")
}
复制代码
Kotlin的类能够有一个主构造函数以及一个或多个 从构造函数。主构造函数是类头的一部分,即在类体外部声明。
constructor关键字能够用来声明 主构造方法 或 从构造方法。
class Person(val name:String)
//其等价于
class Person constructor(val name:String)
复制代码
主构造函数不能包含任何的代码。初始化的代码能够放到以 init 关键字做为前缀的初始化块中。
class Person constructor(val name:String){
init {
println("name = $name")
}
}
复制代码
构造方法的参数也能够设置为默认参数,当全部构造方法的参数都是默认参数时,编译器会生成一个额外的不带参数的构造方法来使用全部的默认值。
class Person constructor(val name:String = "daqi"){
init {
println("name = $name")
}
}
//输出为:name = daqi
fun main(args: Array<String>) {
Person()
}
复制代码
主构造方法同时须要初始化父类,子类能够在其列表参数中索取父类构造方法所需的参数,以便为父类构造方法提供参数。
open class Person constructor(name:String){
}
class daqi(name:String):Person(name){
}
复制代码
当没有给一个类声明任何构造方法,编译器将生成一个不作任何事情的默认构造方法。对于只有默认构造方法的类,其子类必须显式地调用父类的默认构造方法,即便他没有参数。
open class View
class Button:View()
复制代码
而接口没有构造方法,因此接口名后不加括号。
//实现接口
class Button:ClickListener
复制代码
当 主构造方法 有注解或可见性修饰符时,constructor 关键字不可忽略,而且constructor 在这些修饰符和注解的后面。
class Person public @Inject constructor(val name:String)
复制代码
构造方法的可见性是 public,若是想将构造方法设置为私有,可使用private修饰符。
class Person private constructor()
复制代码
从构造方法使用constructor关键字进行声明
open class View{
//从构造方法1
constructor(context:Context){
}
//从构造方法2
constructor(context:Context,attr:AttributeSet){
}
}
复制代码
使用this关键字,从一个构造方法中调用该类另外一个构造方法,同时也能使用super()关键字调用父类构造方法。
若是一个类有 主构造方法,每一个 从构造方法 都应该显式调用 主构造方法,不然将其委派给会调用主构造方法的从构造方法。
class Person constructor(){
//从构造方法1,显式调用主构造方法
constructor(string: String) : this() {
println("从构造方法1")
}
//从构造方法2,显式调用构造方法1,间接调用主构造方法。
constructor(data: Int) : this("daqi") {
println("从构造方法2")
}
}
复制代码
注意:
初始化块中的代码实际上会成为主构造函数的一部分。显式调用主构造方法会做为次构造函数的第一条语句,所以全部初始化块中的代码都会在次构造函数体以前执行。
即便该类没有主构造函数,这种调用仍会隐式发生,而且仍会执行初始化块。
//没有主构造方法的类
class Person{
init {
println("主构造方法 init 1")
}
//从构造方法默认会执行全部初始化块
constructor(string: String) {
println("从构造方法1")
}
init {
println("主构造方法 init 2")
}
}
复制代码
若是一个类拥有父类,但没有主构造方法时,每一个从构造方法都应该初始化父类(即调用父类的构造方法),不然将其委托给会初始化父类的构造方法(即便用this调用其余会初始化父类的构造方法)。
class MyButton:View{
//调用自身的另一个从构造方法,间接调用父类的构造方法。
constructor(context:Context):this(context,MY_STYLE){
}
//调用父类的构造方法,初始化父类。
constructor(context:Context,attr:AttributeSet):super(context,attr){
}
}
复制代码
Java中容许建立任意类的子类并重写任意方法,除非显式地使用final关键字。对基类进行修改致使子类不正确的行为,就是所谓的脆弱的基类。因此Kotlin中类和方法默认是final,Java类和方法默认是open的。
当你容许一个类存在子类时,须要使用open修饰符修改这个类。若是想一个方法能被子类重写,也须要使用open修饰符修饰。
open class Person{
//该方法时final 子类不能对它进行重写
fun getName(){}
//子类能够对其进行重写
open fun getAge(){}
}
复制代码
对基类或接口的成员进行重写后,重写的成员一样默认为open。(尽管其为override修饰)
若是想改变重写成员默认为open的行为,能够显式的将重写成员标注为final
open class daqi:Person(){
final override fun getAge() {
super.getAge()
}
}
复制代码
抽象类的成员和接口的成员始终是open的,不须要显式地使用open修饰符。
Kotlin和Java的可见性修饰符类似,一样可使用public、protected和private修饰符。但Kotlin默承认见性是public,而Java默承认见性是包私有。
Kotlin中并无包私有这种可见性,Kotlin提供了一个新的修饰符:internal,表示“只在模块内部可见”。模块是指一组一块儿编译的Kotlin文件。多是一个Gradle项目,多是一个Idea模块。internal可见性的优点在于它提供了对模块实现细节的封装。
Kotlin容许在顶层声明中使用private修饰符,其中包括类声明,方法声明和属性声明,但这些声明只能在声明它们的文件中可见。
注意:
Kotlin像Java同样,容许在一个类中声明另外一个类。但Kotlin的嵌套类默认不能访问外部类的实例,和Java的静态内部类同样。
若是想让Kotlin内部类像Java内部类同样,持有一个外部类的引用的话,须要使用inner修饰符。
内部类须要外部类引用时,须要使用 this@外部类名 来获取。
class Person{
private val name = "daqi"
inner class MyInner{
fun getPersonInfo(){
println("name = ${this@Person.name}")
}
}
}
复制代码
在Java中建立单例每每须要定义一个private的构造方法,并建立一个静态属性来持有这个类的单例。
Kotlin经过对象声明将类声明和类的单一实例结合在一块儿。对象声明在定义的时候就当即建立,而这个初始化过程是线程安全的。
对象声明中能够包含属性、方法、初始化语句等,也支持继承类和实现接口,惟一不容许的是不能定义构造方法(包括主构造方法和从构造方法)。
对象声明不能定义在方法和内部类中,但能够定义在其余的对象声明和非内部类(例如:嵌套类)。若是须要引用该对象,直接使用其名称便可。
//定义对象声明
class Book private constructor(val name:String){
object Factory {
val name = "印书厂"
fun createAppleBooK():Book{
return Book("Apple")
}
fun createAndroidBooK():Book{
return Book("Android")
}
}
}
复制代码
调用对象声明的属性和方法:
Book.Factory.name
Book.Factory.createAndroidBooK()
复制代码
将对象声明反编译成Java代码,其内部实现也是定义一个private的构造方法,并始终建立一个名为INSTANCE的静态属性来持有这个类的单例,而该类的初始化放在静态代码块中。
public final class Book {
//....
public Book(String name, DefaultConstructorMarker $constructor_marker) {
this(name);
}
public static final class Factory {
@NotNull
private static final String name = "印书厂";
public static final Book.Factory INSTANCE;
//...
@NotNull
public final Book createAppleBooK() {
return new Book("Apple", (DefaultConstructorMarker)null);
}
@NotNull
public final Book createAndroidBooK() {
return new Book("Android", (DefaultConstructorMarker)null);
}
private Factory() {
}
static {
Book.Factory var0 = new Book.Factory();
INSTANCE = var0;
name = "印书厂";
}
}
}
复制代码
用Java调用对象声明的方法:
//Java调用对象声明
Book.Factory.INSTANCE.createAndroidBooK();
复制代码
通常状况下,使用顶层函数能够很好的替代Java中的静态函数,但顶层函数没法访问类的private成员。
当须要定义一个方法,该方法能在没有类实例的状况下,调用该类的内部方法。能够定义一个该类的对象声明,并在该对象声明中定义该方法。类内部的对象声明能够用 companion 关键字标记,这种对象叫伴生对象。
能够直接经过类名来访问该伴生对象的方法和属性,不用再显式的指明对象声明的名称,再访问该对象声明对象的方法和属性。能够像调用该类的静态函数和属性同样,不须要再关心对象声明的名称。
//将构造方法私有化
class Book private constructor(val name:String){
//伴生对象的名称可定义也能够不定义。
companion object {
//伴生对象调用其内部私有构造方法
fun createAppleBooK():Book{
return Book("Apple")
}
fun createAndroidBooK():Book{
return Book("Android")
}
}
}
复制代码
调用伴生对象的方法:
Book.createAndroidBooK()
复制代码
伴生对象的实现和对象声明相似,定义一个private的构造方法,并始终建立一个名为Companion的静态属性来持有这个类的单例,并直接对Companion静态属性进行初始化。
public final class Book {
//..
public static final Book.Companion Companion = new Book.Companion((DefaultConstructorMarker)null);
//...
public static final class Companion {
//...
@NotNull
public final Book createAppleBooK() {
return new Book("Apple", (DefaultConstructorMarker)null);
}
@NotNull
public final Book createAndroidBooK() {
return new Book("Android", (DefaultConstructorMarker)null);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
复制代码
扩展方法机制容许在任何地方定义某类的扩展方法,但须要该类的实例进行调用。当须要扩展一个经过类自身调用的方法时,若是该类拥有伴生对象,能够经过对伴生对象定义扩展方法。
//对伴生对象定义扩展方法
fun Book.Companion.sellBooks(){
}
复制代码
当对该扩展方法进行调用时,能够直接经过类自身进行调用:
Book.sellBooks()
复制代码
做为android开发者,在设置监听时,建立匿名对象的状况再常见不过了。
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
复制代码
object关键字除了能用来声明单例式对象外,还能够声明匿名对象。和对象声明不一样,匿名对象不是单例,每次都会建立一个新的对象实例。
mRecyclerView.setOnClickListener(object :View.OnClickListener{
override fun onClick(v: View?) {
}
});
复制代码
当该匿名类拥有两个以上抽象方法时,才须要使用object建立匿名类。不然尽可能使用lambda表达式。
mButton.setOnClickListener {
}
复制代码