• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

二.Kotlin:面对对象

武飞扬头像
他叫拾贰
帮助1

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

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

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

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

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

  1.  
    public class Person{
  2.  
     
  3.  
    private String name;
  4.  
    private int age;
  5.  
     
  6.  
    public Person(String name,int age){
  7.  
    this.name = name ;
  8.  
    this.age = age ;
  9.  
     
  10.  
    }
  11.  
     
  12.  
    public String getName(){
  13.  
     
  14.  
    return name;
  15.  
    }
  16.  
     
  17.  
    public int getAge(){
  18.  
     
  19.  
    return age;
  20.  
    }
  21.  
    public void setAge(int age){
  22.  
     
  23.  
    this.age = age;
  24.  
    }
  25.  
     
  26.  
    }
学新通

在上面的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().

  1.  
    class Person(val name :String ,var age :Int ){
  2.  
    fun isAdult():Boolean{
  3.  
    return age >= 18
  4.  
    }
  5.  
     
  6.  
    }

又或者利用kotlin的简洁语法

  1.  
    lass Person(val name :String ,var age :Int ){
  2.  
    fun isAdult() = age >= 18
  3.  
     
  4.  
    }

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

  1.  
    lass Person(val name :String ,var age :Int ){
  2.  
    val isAdult
  3.  
    get() = age >= 18
  4.  
     
  5.  
    }

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

  1.  
    lass Person(val name :String ,var age :Int ){
  2.  
    val isAdult :Boolean
  3.  
    get(){
  4.  
    // 扒拉扒拉
  5.  
    retrun age >= 18
  6.  
    }
  7.  
     
  8.  
    }

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

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

  1.  
    class Person(val name :String){
  2.  
    var age : Int = 0
  3.  
    set(value:Int){
  4.  
    field = value
  5.  
    }
  6.  
     
  7.  
    }

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

抽象类与继承

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

  1.  
    abstract class Person(val name :String){
  2.  
    abstract fun walk()
  3.  
    }

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

继承

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

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

  1.  
    class Person(){
  2.  
    fun walk()
  3.  
    }
  4.  
     
  5.  
    class Boy:Person(){}
  6.  
     
  7.  
    //报错,如果一个类不是抽象类,并且没有open修饰的话,它是无法继承的
  8.  
     
  9.  
     
  10.  
     
  11.  
     
  12.  
    open class Person(){
  13.  
    open fun walk()
  14.  
    }
  15.  
     
  16.  
    class Boy:Person(){
  17.  
    override fun walk()
  18.  
    }
  19.  
     
  20.  
    //编译通过
学新通

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

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

接口和实现

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

  1.  
    interface Behavior {
  2.  
     
  3.  
    fun walk()
  4.  
    }
  5.  
     
  6.  
    class Person(val name :String) : Behavior {
  7.  
     
  8.  
    override fun walk(){
  9.  
     
  10.  
    }
  11.  
    }

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

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

  1.  
    interface Behavior {
  2.  
    //接口内可以有属性
  3.  
    val canWalk :Boolean
  4.  
    //接口方法的默认实现
  5.  
    fun walk(){
  6.  
    if(canWalk){
  7.  
     
  8.  
    }
  9.  
    }
  10.  
    }
  11.  
     
  12.  
    class Person(val name :String) : Behavior {
  13.  
    override val canWalk :Boolean
  14.  
    get() = true
  15.  
    }
学新通

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

嵌套

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

  1.  
    class A {
  2.  
    val name : String = ""
  3.  
    fun foo() = 1
  4.  
    class B{
  5.  
    val a = name
  6.  
    val b = foo()
  7.  
    //报错
  8.  
    }
  9.  
    }

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

  1.  
    class A {
  2.  
     
  3.  
    val name : String = ""
  4.  
    fun foo() = 1
  5.  
     
  6.  
    //加上关键字inner
  7.  
    inner class B{
  8.  
    val a = name
  9.  
    val b = foo()
  10.  
    //编译通过
  11.  
    }
  12.  
    }

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()

  1.  
    val tom = Person("Tom",18)
  2.  
     
  3.  
    val(name,age) = tom // 等价于 name =Tom,age = 18
  4.  
     
  5.  
    val mike = tom.copy(name = "Mike")

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

密封类

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

  1.  
    enum class Human{
  2.  
    MAN,WOMAN
  3.  
    }
  4.  
     
  5.  
    fun isMan(data:Human) = when (data){
  6.  
    Human.MAN -> true
  7.  
    Human.WOMAN -> false
  8.  
    }

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

  1.  
    println(Human.MAN == Human.MAN)
  2.  
    println(Human.MAN === Human.MAN)
  3.  
     
  4.  
    输出
  5.  
    true
  6.  
    true

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

  1.  
    sealed class Result<out R>{
  2.  
    data class Success<out T>(val data:T,val message:String = "") :Result<T>()
  3.  
     
  4.  
     
  5.  
    data class Error(val exception:Exception) : Result<Nothing>()
  6.  
     
  7.  
     
  8.  
    data class Loading(val time:Long = System.currentTimeMillis()):Result<Nothing>
  9.  
     
  10.  
     
  11.  
    }

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

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

  1.  
    fun display(data:Result) = when(data){
  2.  
    is Result.Success -> displaySuccessUi(data)
  3.  
    is Result.Error -> showErrorMsg(data)
  4.  
    is Result.Loading -> showLoading()
  5.  
     
  6.  
    }

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

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhhaakaj
系列文章
更多 icon
同类精品
更多 icon
继续加载