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())
}
运行结果:
我们需要使用 kotlin 中的关键字open,将需要被继承的父类加上open即可被继承,如果重写父类的方法,也需要加上open。表示可以被继承或者重写。
2.2 声明构造函数
class HelloLanguage(var name:String){
override fun toString(): String {
return "$name"
}
}
fun main(args: Array) {
println(HelloLanguage("kotlin"))
}
运行结果如下:
在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()
}
运行结果如下:
以上代码还等同于:
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()为普通方法(已经实现了),抽象类中的普通方法子类可以直接调用。
}
运行结果如下:
重点总结一下:
对于普通类,它们方法默认为 final 的,如果子类想要重写则必须要在父类及其方法前加上open 关键字
对于接口和抽象类,如果它们的方法为抽象的,则子类在继承的同时必须要重写父类所有抽象方法,如果不是抽象 的则可以直接调用。
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,也就无法直接声明静态成员,所以引入了伴生对象这个概念,可以理解为“影子”
}
运行结果如下:
是的,我们通过伴生对象去调用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)
}
运行结果:
如代码所示,在上面程序中,我们创建了 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
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()
}
运行结果如下:
在上面的代码中,派生类 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)
}
运行结果如下:
在上面代码中,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用于未初始化的值)