Android开发者快速上手Kotlin(二) 之 面向对象编程

《Android开发者快速上手Kotlin(一) 之 简介和基本语法》文章继续。

5 面向对象编程

在Kotlin中,所有的类都继承于Any,这就像Java中的Object一样。类的实例化不需要使用new关键字。

5.1 包

Kotlin中包跟Java中包是一样,但要注意一点的是,包名可以不跟文件夹名。

5.2 类

Kotlin的类要注意几点:使用关键字constructor来声明构造方法、成员变量默认必须在声明时就初始化或者在init块进行初始化,且可见性默认是publish。

class A1 {
    var mA: Int = 0          // 成员变量必须初始化
    var mB: Int = 0
    constructor(a: Int) {                   // 构造方法1(副构造方法)
        mA = a
    }
    constructor(a: Int, b:Int):this(a) {    // 构造方法2(副构造方法)
        mB = b
    }
    fun f() {
        println(mA)
    }
}
class A2(a: Int, b:Int) {          // 如果只有一个构造函数时可这样写,这叫做主构造方法,推荐类要有一个主构造方法
    var mA: Int = a
    var mB: Int = b
    fun f() {
        println(mA)
        println(mB)
    }
}
class A3(val a: Int, b:Int) {      // 构造函数的参数也是类全局变量
    var mB: Int
    init {                          // init可做一些初始化工作
        mB = b
    }
    fun f() {
        println(a)
    }
}

5.2.1 属性

其实Java中并没有属性的概念,它并不是语法的规则,只是通过约定的形式来实现属性的逻辑。而在Kotlin中就存在一个真正意义上的属性,它的get和set是完整的一体的,它通过直接使用类的成员变量来实现,因为类的成员变量,默认就可以就是属性,所以说类的成员的可见性默认是publish,属性可使用var或val来修饰,可以不用另外去getter和setter,但也可以重载其属性,如:

class A {
    var a = 0
    get() {
        println("get a")
        return field
    }
    set(value) {
        println("set b")
        field = value
    }
}

5.2.2 延迟初始化属性

如果实在需要延迟初始化成员变量的话,当然也是有办法的。变量初始化,使用关键字 lateinit表示延时初始化,如果是常量的话,要使用 by lazy表达式来处理,表示使用时才调用到:

class A {
    var a = 0                   // 变量立即初始化
    var b:String? = null
    lateinit var c:String       // 变量延迟初始化
    val d:Int by lazy {         // 只读变量的延迟初始化,只要第一次访问时就会执行大括号里的初始化代码
        println("init d")
        100
    }
//    var e:Int by lazy {         // 换成var变量的话,这句是报错的
//        println("init e")
//        100
//    }
}

5.2.3 属性代理

上面介绍延心初始化只读变量时,使用的就是属性代码lazy,它只能用于只读变量的情况。如果是变量的话,对应也有一个叫observable的属性代理。当然你也可以通过自定义类的方式来实现,代理类需要实现相应的setValue和getValue方法。如:

class A {
    val a by lazy { 100 }          // 语法已提供,也就是上述的延初始化只读变量
    val b by Delegates.observable(0) {
            property, oldValue, newValue -> println("$property,$oldValue,$newValue") 
    }
    val c by B()                   // 自定义代理
    var d by B()                   // 有了setValue方法后,var声明的变量也可用于代码
}

class B {
    var value: String? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return value ?: "abc"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        this.value = value
    }
}

5.2.4 类及其成员的可见性

在Java中可见性分为:publish、protected、default(包内可见)以及private。

而在Kotlin中,可见性分为:publish(默认)、internal(模块内可见)、protected、以及private。

5.3 接口

Kotlin接口定义跟Java中差不多。如:

interface IA {
    fun a()
}

5.3.1 使用匿名方法代码接口作回调

在Kotlin里,如果遇到使用线程进行耗时操作后进行回调,可以不再定义接口,而是使用方法类型的参数,即匿名方法。假如有这样的方法,需要进行操作后回调:

fun connect(context: Context, connectListener: (result: Boolean) -> Unit) {
}
fun connect2(connectListener: (result: Boolean) -> Unit) {
}

调用如:

// 正式的写法
connect(applicationContext, fun (result: Boolean) {
})
// Lambda表达式写法
connect(applicationContext) { result ->
}
// 如果参数方法仅有一个参数,可省略,调用时使用it代替
connect(applicationContext) {
    val result:Boolean = it
}

// 如果方法仅有一个回调的参数方法,小括号也可省略
connect2() {
}
connect2 {
}

5.4 继承

Kotlin中的继承关系跟Java中一样,也是单继承多实现原则,它的语法跟C++很象。如:

open class A {                      // 可被继承的类,要使用open声明
    fun a() {
        println("this is A.a")
    }
}
interface B {                       // 接口的声明
    fun b1()
    fun b2() {                      // 接口方法可以默认实现
        println("this is B.b2")
    }
}
abstract class C {                  // 抽象类的声明
    abstract fun c1()               // 抽象方法,必须要子类实现
    open fun c2() {                 // open方法,可由子类重写或不重写
        println("this is C.c2")
    }
    fun c3() {                      // 子类不能实现
        println("this is C.c3")
    }
}

class Test1 : A(), B {              // 子类继承使用豆号+父类的构造方法
    override fun b1() {
    }
//    override fun b2() {           // b2方法选择性实现
//    }
}

class Test2 : C() {
    override fun c1() {             // 实现父类或接口的方法,要使用overried
    }
    override fun c2() {
    }
//    override fun c3() {          // 这句会报错,因为c3不能被重写
//    }
}

说明:

  1. 继承父类时,是会同时调用了父类的构造方法
  2. 抽象类依然是使用abstract关键字来进行声明。
  3. 类和方法默认是不能被继承的,想要对类或方式进行继承,需要加上open关键字。
  4. 子类重写父类的方法时,需要在方法前加上overried关键字,Java中只是通过注解来约定,而Kotlin是必须要加关键字。
  5. 接口的方法也可以在接口声明时就默认实现。

5.4.1 判断继承关系

Test1 is A
Test1 is B

5.4.2 继承中的构造函数参数

open class A1(a:Int) {                     // 构造函数参数不加var,表示a只是构造函数中的一个参数
}
class B1(a:Int) : A1(a) {
}

open class A2(var a:Int) {                 // 构造函数参数加var,表示类中的方法可以访问这个属性
}
class B2(a:Int) : A2(a) {
}

open class A3(open var a:Int) {            // 构造函数参数加open var,表示子类可对该属性重写
}
class B3(override var a:Int) : A3(a) {
}

5.4.3 接口代理

使用by关键字进行接口代理。如:

interface A {
    fun a()
}
interface B {
    fun b()
}
class Test1 : A, B {
    override fun a() {
    }
    override fun b() {
    }
}
class Test2(a: A, b: B) : A by a, B by b {     // 使用by关键字代理实现接口
}

5.4.4 使用super指定父类

方法名冲突,使用super<A>来指定父类

interface A {
    fun a(){
        println("A.a")
    }
}
interface B {
    fun a(){
        println("B.a")
    }
}
class Test1 : A, B {
    override fun a() {                      // 接口方法冲突,可以使有super<父类>来指定使用哪个父类的方法
        super<A>.a()
    }
}

5.5 object实现单例

在Kotlin中单例模式最基本的实现就是使用object。如:

object A {
    fun a() {
        println("A.a")
    }
}

// 调用
 A.a()

其反编后的Java代码是这样,这是一个饿汉式的单例:

public final class A {
   public static final A INSTANCE;

   public final void a() {
      String var1 = "A.a";
      boolean var2 = false;
      System.out.println(var1);
   }

   private A() {
   }

   static {
      A var0 = new A();
      INSTANCE = var0;
   }
}

5.6 data数据类

数据类,使用data关键字声明类。如:

data class A(val id:Int, val name:String)
// 调用
var a = A(1, "子云心")
val id1 = a.id
val id2 = a.component1()
val name1 = a.name
val name2 = a.component2()
val(id, name) = a

注意: 

  1. data的数据类不能被继承。
  2. data的数据类它在生成的Java代码中可见存在着N个component方法,其中N是字段数。
  3. 除N个component方法外,编译器还帮我们生成了equals、hashCode、toString、copy等方法。
  4. data的数据类是final声明的,而且没有无参数重载版本的构造函数。
  5. 使用插件:NoArg可以生成无参函造方法;AllOpen可以去掉final的声明,它们都是在编绎期处理的。所以要想使用它们处理后的代码就要使用反射 。
  6. 关于如何引用插件,读者们可以自行搜索,因为它并不是语法范畴,所以这里并不打算在这作过多介绍。

5.6.1 componentN方法的使用

class A() {
    operator fun component1():Int {
        return 1
    }
    operator fun component2():String {
        return "子云心"
    }
}
// 调用
var a = A()
val(id, name) = a

5.7 伴生对象与静态成员

Kotlin使用静态方法时,建议使用包级函数,就是直接在包下写函数,不需要在类里写方法。但是如果一定需要像Java一样在类里的静态方法,可以使用伴生对象。如:

class A private constructor() {
    companion object {
        fun a(p:Int) {
            println(p)
        }

        var TAG = "A"
    }
}
class B private constructor() {
    companion object {
        @JvmStatic
        fun b(p:Int) {
            println(p)
        }
        @JvmField
        var TAG = "B"
    }
}

fun main(args:Array<String>) {
    A.a(2)
    println(A.TAG)
    B.b(3)
    println(B.TAG)
}

注意:

以上代码虽然使用起来跟Java的静态方法很像,但是实际上反编出对应的Java是这样的:

public final class A {
   @NotNull
   private static String TAG = "A";
   public static final A.Companion Companion = new A.Companion((DefaultConstructorMarker)null);

   private A() {
   }
   public static final class Companion {
      public final void a(int p) {
         boolean var2 = false;
         System.out.println(p);
      }

      @NotNull
      public final String getTAG() {
         return A.TAG;
      }

      public final void setTAG(@NotNull String var1) {
         Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
         A.TAG = var1;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
public final class B {
   @JvmField
   @NotNull
   public static String TAG = "B";
   public static final B.Companion Companion = new B.Companion((DefaultConstructorMarker)null);

   private B() {
   }

   @JvmStatic
   public static final void b(int p) {
      Companion.b(p);
   }

   public static final class Companion {
      @JvmStatic
      public final void b(int p) {
         boolean var2 = false;
         System.out.println(p);
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

在Java的调用是这样,A和B的区别在于B的b方法上加了@JvmStatic,这样如果你的代码也需要运行在Java中便会比较方便地调用就可以这样做

A.Companion.a(2);
A.Companion.getTAG();
B.b(3);
String tag = B.TAG;

5.8 内部类

Kotlin中的内部类就是等于Java中的静态内部类。我们在Android开发中往往在使用非静态内部类中产生内存泄露情况,所以一般情况下都是使用静态内部类来实现相关的逻辑。而Kotlin考虑到此情况,所以默认就是静态内部类了。如果你一定需要使用非静态内部的话,需要使用inner关键字来声明内部类。如:

fun main(args: Array<String>) {
    val inA = OutA.InA()
    val inB = OutB().InB()
}

class OutA {
    class InA {         // 静态内部类
    }
}

class OutB {
    val b: Int = 0
    inner class InB {   // 非静态内部类
        val b: Int = 1
        fun fun1() {
            println(b)              // 这里引用的是内部类的b
            println(this@OutB.b)    // 这里引用的是外部类的b
        }
    }
}

注意:

在内部类中如果需要使用外部类的方法以,可以使用this@Outter。同理也存在this@Inner的用法。

5.8.1 匿名内部类

在Kotlin中使用匿名内部类有一处跟Java中不一样,但是匿名内部类也可以同时实现继承。如:

open class Test
interface OnClickListener {
    fun onClick()
}
class View {
    var onClickListener: OnClickListener? = null
}

// 调用
val view = View()
view.onClickListener = object : OnClickListener {         // 正常创建
    override fun onClick() {
    }
}
view.onClickListener = object : Test(), OnClickListener { // 创建时同时继承于Test
    override fun onClick() {
    }
}

5.8.2本地类

Java中也有本地类的语法,不过我们用得比较少。本地类就是在方法中定义类。如:

fun main(args: Array<String>) {
    class LocalClass{
    }
}

5.8.3本地函数

Java中没有本地函数的概念,它对应本地类的概念,本地函数就是函数中定义函数。如:

fun main(args: Array<String>) {
    fun LocalFunc{
    }
}

5.9 enum枚举类

Android官方中已明确强烈不推荐使用枚举,使用注解来代替枚举。因为枚举占用的内存空间要比整型大。通过反编你就知明白,枚举最终会转化成一个继承于Enum的类,并且在类中存在着N个静态该类的字段以及一个该类的数组。所以在手机开发中,它跟简单的几个Int类型对比起来这份性能开销是完全没有优势的。

虽然我们Android开发中并不推荐使用枚举,但是Kotlin中枚举的语法我们还是需要懂的。使用如:

enum class LogLevel(val abbreviation:String) {
    VERBOSE("V"),
    DEBUG("D"),
    INFO("I"),
    WARN("W"),
    ERROR("E"),
    ASSERT("A");      // 注意这个分号,这可能是Kotlin目前唯一需要用到的分号
    fun getTag(): String {
	// ordinal是下标、abbreviation是构造方法传入的值、name是名称
        return "$ordinal, $abbreviation , $name"    
    }
}
// 调用
println(LogLevel.ERROR.getTag())
println(LogLevel.valueOf("DEBUG"))          // 获得DEBUG的对象
LogLevel.values().map(::println)            // 获得所有对象

注意:

  1. 语法中出现了分号“;”,这可能是Kotlin目前唯一需要用到分号的地方。
  2. 枚举其实也是一个类。
  3. 枚举的也有构造方法,对应每一项也需要传入相应的值。

5.10 sealed密封类

密封类是Java中没有的概念,密封类就是一种特殊的抽象类,其实它就是子类可数的类。在Kotlin1.1之前,密封类的子类必须定义为其内部类,而在1.1之后子类只需要与密封类在同一个文件中即可。使用如:

sealed class A{
    class A1(val a:Int):A()
    class A2:A()
    object A3:A()
}

5.11 inline内联类

内联类是Kotlin在1.3中才出现。它是对另外一个类型的包装,它类似于Java装箱类型的一种类型,编译器会尽可能使用被包装的类型进行优化。使用如:

inline class A(val value: Int) {
    operator fun inc(): A {                     // ++运算符重载
        return A(value + 1)
    }
}
// 调用
var a = A(5)
a++
println(a)

注意:  

  1. 内联类主构造器必须有且仅有一个只读属性。
  2. 内联类只能定义方法。
  3. 内联类可以实现接口,但不能继承父类也不能被继承。
  4. 内联类被包装的类型必须不能是泛型类型。
  5. 内联类不能定义为其他类的内部类。

使用场景:

  1. 官方所有无符号的类型都是使用内联类来实现的。
  2. 因为枚举的性能问题,一般可以使用内联类+伴生对象来代替。如:
inline class LogLevel(val ordinal: Int) {
    companion object {
        val VERBOSE = LogLevel(0)
        val DEBUG = LogLevel(1)
        val INFO = LogLevel(2)
        val WARN = LogLevel(3)
        val ERROR = LogLevel(4)
        val ASSERT = LogLevel(5)
    }
}

5.12 typealias类型别名

类型别名为现有类型提供了替代名称。如果类型名称太长,可以引入一个不同的较短名称并使用新的名称。

使用示例1

fun getList1(list: ArrayList<(String, Int?) -> String>) {
    // TODO
}

typealias MyList = ArrayList<(String, Int?) -> String>

fun getList2(list: MyList) {
    // TODO
}

使用示例2

typealias AbcMyData = com.zyx.abc.MyData
typealias DefMyData = com.zyx.def.MyData

fun getAbcMyData():AbcMyData {
    // TODO
}
fun getDefMyData():DefMyData {
    // TODO
}

未完,请关注后面文章更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值