kotlin 学习教程(四)|面向对象

1.前言

我们都知道,Java是一种面向对象编程语言,在 Java 中,万物皆对象。其实,kotlin 也是一门面向对象编程的语言。类为某一系列对象的统称,我们通过对象来完成某些事情,那么,在 kotlin 中,我们是如何面向对象编程的呢?今天我们一起来学习。

2 如何声明一个类
2.1 声明普通类
class 类名{
.... // 代码块

}

在 java中,允许创建子类并重写父类的任意方法,除非显示使用了 final 关键字进行标注。

而在 kotlin 中,我们创建一个类,默认是 final的,也就是说不能被继承,而且类中所有的方法也都是 final 的,那么在 kotlin 中我想实现继承父类及其方法怎么办呢?代码如下:

open class HelloLanguage {
  open  fun type():String{
        return "我是一门语言"
    }
     class HelloKotlin: HelloLanguage() {

         override fun type(): String {
             return "我是 kotlin"
         }
    }
}

fun main(args: Array) {
    println(HelloLanguage.HelloKotlin().type())
}

运行结果:


Snipaste_2020-02-17_10-31-55.png

我们需要使用 kotlin 中的关键字open,将需要被继承的父类加上open即可被继承,如果重写父类的方法,也需要加上open。表示可以被继承或者重写。

2.2 声明构造函数
class HelloLanguage(var name:String){
    override fun toString(): String {
        return "$name"
    }
}

fun main(args: Array) {
   println(HelloLanguage("kotlin"))

}

运行结果如下:

Snipaste_2020-02-17_10-31-55.png

在kotlin中,我们可以在声明类的同时指定该类的构造函数,在类的后面使用括号包含构造函数的参数列表。

在以上程序中,我们定义了HelloLanguage类,并指定了构造方法,在运行程序时输出 kotlin。

2.3声明抽象类及接口
  • 什么是抽象类?

    抽象类对应于具体的类,在具体的类中,我们定义一个类及其方法,通过方法来具体的实现或完成某个任务。而在抽象类中,我们可以定义某个抽象类及其方法,方法可以静态也可以不是静态,但如果是静态,则子类必须要重写父类的方法,如果不是静态,在子类继承且想重写时需加上open ,表示可以被重写

abstract class HelloLanguage{
     abstract  fun type()
    class HelloKotlin: HelloLanguage() {
        override fun type() {
            println("kotlin")
        }
    }
}
fun main(args: Array) {
    HelloLanguage.HelloKotlin().type()
}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

以上代码还等同于:

abstract class HelloLanguage{
     fun type(){
        println("kotlin")
    }
    class HelloKotlin:HelloLanguage(){

    }

}
fun main(args: Array) {
    HelloLanguage.HelloKotlin().type()
}
  • 什么是接口

    • 接口是一种比抽象类更加抽象的“类”,它的本身不是类,并不能被实例化。

    • 接口中的方法默认为抽象的,如果实现了该接口,则必须要实现接口的所有抽象方法;如果接口的方法为普通方法,则可不必重写该方法。

    • Kotlin与Java一样,不支持同时继承多个父类,但可以同时实现多个接口

代码如下:

interface HelloLanguage {
    fun name()
    fun type() {
        println("language")
    }

    class HelloKotlin : HelloLanguage {
        override fun name() {
            println("kotlin")
        }

    }
}

fun main(args: Array) {
    HelloLanguage.HelloKotlin().name()    // 默认的name()为抽象方法,则子类继承该抽象类必须要重写name()
    HelloLanguage.HelloKotlin().type()    //  type()为普通方法(已经实现了),抽象类中的普通方法子类可以直接调用。

}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

重点总结一下:

对于普通类,它们方法默认为 final 的,如果子类想要重写则必须要在父类及其方法前加上open 关键字

对于接口和抽象类,如果它们的方法为抽象的,则子类在继承的同时必须要重写父类所有抽象方法,如果不是抽象 的则可以直接调用。

Snipaste_2020-02-17_10-31-55.png
2.4 继承类

由以上示例代码可知,在kotlin中,我们通过冒号:来连接子类和父类,表示该类继承于某父类;语法格式为:

open class 父类{
   ...  //代码块

    class 子类:父类(){
        ...   //代码块
    }
}
2.5 实现一个接口

实现接口也是通过冒号来连接,但是接口名后无括号,这也是和继承类的写法区别。语法格式为:

       interface 父类 {
   ...  //代码块
    class 子类 : 父类 {
        ...   //代码块  
    }
}
3.object对象

在 Java 中,我们为了保证系统中一个类只有一个实例,我们使用了单例模式,那些在kotlin中,我们如何保证系统中一个类只有一个实例呢?

3.1 object声明单例对象

我们可以使用 object来声明object单例对象。代码以一个项目中网络层封装时获取Retrofit实例为例,代码如下:

package com.example.kongdexi.wanandroidclient.network.api
import com.example.kongdexi.wanandroidclient.app.MyApplication
import com.example.kongdexi.wanandroidclient.constant.Constant
import com.example.kongdexi.wanandroidclient.interceptor.CacheInterceptor
import com.example.kongdexi.wanandroidclient.interceptor.DataEncryptInterceptor
import com.example.kongdexi.wanandroidclient.interceptor.HeaderInterceptor
import com.example.kongdexi.wanandroidclient.interceptor.SaveCookieInterceptor
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.util.concurrent.TimeUnit

object  RetrofitClient {
    val service: BaseApiService by lazy { getRetrofit().create(BaseApiService::class.java) }

    val DEFAULT_TIMEOUT: Int = 60
    //日志拦截器
    var httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)

    //设置 请求的缓存的大小跟位置
    val cacheFile = File(MyApplication.context.cacheDir, "cache")
    val cache = Cache(cacheFile, Constant.MAX_CACHE_SIZE)
    fun getOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
                .addInterceptor(DataEncryptInterceptor())
                .addInterceptor(HeaderInterceptor())
                .addInterceptor(SaveCookieInterceptor())
                .addInterceptor(CacheInterceptor())
                .cache(cache)
                .connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
                .retryOnConnectionFailure(true) // 错误重连
                .build()
    }

   fun getRetrofit():Retrofit{
      // 获取retrofit的实例
      return Retrofit.Builder()
              .baseUrl(Config.BASE_URL)  //自己配置
              .client(getOkHttpClient())
              .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
              .addConverterFactory(GsonConverterFactory.create())
              .build()
  }
}

以上代码通过 object声明了RetrofitClient的单例对象,在项目中有且仅有一个RetrofitClient实例。

3.2 伴生对象

在 Java 中,有静态属性和方法,以及静态代码块,而 kotlin 中则没有,但是却通过关键字companion object提供了伴生对象。代码如下:

class HelloLanguage{
    companion object {
        fun type(){
            println("I am a language")
        }
    }
}

fun main(args: Array) {
    HelloLanguage.type()   //Kotlin 取消了关键字static,也就无法直接声明静态成员,所以引入了伴生对象这个概念,可以理解为“影子”
}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

是的,我们通过伴生对象去调用type(),不用每次都创建HelloLanguage对象,直接用类便可以调用,这也是伴生对象的好处。

4.数据类
4.1 创建数据类

在kotlin中,我们使用 data class 创建数据类,我们不用像在java中一样,写完set()写get(),因为编译器会帮我们自动完成,我们只需要提供属性名(成员)就可以,既简洁又简单,示例代码如下:

import java.io.Serializable
data class LoginResponse(
        val data: LoginData,
        val errorCode: Int,
        val errorMsg: String
): Serializable {
    data class LoginData(
            val chapterTops: MutableList,
            val collectIds: List,
            val email: String,
            val icon: String,
            val id: Int,
            val password: String,
            val token: String,
            val type: Int,
            val username: String
    )
}
4,2 数据类的语法限制
  • 主构造函数至少包含一个参数;

  • 参数必须标识为val或者var;

  • 不能为abstract、open、sealed或者inner;

  • 不能继承其他类(但可以实现接口

5. 泛型
  • 泛型,即 "参数化类型",就是说将类型参数化,这样,在创建实例的时候,我们可以通过指定不同的参数来创建我们需要的不同的类,为类型安全提供保证,消除类型强转的烦恼
  • 泛型可以用在类,接口,方法上
5.1 泛型类

示例代码如下:

class HelloKotlin(t:T) {
   var value=t
}

fun main(args: Array) {
  val kotlinString=HelloKotlin("I am kotlin")
    val kotlinInt=HelloKotlin(666666)

    println(kotlinString.value)

    println(kotlinInt.value)
}

运行结果:

Snipaste_2020-02-17_10-31-55.png

如代码所示,在上面程序中,我们创建了 HelloKotlin的泛型类并指定其构造函数在HelloKotlin(t:T),在我们创建实例的时候,通过指定的泛型参数创建了Sring和Int类型的对象,同时在创建的时候分别给其属性value赋不同类型的值。

5.2 泛型方法

kotlin中的泛型函数的声明和 Java 相同,类型参数要放在函数名的前面,代码如下:

class HelloKotlin {

    fun sayKotlin(value: T){
        println(value)
    }
}

fun main(args: Array) {
    val helloKotlin=HelloKotlin()

    helloKotlin.sayKotlin("沉迷 kotlin,无法自拔")
    helloKotlin.sayKotlin(9898666)
}

运行结果如下:

[图片上传失败...(image-1d5cdf-1581908271299)]

上面程序中,我们声明了泛型类 HelloKotlin 以及泛型函数sayKotlin(value: T),在创建对象实例和调用函数时指定不同类型的参数。最终输出 String 类型的"沉迷 kotlin,无法自拔"的字符串以及 Int 整型的 9898666 数字.

5.3 泛型约束

泛型约束是用来约束一个给定参数允许使用的类型。我们知道,我们可以通过泛型创建不同类型的实例对象,但是假如我们只想要创建一定区域内的类型呢?这个时候,泛型约束就来了,它表示允许我们创建和使用的类型。

Kotlin 中使用 : 对泛型的类型上限进行约束,示例代码以MVP架构项目中封装 P层代码为例:

  • 接口 IBaseView
interface IBaseView {

  /**
   * 显示加载
   */
  fun showLoading()

  /**
   * 隐藏加载
   */
  fun hideLoading()

  /**
   * 使用默认的样式显示信息: CustomToast
   */
  fun showDefaultMsg(msg: String)

  /**
   * 显示信息
   */
  fun showMsg(msg: String)

  /**
   * 显示错误信息
   */
  fun showError(errorMsg: String)

}
  • 接口 IPresenter
interface IPresenter {

    fun attachView(mRootView: V)

    fun detachView()

}
  • 泛型接口 BasePresenter
open class BasePresenter : IPresenter {

    var mRootView: T? = null
        private set

    private var compositeDisposable = CompositeDisposable()


    override fun attachView(mRootView: T) {
        this.mRootView = mRootView
    }

    override fun detachView() {
        mRootView = null

        //保证activity结束时取消所有正在执行的订阅
        if (!compositeDisposable.isDisposed) {
            compositeDisposable.clear()
        }
    }

    private val isViewAttached: Boolean
        get() = mRootView != null

    fun checkViewAttached() {
        if (!isViewAttached) throw MvpViewNotAttachedException()
    }

    fun addSubscription(disposable: Disposable) {
        compositeDisposable.add(disposable)
    }

    private class MvpViewNotAttachedException internal constructor() : RuntimeException("Please call IPresenter.attachView(IBaseView) before" + " requesting data to the IPresenter")

}

在上面代码中,泛型类 BasePresenter 的泛型参数T : IBaseView 就是泛型约束, T必须是 IBaseView 的子类。

6.委托
6.1 类委托

委托就是说某个类想要实现一些方法/属性,可以借用其他已经实现了该方法/属性的类的对象来作为自己的实现。

  • 委托类:其方法不用我们去实现,可以借助被委托类的对象来实现

  • 被委托类:该类拥有方法/属性的具体实现

  • 如何连接:创建委托类的时候将被委类的对象作为形参传入并通过 by关键字将那些需要实现的方法或属性委托给了被委托类对象。

  • 是实现继承的一种很好替代方式

代码如下:

//创建接口
interface Language {
    fun type()
}

// 创建被委托类
class HelloKotlin:Language{
    override fun type() {
        println("我是一门计算机编程语言,我叫 kotlin")
    }
}
// 创建委托类,委托类作为形参传入,由形参委托类对象作为委托对象
class Derived(helloKotlin: HelloKotlin):Language by helloKotlin

fun main(args: Array) {
    val helloKotlin=HelloKotlin()
    Derived(helloKotlin).type()
}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

在上面的代码中,派生类 Derived想要实现 Language类的typ方法,通过关键字by 委托了 HelloKotlin类的对象来完成。

6.2 属性委托

属性委托就是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。本质上就是 多个类的类似属性交给委托类统一实现,避免每个类都要单独重复实现一次。

代码如下:

import kotlin.reflect.KProperty
class Language(var name:String){
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        this.name = value
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return this.name
    }
}

class HelloKotlin{
    var name: String by Language("kotlin是一门计算机编程语言")
}

class HelloJava{
    var name: String by Language("java 也一样")
}

fun main(args: Array) {
   println(HelloKotlin().name)
    println(HelloJava().name)
}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

在上面代码中,HelloKotlin 和 HelloJava 类将自己的属性 name 的实现委托给了Language来管理。

6.3 关于委托类的委托属性的getValue、setValue形参说明

(1)getValue(thisRef, property)

  • thisRef:代表委托属性所属对象,因此thisDef类型需要与委托属性所属对象类型一致,或者是其父类

  • property:代表目标属性,该属性必须是KProperty<>类型或其父类类型*;

  • 返回值:返回目标属性相同的类型,或者其子类类型

(2)setValue(thisRef, property, value)

  • thisRef:同上;

  • property:同上;

  • value:为目标属性设置的新的值,因此该值类型必须与目标属性类型一致,或者是其父类

6.4 实战案例(属性委托)

接下来我们通过 kotlin 委托属性和 SharedPreference来封装一个Preference 类,在项目中可以用于数据的保存和获取。代码如下:

import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import com.example.kongdexi.wanandroidclient.app.MyApplication
import java.io.*
import kotlin.reflect.KProperty

/**
 * kotlin委托属性+SharedPreference实例
 */
class Preference(val name: String, private val default: T) {

    companion object {
        private val file_name = "wan_android_file"

        private val prefs: SharedPreferences by lazy {
            MyApplication.context.getSharedPreferences(file_name, Context.MODE_PRIVATE)
        }

        /**
         * 删除全部数据
         */
        fun clearPreference() {
            prefs.edit().clear().apply()
        }

        /**
         * 根据key删除存储数据
         */
        fun clearPreference(key: String) {
            prefs.edit().remove(key).apply()
        }

        /**
         * 查询某个key是否已经存在
         *
         * @param key
         * @return
         */
        fun contains(key: String): Boolean {
            return prefs.contains(key)
        }

        /**
         * 返回所有的键值对
         *
         * @param context
         * @return
         */
        fun getAll(): Map {
            return prefs.all
        }
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return getSharedPreferences(name, default)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        putSharedPreferences(name, value)
    }

    @SuppressLint("CommitPrefEdits")
    private fun putSharedPreferences(name: String, value: T) = with(prefs.edit()) {
        when (value) {
            is Long -> putLong(name, value)
            is String -> putString(name, value)
            is Int -> putInt(name, value)
            is Boolean -> putBoolean(name, value)
            is Float -> putFloat(name, value)
            else -> putString(name, serialize(value))
        }.apply()
    }

    @Suppress("UNCHECKED_CAST")
    private fun getSharedPreferences(name: String, default: T): T = with(prefs) {
        val res: Any = when (default) {
            is Long -> getLong(name, default)
            is String -> getString(name, default)
            is Int -> getInt(name, default)
            is Boolean -> getBoolean(name, default)
            is Float -> getFloat(name, default)
            else -> deSerialization(getString(name, serialize(default)))
        }
        return res as T
    }

    /**
     * 序列化对象

     * @param person
     * *
     * @return
     * *
     * @throws IOException
     */
    @Throws(IOException::class)
    private fun  serialize(obj: A): String {
        val byteArrayOutputStream = ByteArrayOutputStream()
        val objectOutputStream = ObjectOutputStream(
                byteArrayOutputStream)
        objectOutputStream.writeObject(obj)
        var serStr = byteArrayOutputStream.toString("ISO-8859-1")
        serStr = java.net.URLEncoder.encode(serStr, "UTF-8")
        objectOutputStream.close()
        byteArrayOutputStream.close()
        return serStr
    }

    /**
     * 反序列化对象

     * @param str
     * *
     * @return
     * *
     * @throws IOException
     * *
     * @throws ClassNotFoundException
     */
    @Suppress("UNCHECKED_CAST")
    @Throws(IOException::class, ClassNotFoundException::class)
    private fun  deSerialization(str: String): A {
        val redStr = java.net.URLDecoder.decode(str, "UTF-8")
        val byteArrayInputStream = ByteArrayInputStream(
                redStr.toByteArray(charset("ISO-8859-1")))
        val objectInputStream = ObjectInputStream(
                byteArrayInputStream)
        val obj = objectInputStream.readObject() as A
        objectInputStream.close()
        byteArrayInputStream.close()
        return obj
    }

}

在上面代码中,SharedPreference对数据的存取我们交给了代理类 Preference 来完成。

假如我们想要检查用户是否登录这个标志位 isLogin,则可以通过如下代码来获取:

 protected var isLogin: Boolean by Preference(Constant.LOGIN_KEY, false)

需要设置状态时,可对isLogin直接赋值。

isLogin = true
7. 延迟属性 Lazy
7.1 lazy的使用

lazy() 是接受一个lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托。也就是说:

第一次调用get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用get() 只是返回记录的结果。

示例代码如下:

val lazyValue: String by lazy {
    "Hello"
}
fun main(args: Array) {
    println(lazyValue)
}

运行结果会输出字符串 “hello”。

7.2 lazy 和 lateinit 的区别
  • lazy { ... }只能用于val属性,而lateinit只能用于var

  • lazy 应用于单例模式(if-null-then-init-else-return),而且当且仅当变量被第一次调用的时候,委托方法才会执行。

  • `lateinit 可以在任何位置初始化并且可以初始化多次。而lazy在第一次被调用时就被初始化,想要被改变只能重新定义

  • lateinit`不能用于可空属性和Java原语类型(这是因为null用于未初始化的值)

你可能感兴趣的:(kotlin 学习教程(四)|面向对象)