二.Kotlin之面对对象

二.Kotlin之面对对象,第1张

面对对象:把程序的数据和方法当作为一个整体来看待

在java和kotlin中,都有类,接口,继承,嵌套,枚举的概念,唯一的区别就是这些概念在具体的语法不同而已,在kotlin中也比java多一些东西,比如数据类,密封类,密封接口等。

kotlin中的类,我们可以将其理解成对某种事物的抽象模型,比如人

class Person(val name : String, var age:Int)

而我们用java来写的话是这样的

public class Person{

 private String name;
 private int age;
    
 public Person(String name,int age){
    this.name = name ;
    this.age  = age  ;

   }
   
 public String getName(){
    
        return name;
 }

 public int getAge(){

        return age;
    }
 public void setAge(int age){

   this.age = age;
}

}

在上面的Person类中 分别定义了两个属性,分别是name,age。在kotlin 中,我们name 属性是用val修饰的,这意味着它在初始化后无法被修改,对应java中,就是该变量只有getter没有setter,而age则是用var修饰,这意味它可以被随意修改,对应java中,既有getter也有setter。

再者,kotlin定义的类,默认是public的,编译器会为我们生成构造函数,对于类中的属性,也会为我们自动生成getter和setter。

自定义属性 getter and setter

如果想要实现一个功能,就是根据年龄的大小判断是不是成年人,也就是age>=18.

如果按照我们java思维,我会给Person定义一个新的方法,isAdult().

class Person(val name :String ,var age :Int ){
    fun isAdult():Boolean{
        return age >= 18
    }

}

又或者利用kotlin的简洁语法

lass Person(val name :String ,var age :Int ){
    fun isAdult() = age >= 18
   
}

这样已经算不错的写法了,然而kotlin有一种更符合直觉的写法,那就是把isAdult()定义为属性,也就是kotlin属性的自定义getter

lass Person(val name :String ,var age :Int ){
    val isAdult
        get() = age >= 18

}

如果get()内部逻辑比较复杂的话,我们可以这样写

lass Person(val name :String ,var age :Int ){
    val isAdult :Boolean
        get(){
            //    扒拉扒拉
            retrun age >= 18
        }

}

这里要注意一下,这种情况下编译器的类型推导就会失效,就需要加上明确的类型Boolean

这里会想, 如果需要用setter的呢,我们可以这样写

class Person(val name :String){
    var age : Int = 0
        set(value:Int){
            field = value    
        }

}

kotlin编译器为我们提供字段field,field = value 就实现了我们对age的赋值 *** 作。

抽象类与继承

在kotlin中,抽象类的定义跟java的几乎一样,也就是在关键字“class” “fun”的前面加上abstract关键字即可。我们把Person定义为抽象类,然后为它添加一个抽象方法:

abstract class Person(val name :String){
    abstract fun walk()
}

这样一来,我们要创建Person类,就必须要使用匿名内部类的方式,或者使用Person的子类来创建变量,而这里,我们就需要用到类的继承了;

继承

在java中,我们继续是需要用extends关键字,而kotlin 是直接使用冒号表示继承。

在kotlin中,除了抽象类以外,正常的类其实是可以被继承的,不过我们必须对这个类标识为open

class Person(){
    fun walk()
}

class Boy:Person(){}
 
//报错,如果一个类不是抽象类,并且没有open修饰的话,它是无法继承的




open class Person(){
    open fun walk()
}

class Boy:Person(){
    override fun walk()
}

//编译通过

对于被open修饰的普通类,它的内部方法,属性默认都是不可以重写的,除非它们也用open修饰

在继承的行为上,kotlin和java完全相反,Java当中,一个类如果没有被final修饰的话,它默认是可以被继承的。也就是说,Java的继承是默认开放的,Kotlin的继承是默认封闭的

接口和实现

Kotlin当中的接口和java也是大同小异,它们都是通过interface定义

interface Behavior {

    fun walk()
}

class Person(val name :String) : Behavior {

    override fun walk(){

    }
}

我们定义一个新的接口Behavior,它里面有一个需要被实现的方法walk。我们可以发现,Kotlin的继承跟接口都是使用冒号去实现的

kotlin跟java接口的最大的差异,就是kotlin在接口可以有默认实现的情况下,同时它也可以有属性

interface Behavior {
    //接口内可以有属性
    val canWalk :Boolean
    //接口方法的默认实现
    fun walk(){
        if(canWalk){
        
        }
    }
}

class Person(val name :String) : Behavior {
        override val canWalk :Boolean
            get() = true
    }

我们在Behavior接口当中定义了一个属性canWalk,代表是否可以行走,并且在接口方法中,默认实现了walk。我们可以看到,在person类中我们就可以不必实现walk方法了。

嵌套

在kotlin中,嵌套类跟java一样,分为两种:非静态内部类,静态内部类。

class A {
    val name : String = ""
    fun foo() = 1
    class B{
        val a  = name 
        val b  = foo()
        //报错
    }
}

看一下上面的代码,B是A的嵌套类,这非常容易理解,不过我们这样写编译器报错,我们无法在B类当中访问A类的属性和成员方法。如果有java的基础,应该可以发现这样的写法对应java当中的静态内部类!所以说,kotlin当中的普通嵌套类,它本质上是静态的,如果你想定义一个普通的内部类的话,我们需要在嵌套类的前面加上关键字 inner 关键字。

class A {

    val name : String = ""
    fun foo() = 1

//加上关键字inner
   inner class B{
        val a  = name 
        val b  = foo()
        //编译通过
    }
}

inner关键字,代表B类是A类的内部类,这种情况下B类的内部是可以访问A类的成员属性和方法的

kotlin的设计非常巧妙,如果你熟悉java开发,你会知道,java当中的嵌套类, 如果没有static关键字修饰的话(我们经常会忘记加static关键字),它就是个内部类,这样的内部类会持有外部类的引用的,而这样会非常容易出现内存泄漏。

Kotlin中的特殊类

数据类

用于存放数据的类,定义一个数据类,我们只需要在普通类的前面加上一个关键字data就可以

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

在kotlin当中,编译器会为数据类自动生成一些有用的方法

如:

equals()

hashCode()

toString()

componentN();

copy()

val tom = Person("Tom",18)

val(name,age) = tom  // 等价于 name =Tom,age = 18

val mike = tom.copy(name = "Mike")

val(name ,age) = Tom这行代码其实使用了数据类的解构声明,这种方式可以让我们快速通过数据类来创建一连串的变量。另外就是copy方法,我们可以拷贝的同时,修改某个属性 。

密封类

我们先看看枚举类,我们通过enum就可以定义枚举类,所谓枚举,就是一组有限的数量的值,比如,人分为男人女人,这样的非类是有限的,我们可以枚举出每一种情况,我们在when表达式使用枚举时,编译器甚至能自动帮我们推导出逻辑是否完备。这就是枚举的优势

enum class Human{
    MAN,WOMAN
}

fun isMan(data:Human) = when (data){
    Human.MAN -> true
    Human.WOMAN -> false
}

但是,枚举也有局限性。比如我们想判断枚举的结构相等和引用相等时,结果始终都为ture,而这代表了,每一个枚举的值,它在内存当中始终都是同一个对象引用。

println(Human.MAN == Human.MAN)
println(Human.MAN === Human.MAN)

输出
true
true

那么万一,我们想要枚举的值拥有不一样的对象引用,那么我们就需要使用密封类,定义密封类,我们需要使用sealed关键字,在android开发中,我们会经常使用密封类对数据进行封装,比如

sealed class Result{
    data class Success(val data:T,val message:String = "") :Result()
    

    data class Error(val exception:Exception) : Result()
    
    
    data class Loading(val time:Long = System.currentTimeMillis()):Result


}

首先我们使用sealed关键字定义了Result类并且它需要一个泛型参数R,R前面的out我们可以暂时先忽略。

这个密封类。我们是用来封装网络请求结果的。可以看到在Result类中,分别有三个数据类,分别是Success,Error,Loading,这样当请求返回结果后,我们将其与kotlin协程中的when表达式相结合,我们UI展示逻辑就会变得非常简单,成功,失败,进行中,我们可以相对应的显示

fun display(data:Result) = when(data){
 is Result.Success -> displaySuccessUi(data)
 is Result.Error -> showErrorMsg(data)
 is Result.Loading -> showLoading()

}

由于我们的密封类只有这三种情况,所以我们的when表达式不需要else分支,可以看到,这样的代码风格,即实现了类似枚举类的逻辑完备性,还完美实现了数据结构的封装。

下一篇我会复习一下object关键字的语意以及具体使用场景

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/723772.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存