Kotlin核心编程 第三章面向对象

3.1类和构造方法

class Bird{
    val weight:Double = 500.0
    val color:String = "blue"
    val age:Int = 1
    fun fly(){}//全局可见
    
}
///反编译后的java代码
public final class Bird{
    private final double weight = 500.0
    @NotNull
    private final String color = "blue"
    private final Int age = 1
    
    public final double getWeight(){
        return this.weight
    }
    @NotNull
    public final String getColor(){
        return this.color
    }
    public final Int getAge(){
        return this.age
    }
    public final void fly(){}
}

不同点:
1不可变的属性成员。利用java的final修饰符实现不可变的属性成员。
2属性默认值。因为java都有默认值(int为0,引用类型为null),所以在声明属性的时候我们不需要指定默认值。而在Kotlin中,除非显示地声明延迟初始化,否则就要指定属性的默认值。
3不同的可访问修饰符。Kotlin类中的成员默认是全局可见的,而java默认是包作用域,因此在java版本中要用public修饰符才能达到相同的效果。

接口:
接口方法的默认实现使得我们在向接口中新增方法的时候,之前继承过该接口的类则可以不需要实现这个新方法。

public interface Fly{
    public String kind()
    
    default public void fly(){//接口方法的默认实现,java8引入
        System.out.println("i can fly")
    }
}

kotlin的写法

interface Fly{
    val speed:Int
    fun kind()
    fun fly(){
        println("i can fly")
    }
}

支持抽象属性(如上面的speed),然而因为kotlin是支持java6的,那么他是如何支持这种行为的呢?转为java代码如下:

public interface Fly{
    int getSpeed();
    void kind();
    void fly();
    public static final class DefaultImpls{
        public static void fly(Fly $this){
            String var1 = "i can fly"
            System.out.println(var1)
        }
    }
}

通过定义一个DefaultImpls静态内部类,来提供fly方法的默认实现,同时虽然kotlin接口支持属性声明,然而他在java源码中是通过一个get方法来实现的。在接口中属性并不能像java那样直接被赋值一个常量。
如下代码是错误的:

interface Fly{
    val height = 1000
//Property initializers are not allowed in interfaces
}

kotlin提供了另一种方式实现这种效果

interface Fly{
    val height 
        get() = 1000
}

接口中的普通属性,同方法一样,若没有指定默认行为,则在接口的实现类中必须多该属性进行初始化。

java通过类的构造方法重载实现不同参数组合new对象。不同组合实现其的构造方法非常多;每个构造方法中存在相同赋值操作存在冗余。
kotlin采用新的构造方法

class Bird(val weight:Double = 0.00,val age:Int = 0,val color:String = "blue")
//可以省略{}
val b1 = Bird(color = "black")
val b2 = Bird(100.0,1,"black")
//val b3 = Bird(100.0,"black")//!!必须按照顺序赋值

构造方法中可以没有var和val,带上他们之后接等价于在类的内部声明了一个同名的属性。

class Bird(var name:String,var age:Int ){
    val weight = 1000;
}

等价于

class Bird(name:String,age:Int){
    var name:String? = null
    var age :Int = 0
    val weight = 1000;
    init{
        this.name = name 
        this.age  = age  
    }
}

kotlin引入了一种叫做init语句块的语法,属于构造方法的一部分。
kotlin规定所有类中所有非抽象属性成员都必须在对象创建时被初始化值。
延迟初始化:by lazy 和lateinit

class Bird(){
    val sex:String by lazy{
        "male"
    }
    fun print(){
        println(sex)
    }
}

by lazy特点:
变量必须是应用不可变的,而不能通过var声明
被首次调用时,才会执行赋值操作,一旦被赋值之后将不能被更改。(后续返回记录的结果,还会默认加上同步锁).

class Bird(){
    val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
        "male"
    }
    ...
}

lateinit主要用于var声明的变量,然而他不能用于基本数据类型,如Int,Long等我们需要用Integer这种包装类作为替代。

class Bird(){
    lateinit val sex:String 
    fun print(){
        this.sex =" male"
        println(sex)
    }
}

主从构造方法:
通过constructor方法定义了一个新的构造方法,被称为从构造方法。相应的我们熟悉的在类外部定义的构造方法叫做主构造方法。每个类最多存在一个主构造方法和多个从构造方法,如果柱构造方法存在注解或可见性修饰符,也必须像从构造方法一样加上constructor关键字。
每个从构造方法由两部分组成,对其他构造方法的委托,花括号包裹的代码块。
执行顺序上,主构造器>init代码块>次构造器

/**
 * kotlin 企鹅类
 */
class Penguin {
    var weight:Int=100  //体重
    var age:Int=0
    var name:String?=null

    //空参次构造方法,如果没有逻辑,可以省略大括弧
    constructor()
    //有参次构造方法,如果没有逻辑,可以省略括弧,不过有参的空构造方法是没有意义的
        //有参次构造器
    constructor(name:String,age:Int){
        this.name=name
        this.age=age
    }
}

当主构造器和次构造器同时存在时,次构造器必须直接或者间接调用主构造器

class Penguin constructor(name: String) {
    var weight:Int=100  //体重
    var age:Int=0
    var name:String?=null

    //空参次构造器,调用了下面的次要构造方法
    constructor():this("奔波儿霸",10)

    //有参次构造器,调用了主构造方法
    constructor(name:String,age:Int):this(name){
        this.name=name
        this.age=age
    }
}

与java相似,当我们不声明任何构造方法时,Kotlin编译器会默认给我们增加一个空参构造方法

class Penguin constructor() {
    var weight:Int=100  //体重
    var age:Int=0
    var name:String?=null
}

3.2不同的访问控制原则

kotlin中没有个采用java的extends 和implements关键词,而是用:来代替类的继承和接口的实现
由于kotlin中的类和方法默认是不可被继承或重写的,所以必须加上open修饰符

open class Bird(){
    open fun fly(){
        println("i can fly")
    }
}

class Penguin:Bird(){
    override fun fly(){
        println("i can not fly")
    }
}

*子类应该避免重写父类的非抽象方法--一旦父类方法变化,子类会出错,李氏替换原则
kotlin中类、方法和属性默认加了final修饰符


  • 如果你不指定任何可见性修饰符,默认为 public,这意味着你的声明将随处可见;
  • 如果你声明为 private,它只会在声明它的文件内可见;
  • 如果你声明为 internal,它会在相同模块内随处可见;
  • protected 不适用于顶层声明。

private 意味着只在这个类内部(包含其所有成员)可见;
protected—— 和 private一样 + 在子类中可见。
internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
public —— 能见到类声明的任何客户端都可见其 public 成员。

3.3解决多继承的问题

接口实现多继承
用super关键字指定继承哪个父类接口的方法:

fun main() {
    val b = Bird()
    println(b.kind())//[Flyer] flying animals
}

interface Flyer{
    fun kind() = "[Flyer] flying animals"
}
interface Animals{
    fun kind() = "[Animals] flying animals"
}
class Bird():Flyer,Animals{
    override fun kind () = super<Flyer>.kind()
    //或者主动覆盖父类接口
    //override fun kind () = "a flying Bird"
}

*kotlin编译器自动帮我们生成了getter和setter方法。
1用val声明的属性将只有getter方法,因为它不可修改;用var修饰的属性将同时拥有getter和setter方法。
2用private修饰的属性编译器会省略getter和setter方法,因为类外部已经无法访问他了,这两个方法也就没意义了。

内部类解决多继承问题的方案
java通过在内部类语法上增加一个static关键词,把它变成嵌套类;kotlin默认是嵌套类,必须加上inner关键字才是一个内部类,也就是说可以把静态内部类看成嵌套类。
内部类包含着对其外部类实例的引用,在内部类中可以使用外部类中的属性。

fun main() {
    val b = Bird()
    b.kindFly()
}

open class Flyer{
    fun kind() = println("[Flyer] flying animals")
}
open class Animals{
    fun kind() = println("[Animals] flying animals")
}
class Bird(){
    fun kindFly(){
        FlyerC().kind()
    }
    fun kindAnimals(){
        AnimalsC().kind()
    }
    
    private inner class FlyerC:Flyer()
    private inner class AnimalsC:Animals()
}

使用委托代替多继承:by 关键字

fun main() {
    val flyer = Flyer()
    val animal = Animal()
    
    val b = Bird(flyer,animal)
    b.fly()
    b.eat()
}
class Bird(flyer:Flyer,animal:Animal):CanFly by flyer,CanEat by animal{}

interface CanFly{
    fun fly()
}
interface CanEat{
    fun eat()
}

open class Flyer:CanFly{
    override fun fly(){
        println("i can cly")
    }
}

open class Eater:CanEat{
    override fun eat(){
        println("i can eat")
    }
}


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

//
i can cly
Animal can eat

委托使使用时更加直观,比如需要继承A,委托对象是BC,具体调用时不是A.B.method()而是A.method()。

真正的数据类 data class

data class Bird(var weight:Double,var age:Int)

一行代码就拥有了getter/setter,equals,hashcode,构造函数,还有两个特别的方法copy和componentN

*data class 之前不能用abstract、open、inner、sealed修饰

3.5从static到object

伴生对象companion object
伴生是相对一个类而言,意为伴随某个类的对象,因此伴生对象跟java中static修饰效果性质一样,全局只有一个单例。他需要声明在类的内部,在类被装载时会被初始化。

fun main() {
    println("Hello, world!!!")

    val prize = Prize("红包",10,Prize.TYPE_REDPACK)
    println(Prize.isRedPack(prize))
}
class Prize(val name:String,val count:Int,val type:Int){
    companion object{
        val TYPE_REDPACK = 0
        val TYPE_COUPON = 1
        fun isRedPack(prize :Prize):Boolean{
            return prize.type == TYPE_REDPACK
        }
    }
}

伴生对象也是实现工厂方法模式的一种思路

fun main() {
    val redpackPrize = Prize.newRedPackPrize("红包",10)
    val couponPrize = Prize.newCouponPrize("代金券",10)
    val commonPrize = Prize.defaultCommonPrize()
}
class Prize(val name:String,val count:Int,val type:Int){
    companion object{
        val TYPE_COMMON = 1
        val TYPE_REDPACK = 2
        val TYPE_COUPON = 3
        
        val defaultCommonPrize = Prize("普通奖品",10,Prize.TYPE_COMMON)
        
        fun newRedPackPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_REDPACK)
        fun newCouponPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_COUPON)
        fun defaultCommonPrize() = defaultCommonPrize
    }
}

天生的单例object
可以直接用它来实现单例

object DatabaseConfig{
    var host :String = "127.0.0.1"
    var post :int= 3306
}

由于object全局声明的对象只有一个,所以他并不用语法上的初始化,甚至都不需要构造方法。

他还有一个作用就是替代java中的匿名内部类

List <String> list = ArrayList.asList("red","ss","cc")
Collections.sort(list,new Comparator<String>(){
    @Override
    public int compare(String s1,String s2){
        if(s1==null)
        return -1;
        if(s2==null)
        return 1;
        return s1.cpmpareTo(s2)
    }
    
})
//利用object表达式对它进行改善
val comparator = object:Compararor<String>{
    override fun compare(s1:String?,s2:String?):Int{
        if(s1==null)
            return -1
        else if(s2 == null)
            return 1
        return s1.compareTo(s2)
    }
}
Collections.sort(list,comparator)

优点:
object表达式可以赋值给一个变量,重复使用时减少很多代码;
object可以继承和实现接口,匿名内部类只能继承和实现一个接口,而object却没有这个限制。

用于代替匿名内部类的object表达式在运行中不像我们在单例中那样全集只有一个对象,而是每次运行时都会生成一个对象

当匿名内部类使用的类的接口只需要实现一个方法时,使用lambda表达式更适合,当匿名内部类有多个方法实现的时候,使用object表达式更加合适。

val comparator = Compararor<String>{s1,s2->
    if(s1==null)
        return @Comparator-1
    else if(s2 == null)
        return @Comparator1
    return s1.compareTo(s2)
}
Collections.sort(list,comparator)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章