Kotlin(二)面向对象

面向对象

2.1 kotlin中的类

class Bird {
    val weight: Double = 500.0
    val color: String = "blue"
    val age: Int = 1
    fun fly() {}// 全局可见
}
  1. 不可变属性成员。用val声明不可见成员,底层是java 的final实现
    2)属性默认值,除非显示声明延迟初始化,否则必须指定属性默认值。java 不用指定,引用默认null,其他对应有默认值
    3)不同的可访问的修饰符。kotlin默认是全局可见。java 默认是包可见,java 必须用public才能达到同样效果

可带有属性和默认方法的接口

Java 8 中引入

//java 8 默认实现
public interface Flyer {
    long height = 1_000l;
    public String kind();
    default public void fly(){
        System.out.println("I can fly");
    }
}

kotlin 是基于java6 但是,同样的实现,反编译底层使用final static final class DefaultImpl实现

//kotlin 中的接口
interface Flyer {
    val speed: Int
    val height: Long
        get() = 1_000L

    fun kind()
    fun fly() {
        println("I can fly")
    }
}

虽然,kotlin接口支持属性声明,但是通过一个get方法来实现,对比不难发现

2.2 更简洁构造类的对象

    // kotlin 直接声明一个对象
    val bird = Bird()

对于Java来说有多个重载

package top.zcwfeng.java.base;
public class Bird {
    public double weight;
    public String color;
    public int age;
    public Bird(double weight) {
        this.weight = weight;
    }
    public Bird(String color, int age) {
        this.color = color;
        this.age = age;
    }
    public Bird(double weight, String color, int age) {
        this.weight = weight;
        this.color = color;
        this.age = age;
    }
}

缺陷:

  • 支持任意多个参数组合创建对象,实现多个构造方法(用设计及模式 Builder模式能解决,但是还是比较麻烦,各种组合对外暴露)
  • 每个构造方法餐宿不同冗余严重

Kotlin 构造方法的解决

  1. 多参数指定默认值,避免不必要的重载
  2. init 语句块,属于构造的一部分,但是确实分离开的,对初始化进行额外操作用
class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
    val weight: Double
    val color: String
    val age: Int 
    fun fly() {}
    //构造方法可以在init 块调用
    init {
        this.weight = weight
        this.color = color
        this.age = age
    }
    init {
        println("多个init")
    }
}

可以有多个init 可以将初始化进行职能分离

3. 延迟初始化: by lazy 和 lateinit

by lazy

class Bird3Bird(val weight: Double, age: Int, color: String ) {
    val sex:String by lazy{
        if (color=="yellow") "male" else "female"
    }
}
  • by lazy 必须是引用不可变,而且不能通过var声明
  • 在第一次调用是,才被赋值,赋值 后不能更改

lazy 背后是接受一个lambda 并且返回一个Lazy<T>实例的函数

/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
 * the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
 */
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

第一此 访问执行lazy的lambda表达式并记录结果,后续访问该属性,只返回记录的结果。
系统会默认给lazy属性加上同步锁,也就是LazyThreadSafetyMode.SYNCHRONIZED,在同一时刻只允许一个线程对lazy属性进行初始化,所以他是线程安全的。

如果 你能确认该属性可以并行执行,没有线程安全问题,那么可以给lazy传递LazyThreadSafetyMode.PUBLICATION参数。

还可以传递LazyThreadSafetyMode.NONE,不会有任何线程开销,这个没有线程安全的保证了。

class Bird3Bird(weight: Double, age: Int, color: String ) {
    //并行模式
    val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
        if (color=="yellow") "male" else "female"
    }
    // 不安全,没有线程开销
    val sex2:String by lazy(LazyThreadSafetyMode.NONE){
        if (color=="yellow") "male" else "female"
    }
}

lazyinit

用于var不能用于基本类型,要用Integer等包装

class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
    val weight: Double
    val color: String
    val age: Int
    fun fly() {}
    //构造方法可以在init 块调用
    init {
        this.weight = weight
        this.color = color
        this.age = age
    }
    
    lateinit var sex:String
    
    fun printSex(){
        if (color=="yellow") "male" else "female"
        print(sex)
    }

    init {
        println("多个init")
    }
}

lateinit var 用语法Delegates.notNull<T>实现,举个栗子:

// 委托 lateinit var 实现原理
var test by Delegates.notNull<Int>()
fun doSomething(){
    test = 1
    println("test value is ${test}")
    test = 2
}

2.3 主从构造方法

class Bird4(age: Int) {

    var age: Int

    constructor(color: String):this(0) {
        //....
        println("------$color-----从构造")
    }

    constructor(color: String,weight: Double):this(0) {
        //....
        println("------$color-----从构造2")
    }

    init {
        this.age = age
        println("init")
    }


}

kotlin也支持多个构造方法。这个看Android中自定义view多个重载构造
目的方便扩展第三方库

2.4 限制修饰符,访问控制

  • kotlin中的extends和implemens用的是符号”:“
  • kotlin默认是不能继承和重写的,必须加上关键字open 才可以

open class Bird2 {
    val weight: Double = 500.0
    val color: String = "blue"
    val age: Int = 1
    open fun fly() {}// 全局可见
}

class Penguin: Bird2() {
    override fun fly() {
        super.fly()
    }
}

根据里氏替换原则类默认final的,如果fly 不加open也是不允许继承的

除了利用final外,kotlin还可以用sealed密封类来限制继承

要继承sealed类,就必须在同一个文件中,其他文件无法实现。而且sealed类是抽象类实现不能被初始化,背后实现是一个抽象类

修饰符 含义 与Java对比
public Kotlin中默认全局可见 与java中的public相同
protected 受保护修饰,类和子类可见 含义一致,除了类和子类,包内可见
private 私有修饰,类内修饰只有本类可见,类外修饰只有文件内可见 只有类内可见
internal 模块内可见

2.4 多继承问题

C++ 支持多继承的。有一个经典问题---> 钻石问题---骡子的多继承困惑,我们用类图表示

2020-12-19 16.23.53.png

也叫做菱形问题,但是kotlin和java一样支持单继承

kotlin可以通过其他方式完成多继承需求

接口实现多继承

一个类实现多个接口这个是java经常干的事。kotlin也是这样。


//多继承
interface Flyer1{
    fun fly()
    fun kind() ="[Flyer]flying animals"
}
interface Animal{
    val name:String
    fun eat()
    fun kind() = "[Animal] flying animals"
}

class Bird6(override val name:String):Flyer1,Animal{
    override fun eat() {
        println("i can eat")
    }

    override fun fly() {

        println("I can fly")
    }

    override fun kind() = super<Flyer1>.kind()
}

Bird6 继承了Flyer1和Animal接口,但他们都用有默认kind的实现,同样会引起钻石问题。kotlin 提供了super关键字解决问题。可以指定继承那个父类接口super<Flyer1>.kind(). 也可以实现kind覆盖父类方法

1)Kotlin 中实现一个接口,需要实现没有默认实现的方法以及未初始化的属性。同时实现多个接口有相同名字的方法默认实现,需要主动指定使用那个接口的方法或者重写方法
2)如果是默认的接口方法,可以在实现类中用super<T> 的方式调用,T为拥有改方法的接口名字
3)在实现接口的方法和属性时候,需要带上Override关键字,不能省略

我们是通过主构造方法实现的Animal接口中的name属性。
还可以通过val 声明构造参数,其实是在内部定义了一个同名的属性

class Bird6(name:String):Flyer1,Animal{
    override val name:String
    
    init {
        this.name = name
    }
    
    override fun eat() {
        println("i can eat")
    }

    override fun fly() {

        println("I can fly")
    }

    override fun kind() = super<Flyer1>.kind()
}

getter 和 setter

koltin没有字段,只有属性。幕后都帮着实现好了setter和getter
当然我们自己可以主动声明。

1)val 声明的属性只有 getter 因为不可以修改;var 声明可同时拥有setter和getter
2)用private声明的属性,编译器会省略getter和setter因为外部已经无法访问了

这个和C# setter getter 相似

内部类解决多继承问题

java 内部类

public class OuterJava {
    private String name ="Java inner clas syntax";
    class InnerJava{
        public void printName(){
            System.out.println(name);
        }
    }
}

Kotlin 必须用inner 修饰内部类否则相当于java中static修饰的嵌套类

// 内部类
class OuterKotlin{
    val name = "This is kotlin inner class syntax"
    inner class InnerKotlin{
        fun printName(){
            println(name)
        }
    }
}

java 加static 嵌套类,不加内部类
kotlin 不加 嵌套类,加 inner内部类

内部类可以包含对外部name属性的引用,嵌套类不行

open class Hourse{
    fun runFast(){
        println("I can run fast")
    }
}

open class Dounkey{
    fun runFast(){
        println("I can work long time")
    }
}

class Mule{
    fun runFast(){
        HorseC().runFast()
    }
    
    fun doLongTimeThing(){
        
        DounkeyC().runFast()
    }
    private inner class HorseC:Hourse()
    private inner class DounkeyC:Dounkey()
}
  1. 类内部定义多个内部类,每个内部类都有自己独立的状态,与外部对象信息相互独立
    2)让HorseC和DonkeyC 分别集成Horse,Donkey.就可以在Mule中定义他们的实现从二获得不同的状态和行为
    3)利用private 外部类不能访问内部,具有很好的封装性
    因此某种场景,内部类也可以实现多继承

使用委托代替多继承

委托,(C# 里面有delegate思想是一样的)我们之前有提过by lazy,除了延迟外,委托还有观察的行为,比较像观察者模式。

open class Flyer2 : CanFly {
    override fun fly() {
        println("I can fly")
    }
}

open class Animal2 : CanEat {
    override fun eat() {
        println("I can eat")
    }
}

class Bird7(flyer: Flyer2, animal: Animal2) : CanFly by flyer, CanEat by animal {}

// 委托
    val mFly = Flyer2()
    val mAnimal = Animal2()
    val mBird = Bird7(mFly,mAnimal)
    mBird.fly()
    mBird.eat()

1) 接口是无状态,只能提供默认简单实现。利用委托的方式,虽然是接口委托,但是用类实现,可以有更强大的能力
2)A 委托 B,C 并不是想组合一样A.B.method 而是直接A.metod 更加直观

2.5 数据类

Java 中 定义Bean很麻烦。setter,getter .有时候需要重写hashcode,equals方法

而kotlin只需要一行,data class 声明

data class Bird8(var weight:Double,var age:Int,var color:String)

反编译后,和javaBean 一模一样,多了两个方法copy和componentN

copy 帮助我们实现了简单的浅拷贝相当于,数据类被var修饰,引用是可以修改的

componentN 对应的具体按顺序属性,kotlin中称为解构。

还提供了元组类Pair 两个属性 Triple 三个属性

// 元祖
    val pair = Pair("a",11)
    val triple = Triple(2,3.0,"aaa")
    val name= pair.first
    val pV = pair.second
    val tripA = triple.first
    val tripB = triple.second
    val tripC= triple.third
    // 解构
    val (pName,pValue) = Pair("name",2)
    val (ta,tb,tc)= Triple(2,3,"blue")
    

数据类的使用约定

  • 数据类必须有一个构造方法,至少包含一个参数,无参没意义
  • 数据类方法强制使用var 或者val,普通类可以不强制
  • data class 之前不能用abstract open sealed 或者innder修饰
  • data class kotlin 1.1 之后可以实现接口也可以继承类

2.6 object

kotin不会出现static,因为object替代了他

kotlin 伴生对象,cmpaanion object{} 替代了java中的static 声明的类中定义的属性

Java 中的类

public class Prize {
    private String name;
    private int count;
    private int type;

    public Prize(String name, int count, int type) {
        this.name = name;
        this.count = count;
        this.type = type;
    }
    static int TYPE_REDPACK = 0;
    static int TYE_COUPON = 1;
    static boolean isRedPackl(Prize prize){
        return prize.type == TYPE_REDPACK;
    }

    public static void main(String[] args) {
        Prize prize = new Prize("红包",10,Prize.TYPE_REDPACK);
        System.out.println(prize.isRedPackl(prize));
    }
}

Kotlin 伴生类替换static

class Prize private constructor(val name: String, val count: Int, val type: Int) {
    companion object {
        val TYPE_COMMON = 0
        val TYPE_REDPACK = 1
        val TYE_COUPON = 2

        val defaultCommonPrize = Prize("普通奖品", 10, Prize.TYPE_COMMON)
        fun newRedPackPrize(name: String, count: Int) = Prize(name, count, Prize.TYPE_REDPACK)
        fun newCoupo(name: String, count: Int) = Prize(name, count, Prize.TYE_COUPON)
        fun defaultCommonPrize() = defaultCommonPrize
    }
}


 // 伴生
    val redPackPrize = Prize.newRedPackPrize("红包",10)
    val couplePrize = Prize.newCoupo("十元代金券",10)
    val commonPrize = Prize.defaultCommonPrize()

伴生对象是实现工厂方法的一种方式。

object 是天生单例

object DataConfig{
  var host:String = "127.0.0.1"
}

object 全局声明对象只有一个,并不用语法上面初始化,甚至不需要构造方法。

object 表达式

Java 中的匿名内部类

public class 匿名内部类 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("jffy", "lrri", "tony");
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                if (o1 == null)
                    return -1;
                if (o2 == null)
                    return 1;
                return o1.compareTo(o2);
            }
        });
    }
}

Kotlin object 表达式改善

val comparator = object : Comparator<String>{
    override fun compare(o1: String?, o2: String?): Int {
        if(o1 == null)
            return -1
        else if(o2 ==null)
            return 1
        return o1.compareTo(o2)
    }
}

    Collections.sort(list,comparator)

object 表达式可以赋值给一个变量,而且每次运行都会生成一个新的对象

使用Lambda表达式改造

Java

Collections.sort(list,(String o1, String o2)->{
            if (o1 == null)
                return -1;
            if (o2 == null)
                return 1;
            return o1.compareTo(o2);
        });

Kotlin

val comparator2 = Comparator<String> { o1, o2 ->
    if (o1 == null)
       return@Comparator -1
    else if (o2 == null)
        return@Comparator 1
    o1.compareTo(o2)

}

匿名内部类接口需要实现一个方法的时候Lambda更加适合
匿名内部类有多个方法的时候object更加适合

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

推荐阅读更多精彩内容