Kotlin基础(二)类、对象和接口

修饰符

访问修饰符
修饰符 相关成员 评注
final 不能被重写 类中成员默认使用
open 可被重写 需要明确什么
abstract 必须被重写 在抽象类中使用
override 重写父类或接口中的成员 若没有final声明,重写的成员默认是open

Java中可以创建任意类的子类并重写任意方法,除非显示声明final。对基类的修改会导致子类不正确的行为,即脆弱的基类问题。Effective Java建议“要么为继承做好设计并记录文档,要么禁止。”Kotlin采用该思想哲学,Java中类和方法默认是open的,而Kotlin中类和方法默认是final

open class RichButtion:Clickable{
    //默认是final不能被重写
    fun disable(){}
    //open可重写
    open fun animate(){}
    //override 方法默认是open
    override fun click(){}
}

接口和抽象类默认是open, 其抽象成员默认是open

abstract class Animate{
    //默认是open
    abstract fun animate()
    //非抽象方法默认是final
    fun animateTwice(){}
}
可见性修饰符
修饰符 类成员 顶层声明
public(默认) 所有地方可见 所有地方可见
internal 模块中可见 模块中可见
protected 子类中可见 ——
private 类中可见 文件中可见

Java中默认可见性——包私有,在kotlin中并没有。Kotlin只把包作为命名空间里组织代码的一种方式,并没有将其用作可见性控制。作为替代方案,Koltin是使用新的修饰符internal,表示“只能在模块内可见。”internal优势在于它对模块实现细节提供真正的封装。

接口

接口包含抽象方法的定义和非抽象方法的实现,但是他们都不能包含任何状态。

interface Clickable {
    //不支持backing-field,不能存储值
    var clickable: Boolean
    //默认open,可被重写
    fun click()
    //默认final,不能被重写
    fun showOff() = println("I'm Clickable")
}

由于Koltin 1.0Java 6为目标设计,其并不支持接口中的默认方法,因此会把每个默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类的结合体,如上面的接口反编译后看到:

public interface Clickable {
   boolean getClickable();
   void setClickable(boolean var1);
   void click();
   void showOff();
 
   public static final class DefaultImpls {
      public static void showOff(Clickable $this) {
         String var1 = "I'm Clickable";
         boolean var2 = false;
         System.out.println(var1);
      }
   }
}

构造函数

Kotlin构造函数相对于Java做了部分修改,区分主构造函数从构造函数初始化块中的代码实际上会成为主构造函数的⼀部分。委托给主构造函数会作为次构造函数的第⼀条语句,因此所有初始化块中的代码都会在次构造函数体之前执⾏。

class Person {
    init {
        println("Init block")
    }
    constructor(i: Int) {
        println("Constructor")
    }
}

在大多数场景中,类的构造函数非常简明:要么没有参数,要么直接把参数于对应的属性关联

class User(val nickname:String,val isSubscribed:Boolean=false)

如果类有主构造函数,每个从构造函数需要委托主构造函数,可直接委托或者间接委托。

class User(val nickname: String) {
    var isSubscribed: Boolean?=null
    constructor(_nickname: String, _isSubscribed: Boolean) : this(_nickname) {
        this.isSubscribed = _isSubscribed
    }
}

如何该类有父类,应该显式的调用父类的构造方法

//Clickable为接口,没有构造函数
class Buttion:Clickable{

}
//即便没有任何参数,也要显示调用父类构造函数
class RiseButton:Button(){
    
}
//如果有多级构造函数,可以super关键字调用父类构造
class MyButton: View {
    constructor(ctx:Context):super(ctx)
    constructor(ctx: Context,attributes: AttributeSet?):super(ctx,attributes)
}
内部类、嵌套类、密封类、数据类·
内部类和嵌套类

Kotlin中嵌套类不能访问外部类的实例,类似Java静态内部类;而Kotlin中的内部类需要用inner关键字修饰才能访问外部类的实例。

class Outer {
    private val bar: Int = 1
    //内部类
    inner class Inner {
        fun foo() = bar
    }
}
class Outer2 {
    private val bar: Int = 1
    //嵌套类,不持有外部类的引用
    class Nested {
        fun foo() = 2
    }
}
val demo = Outer().Inner().foo() // == 1
val demo2 = Outer2.Nested().foo() // == 2
密封类

密封类⽤来表⽰受限的类继承结构:当⼀个值为有限集中的类型、⽽不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在⼀个实例,⽽密封类的⼀个⼦类可以有可包含状态的多个实例。

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

⼀个密封类是⾃⾝抽象的,它不能直接实例化并可以有抽象(abstract)成员。
密封类不允许有⾮-private 构造函数(其构造函数默认为 private)。
请注意,扩展密封类⼦类的类(间接继承者)可以放在任何位置,⽽⽆需在同⼀个⽂件中。

数据类

创建⼀些只保存数据的类。 在这些类中,⼀些标准函数往往是从数据机械推导⽽来的。在
Kotlin 中,这叫做 数据类 并标记为 data :

data class User(val name: String, val age: Int)

编译器⾃动从主构造函数中声明的所有属性导出以下成员:

  • equals() / hashCode() 对;
  • toString() 格式是 "User(name=John, age=42)" ;
  • componentN()函数 按声明顺序对应于所有属性;
  • copy() 函数。

为了确保⽣成的代码的⼀致性以及有意义的⾏为,数据类必须满⾜以下要求:

  • 主构造函数需要⾄少有⼀个参数;
  • 主构造函数的所有参数需要标记为 val 或 var ;
  • 数据类不能是抽象、开放、密封或者内部的;
属性与字段

声明一个属性的完整语法为:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

其初始器(initializer)、getter 和 setter 都是可选.

一个只读属性的语法和一个可变的属性的语法有两方面的不同:

  • 只读属性用val,而可变属性用var声明
  • 只读属性不允许有setter方法

默认的属性的声明为:

var name: String = "Kotlin"
        get() = field
        set(value) {
            field = value
        }
Object关键字

Object关键字定义一个类并同时创建一个实体:

  • 对象声明:定义单例的方式
  • 伴生对象:可持有工厂方法及其他与类相关
  • 对象表达式:代替Java的匿名内部类

对象表达式和对象声明之间有⼀个重要的语义差别:

  • 对象表达式是在使⽤他们的地⽅⽴即执⾏(及初始化)的;
  • 对象声明是在第⼀次被访问到时延迟初始 化的;
  • 伴⽣对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
对象声明

对象声明将类的声明与该类的单一实例声明结合在一起。与普通类的实例不同,对象声明在定义的时候就创建了实例。

object PayRoll {
    val allEmployees = arrayListOf<Person>()

    fun calculateSalary(){  
    }
}

可以反编译看到:

对象声明被编译成通过静态字段来持有它的单一实例的类,字段名始终为INSTANCE

public final class PayRoll {
   @NotNull
   private static final ArrayList allEmployees;
   public static final PayRoll INSTANCE;

   @NotNull
   public final ArrayList getAllEmployees() {
      return allEmployees;
   }

   public final void calculateSalary() {
   }
    //构造函数私有
   private PayRoll() {
   }

   static {
      PayRoll var0 = new PayRoll();
       //静态代码块初始化化实例对象
      INSTANCE = var0;
      boolean var1 = false;
      allEmployees = new ArrayList();
   }
}
伴生对象

Javastatic关键字并不是kotlin的一部分,作为替代,kotlin依赖包级别的函数和对象声明,但是顶层函数不能访问类的私有成员, 需要写一个没有类实例情况下调用但需要访问类内部的函数,可以将其写为类中的对象声明的成员。

fun getFacebookName(accountId: Int) = "fb:$accountId"

class User private constructor(val nickname: String) {
    companion object {
        fun newSubscribingUser(email: String) =
            User(email.substringBefore('@'))

        fun newFacebookUser(accountId: Int) =
            User(getFacebookName(accountId))
    }
}

fun main(args: Array<String>) {
    val subscribingUser = User.newSubscribingUser("bob@gmail.com")
    val facebookUser = User.newFacebookUser(4)
    println(subscribingUser.nickname)
}

伴生对象作为普通对象,一样可以实现接口和扩展函数和属性

data class Person(val name: String) {
    object NameComparator : Comparator<Person> {
        override fun compare(p1: Person, p2: Person): Int =
            p1.name.compareTo(p2.name)
    }
}

class Person(val firstname:String,val lastname:String){
    companion object{
        //...可空,但不能省略
    }
}
fun Person.Companion.fromJson(json:String):String{
    return json.substring(4)
}
对象表达式

object不仅可用来声明单例对象,还可以声明匿名对象,替代java内部类的用法

fab.setOnClickListener(
     object : View.OnClickListener {
        override fun onClick(view: View?) {
         //....
        }
      })

当然,也可以将其存储到一个变量中:

val listener = object : View.OnClickListener {
    override fun onClick(p0: View?) {
       //....
    }
}

Java匿名内部类只能扩展一类或者实现一个接口,kotlin的匿名对象可以实现多个接口或者实现不同的接口。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容