Kotlin学习笔记之 6

作者: super_shanks | 来源:发表于2019-03-25 21:14 被阅读10次

6.Kotlin 继承

  • Any

    Any是所有类的超类,就像java中的Object。但它并不等同于Object,除了equals()``hashCode()``toString()没有其他任何成员。

  • 继承和实现':'

    之前我们已经知道了接口是interface,可被继承的类是open,抽象类是abstract

    跟java不同的是,java中extends是继承,implements是实现。而在kotlin中跟在:后面,不管是接口还是类,没有先后顺序。也就是说:既可以继承也可以实现,互相用,隔开

    open class Person3 {
         open var sex: String = "unknow"
     
         init {
             println("基类初始化")
         }
     }
     
     interface Grand {
         fun make()
     }
     
     class Student : Grand, Person3() {
         override fun make() {
         }
     
         override var sex: String = "male"
     }
    
  • 基类构造和子类构造

    首先我们先看下最基础的构造,在经过编译之后分别是什么情况的

    • 默认定义

      ```js
      //kt
      class Person(name: String) {    
      }
      
      //decompiled
      public final class Person {
         public Person(@NotNull String name) {
            //无视,这段代码之后会出现很多次
            Intrinsics.checkParameterIsNotNull(name, "name");
            super();
         }
      }
      ```
      

      我们看到经过编译之后,就是定义了一个final的class Personname只是一个构造函数的传参而已

    • var定义

      我们尝试着在构造传参name前加一个var看看,会发现编译之后的差别不是一点点。

      ```js
      //kt
      class Person(var name: String) {    
      }
      
      //decompiled
      public final class Person {
         @NotNull
         private String name;
      
         @NotNull
         public final String getName() {
            return this.name;
         }
      
         public final void setName(@NotNull String var1) {
            Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
            this.name = var1;
         }
      
         public Person(@NotNull String name) {
            Intrinsics.checkParameterIsNotNull(name, "name");
            super();
            this.name = name;
         }
      }
      ```
      

      编译过程帮我们创建了一个同名的属性name,并且提供了namesetget方法,也就是说一旦主构造函数中出现了var(我这边故意重点说了主构造函数,因为次构造函数中无法使用var),那么所描述的这个参数就可以变得在类中可以被访问和修改,name就变成了Person的一部分了。

OK,我们接下去看一下两种情况下作为基类被继承了之后会有什么地方的不同。

前面我们有提到,kotlin中无论使用什么修饰符,对于参数本身来说,编译成java代码之后始终是private的,只是通过getset方法来体现修饰符,

如果我们的子类是这么定义的:

//kt
class Sun(name : String): Person(name){   
}

//decompiled
public final class Sun extends Person {
    public Sun(@NotNull String name) {
       Intrinsics.checkParameterIsNotNull(name, "name");
       super(name);
    }
 }

看起来完全没有问题。这时候我们把name写上var,我们先不反编译,直接按照上面的理解,应该是这样的

```js
//kt
class Sun(var name : String): Person(name){ 
}

//decompiled
public final class Sun extends Person {
   @NotNull
   private String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public Sun(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}
```

乍一看,没有问题。但是实际上这么写IDE直接会报错。Sun的方法getNamesetNamepublic final的,由于父类也有相同的方法getNamesetName。犯了java的大忌,子类出现了父类的同名函数,父类的函数还他么是final的。

也就是说,除了父类是非final字段,子类不能出现相同的字段。

综上所述,只要不是open形容的字段,都不能在子类出现重复的字段名。无论是val还是var

  • varval复写关系

    val无法复写var,不要当做概念去背。

    前面我们说过,一个属性必须要用open来形容才能被子类复写。这是其一

    其二,kotlin中我们是直接去操作属性的,因为编译成java文件是通过gettersetter方法做访问和修改。var是可以访问并修改,val只能访问无法修改。

    父类中本身可以访问并修改的,在子类中变得只能访问了,是不允许的。继承关系我们只能拓展而不能缩减,子类必须包含父类。

  • 子类构造函数的影响

    首先,一个类class肯定会有一个构造函数。那么对于子类来说需要注意什么呢。

    我们先来看一个隐式主构造函数的类:

    open class Parent{
    }
    

    我们前面说到,一旦没有显式的跟在类名后面申明constructor,或者(),并且在类体中没有出现次级构造函数constructor。我们就认为有一个隐式的无参的主构造函数。

    这时候我们继承一下,并且尝试着什么都不做

    class Sun : Parent{
    }
    

    你会发现Person下面有根红线报错了,写道

    This type has a constructor, and thus must be initialized here

    父类有构造器,必须要进行初始化。也就是说我们必须显式或者隐式的去调一次Parent的无参的构造函数。

    方式有很多种,我们先看第一种:

    class Sun : Parent(){
    }
    

    直接在父类后面来个括号即可。

    你会发现加()和不加的前后经过编译得到的java文件是一样的,都是

    public final class Sun extends Parent {
    }
    

    实际上,这边我们用有参构造去讲,会更加容易理解。比如我们针对Parent类添加一个有参的主构造函数,并且保留无参构造函数。

    open class Parent(name : String){
    
    }
    

    子类分别调用和不调用父类的构造函数,所编译出来的效果是明显不同的。

    //kt
    class Sun1 : Parent{
    } 
    
    class Sun2 : Parent(){
    } 
    
    class Sun3 : Parent("tom"){
    }
    
    
    //decompiled
    public final class Sun1 extends Parent {
    }
    
    public final class Sun2 extends Parent {
    }
    
    public final class Sun3 extends Parent {
         public Sun3() {
            super("tom");
         }
    }
    

Sun1是无法编译通过的,所以出来的decompiled实际上是没有意义,上面说过调用父类的无参构造和不调用最终出来的结果是一样的,所以我们直接看Sun2即可

Sun2Sun3连在一起看,首先我们知道,在java中,每一个类如果没有单独的去申明他的构造函数,那么他们都将有一个默认的无参构造函数。并且又有继承,所以Sun2实际上是这样的

public final class Sun2 extends Parent {
      public Sun2() {
         super();
      }
 }

相关文章

网友评论

    本文标题:Kotlin学习笔记之 6

    本文链接:https://www.haomeiwen.com/subject/jklivqtx.html