美文网首页Android 进阶之路Android开发Android开发经验谈
重拾Kotlin(6)-抽象类、数据类、密封类、枚举类、匿名内部

重拾Kotlin(6)-抽象类、数据类、密封类、枚举类、匿名内部

作者: 业志陈 | 来源:发表于2019-05-23 00:32 被阅读1次

    一、抽象类、数据类、密封类、枚举类、匿名内部类、内部类、嵌套类

    1.1、抽象类

    声明为 abstract 的类内部可以包含没有实现体的成员方法,且该成员方法也用 abstract 标记,这种类称为抽象类,包含的没有实现体的方法称为抽象方法

    此外,我们并不需要用 open 标注一个抽象类或者抽象方法,因为这是默认声明的

    abstract class BaseClass {
        abstract fun fun1()
    }
    
    class Derived : BaseClass() {
        override fun fun1() {
            
        }
    }
    

    1.2、数据类

    数据类是一种非常强大的类,可以避免重复创建 Java 中的用于保存状态但又操作非常简单的 POJO 的模版代码,它们通常只提供了用于访问它们属性的简单的 getter 和 setter

    定义一个新的数据类非常简单,例如

    data class Point(val x: Int, val y: Int)
    

    数据类默认地为主构造函数中声明的所有属性生成了如下几个方法

    • getter、setter(需要是 var)
    • componentN()。按主构造函数的属性声明顺序进行对应
    • copy()
    • toString()
    • hashCode()
    • equals()

    为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:

    • 主构造函数需要包含一个参数
    • 主构造函数的所有参数需要标记为 val 或 var
    • 数据类不能是抽象、开放、密封或者内部的

    可以利用 IDEA 来反编译查看 Point 类的 Java 实现,了解其内部实现

    public final class Point {
       private final int x;
       private final int y;
    
       public final int getX() {
          return this.x;
       }
    
       public final int getY() {
          return this.y;
       }
    
       public Point(int x, int y) {
          this.x = x;
          this.y = y;
       }
    
       public final int component1() {
          return this.x;
       }
    
       public final int component2() {
          return this.y;
       }
    
       @NotNull
       public final Point copy(int x, int y) {
          return new Point(x, y);
       }
    
       // $FF: synthetic method
       // $FF: bridge method
       @NotNull
       public static Point copy$default(Point var0, int var1, int var2, int var3, Object var4) {
          if ((var3 & 1) != 0) {
             var1 = var0.x;
          }
    
          if ((var3 & 2) != 0) {
             var2 = var0.y;
          }
    
          return var0.copy(var1, var2);
       }
    
       public String toString() {
          return "Point(x=" + this.x + ", y=" + this.y + ")";
       }
    
       public int hashCode() {
          return this.x * 31 + this.y;
       }
    
       public boolean equals(Object var1) {
          if (this != var1) {
             if (var1 instanceof Point) {
                Point var2 = (Point)var1;
                if (this.x == var2.x && this.y == var2.y) {
                   return true;
                }
             }
    
             return false;
          } else {
             return true;
          }
       }
    }
    
    

    通过数据类可以简化很多的通用操作,可以很方便地进行:格式化输出变量值、映射对象到变量、对比变量之间的相等性、复制变量等操作

    fun main(args: Array<String>) {
        val point1 = Point(10, 20)
        val point2 = Point(10, 20)
        println("point1 toString() : $point1") //point1 toString() : Point(x=10, y=20)
        println("point2 toString() : $point2") //point2 toString() : Point(x=10, y=20)
    
        val (x, y) = point1
        println("point1 x is $x,point1 y is $y") //point1 x is 10,point1 y is 20
    
        //在 kotlin 中,“ == ” 相当于 Java 的 equals 方法
        //而 “ === ” 相当于 Java 的 “ == ” 方法
        println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
        println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
    
        val point3 = point1.copy(y = 30)
        println("point3 toString() : $point3") //point3 toString() : Point(x=10, y=30)
    }
    

    需要注意的是,数据类的 toString()、equals()、hashCode()、copy() 等方法只考虑主构造函数中声明的属性,因此在比较两个数据类对象的时候可能会有一些意想不到的结果

    data class Point(val x: Int) {
    
        var y: Int = 0
    
    }
    
    fun main(args: Array<String>) {
        val point1 = Point(10)
        point1.y = 10
    
        val point2 = Point(10)
        point2.y = 20
    
        println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
        println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
    }
    

    1.3、密封类

    Sealed 类(密封类)用于对类可能创建的子类进行限制,用 Sealed 修饰的类的直接子类只允许被定义在 Sealed 类所在的文件中(密封类的间接继承者可以定义在其他文件中),这有助于帮助开发者掌握父类与子类之间的变动关系,避免由于代码更迭导致的潜在 bug,且密封类的构造函数只能是 private 的

    例如,对于 View 类,其子类只能定义在与之同一个文件里,Sealed 修饰符修饰的类也隐含表示该类为 open 类,因此无需再显式地添加 open 修饰符

    sealed class View {
    
        fun click() {
    
        }
    
    }
    
    class Button : View() {
    
    }
    
    class TextView : View() {
    
    }
    

    因为 Sealed 类的子类对于编译器来说是可控的,所以如果在 when 表达式中处理了所有 Sealed 类的子类,那就不需要再提供默认分支

    fun check(view: View): Boolean {
        when (view) {
            is Button -> {
                println("is Button")
                return true
            }
            is TextView -> {
                println("is TextView")
                return true
            }
        }
    }
    

    1.4、枚举类

    Kotlin 也提供了枚举的实现,相比 Java 需要多使用 class 关键字来声明枚举

    enum class Day {
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
    }
    

    枚举可以声明一些参数

    enum class Day(val index: Int) {
        SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)
    }
    

    此外,枚举也可以实现接口

    interface OnChangedListener {
    
        fun onChanged()
    
    }
    
    enum class Day(val index: Int) : OnChangedListener {
        SUNDAY(0) {
            override fun onChanged() {
    
            }
        },
        MONDAY(1) {
            override fun onChanged() {
                
            }
        }
    }
    

    枚举也包含有一些共有函数

    fun main(args: Array<String>) {
        val day = Day.FRIDAY
        //获取值
        val value = day.index  //5
        //通过 String 获取相应的枚举值
        val value1 = Day.valueOf("SUNDAY") //SUNDAY
        //获取包含所有枚举值的数组
        val value2 = Day.values()
        //获取枚举名
        val value3 = Day.SUNDAY.name //SUNDAY
        //获取枚举声明的位置
        val value4 = Day.TUESDAY.ordinal //2
    }
    

    1.5、匿名内部类

    使用对象表达式来创建匿名内部类实例

    interface OnClickListener {
    
        fun onClick()
    
    }
    
    class View {
    
        fun setClickListener(clickListener: OnClickListener) {
    
        }
    
    }
    
    fun main(args: Array<String>) {
        val view = View()
        view.setClickListener(object : OnClickListener {
            override fun onClick() {
    
            }
    
        })
    }
    

    1.6、嵌套类

    在 Kotlin 中在类里面再定义的类默认是嵌套类,此时嵌套类不会包含对外部类的隐式引用

    class Outer {
    
        private val bar = 1
    
        class Nested {
            fun foo1() = 2
            //错误
            //fun foo2() = bar
        }
    }
    
    fun main(args: Array<String>) {
        val demo = Outer.Nested().foo1()
    }
    

    以上代码通过 IDEA 反编译后可以看到其内部的 Java 实现方式

    可以看到 Nested 其实就是一个静态类,因此 foo2() 不能访问外部类的非静态成员,也不用先声明 Outer 变量再指向 Nested 类,而是直接通过 Outer 类指向 Nested 类

    public final class Outer {
       private final int bar = 1;
    
       public static final class Nested {
          public final int foo1() {
             return 2;
          }
       }
    }
    
    public final class MainKotlinKt {
       public static final void main(@NotNull String[] args) {
          Intrinsics.checkParameterIsNotNull(args, "args");
          int demo = (new Outer.Nested()).foo1();
       }
    }
    

    1.7、内部类

    如果需要去访问外部类的成员,需要用 inner 修饰符来标注被嵌套的类,这称为内部类。内部类会隐式持有对外部类的引用

    class Outer {
    
        private val bar = 1
    
        inner class Nested {
            fun foo1() = 2
            fun foo2() = bar
        }
    }
    
    fun main(args: Array<String>) {
        val demo = Outer().Nested().foo2()
    }
    

    再来看其内部的 Java 实现方式

    使用 inner 来声明 Nested 类后,就相当于将之声明为非静态内部类,因此 foo2() 能访问其外部类的非静态成员,在声明 Nested 变量前也需要通过 Outer 变量来指向其内部的 Nested 类

    public final class Outer {
       private final int bar = 1;
    
       public final class Nested {
          public final int foo1() {
             return 2;
          }
    
          public final int foo2() {
             return Outer.this.bar;
          }
       }
    }
    
    public final class MainKotlinKt {
       public static final void main(@NotNull String[] args) {
          Intrinsics.checkParameterIsNotNull(args, "args");
          int demo = (new Outer().new Nested()).foo2();
       }
    }
    
    
    类A在类B中声明 在Java中 在Kotlin中
    嵌套类(不存储外部类的引用) static class A class A
    内部类(存储外部类的引用) class A inner class A

    重拾 Kotlin 系列文章目录: 重拾 Kotlin

    相关文章

      网友评论

        本文标题:重拾Kotlin(6)-抽象类、数据类、密封类、枚举类、匿名内部

        本文链接:https://www.haomeiwen.com/subject/kaxwnqtx.html