Kotlin系列 - 函數與類相關細節小結(二)

Kotlin細節文章筆記整理更新進度: Kotlin系列 - 基礎類型結構細節小結(一)java

1. 函數繼承與實現、複寫等

  • 父類須要open才能夠被繼承(kotlin默認爲final)
  • 父類的方式、屬性須要open才能夠被覆寫
  • 接口、接口方法、抽象類默認爲open
  • 覆寫父類(接口)成員須要override關鍵字
  • 注意繼承類時,其實是調用了父類的構造方法
  • 類只能單繼承,接口能夠多實現
abstract class Person(open val age: Int){
    abstract fun work()
}

class MaNong(age: Int): Person(age){
    override val age: Int
        get() = 0
    override fun work() {
        println("我是碼農,我在寫代碼")
    }
}

class Doctor(override val age: Int): Person(age){
    override fun work() {
        println("我是醫生,我在給病人看病")
    }
}

fun main(args: Array<String>) {
    val person: Person = MaNong(23)
    person.work()
    println(person.age)

    val person2 : Person = Doctor(20)
    person2.work()
    println(person2.age)
}
-------------------------------------打印出來
我是碼農,我在寫代碼
0
我是醫生,我在給病人看病
20
複製代碼

2. 接口代理(by

接口方法實現交給代理類實現ios

class Manager(diver:Driver): Driver by driver
複製代碼

3. 接口方法衝突(實現多接口)

  • 接口方法能夠有默認實現
  • 簽名一致(方法名、參數等一致)且返回值相同的衝突
  • 實現類必須覆寫衝突方法
  • super<[接口名]>.方法名(參數列表)
abstract class A{
    open fun x(): Int = 5
}

interface B{
    fun x(): Int = 1
}

interface C{
    fun x(): Int = 0
}

class D(var y: Int = 0): A(), B, C{

    override fun x(): Int {
        println("call x(): Int in D")
        if(y > 0){
            return y
        }else if(y < -200){
            return super<C>.x()
        }else if(y < -100){
            return super<B>.x()
        }else{
            return super<A>.x()
        }
    }
}
複製代碼

4.類及其成員的可見性private protect interanl public

protected:子類可見 internal:模塊內可見數組

5. Object關鍵字

  • 只有一個實例的類
  • 不能自定義構造函數
  • 能夠實現接口、繼承父類
  • 本質上就是單例模式最基本的實現
object TestKotlin
------------------------------轉化爲java代碼
public final class TestKotlin {
   public static final TestKotlin INSTANCE;

   private TestKotlin() {
   }

   static {
      TestKotlin var0 = new TestKotlin();
      INSTANCE = var0;
   }
}
複製代碼

6. companion object伴生對象、靜態變量成員還有 kotlin包級成員

  • kotlin容許不在類下面寫成員變量跟方法,稱爲包級別對象/函數
  • 每一個類能夠對應有一個伴生對象companion object
  • 伴生對象companion objectjavastatic靜態方法/靜態成員的效果相似相同。
  • kotlin中的靜態成員考慮用包級函數、變量代替
  • JvmFieldJvmStatic@file:JvmName(本身自定義的類名)
package com.demo.dan.voice

val Str = "包級成員"
fun packFun(): String {
    return "包級函數"
}

class TestKotlin {
    companion object {
        fun compainFun(): String {
            return "伴生對象方法"
        }
        var Str = "伴生對象成員"
    }
}
複製代碼

Koltinjava中調用bash

//--------------在kotlin中調用
fun main() {
    println(Str)//包級成員
    println(packFun())//包級函數
    TestKotlin.Str
    TestKotlin.compainFun()
}

//----------------在java中調用
public class TestJava {
    public static void main(String[] args) {
        TestKotlin.Companion.compainFun();
        TestKotlin.Companion.getStr();
    }
複製代碼

能夠看到上面java調用kotlin的代碼並不像咱們java調用靜態對象同樣(中間多了層Companion)。app

上面的java調用Kotlin能夠在伴生對象中增長對應的註解: 函數上加 @JvmStatic, 變量上加@JvmFieldjvm

實現相似調用java靜態成員/方法maven

class TestKotlin {
    companion object {
      @JvmStatic
        fun compainFun(): String {
            return "伴生對象方法"
        }
        @JvmField
        var Str = "伴生對象成員"
    }
}

//----------------在java中調用
public class TestJava {
    public static void main(String[] args) {
        TestKotlin.compainFun();
        String str = TestKotlin.Str;
    }
}
複製代碼

具體的你們能夠轉一下kotlinjava看一下加了註解跟沒有加註解的區別,這裏由於篇幅緣由我就不加代碼了。ide

這裏補充一點: java怎麼調用kotlin的包級別對象呢???函數

答案:可使用@file:JvmName(本身自定義的類名)工具

@file:JvmName("TestKotlin1")
package com.demo.dan.imoc_voice

val Str = "包級成員"
fun packFun(): String {
    return "包級函數"
}
複製代碼

java調用:

public class TestJava {
    public static void main(String[] args) {
        TestKotlin1.getStr();
        TestKotlin1.packFun();
    }
}
複製代碼

7. 方法重載跟默認參數

  • Overloads 方法重載
  • 名稱相同、參數不一樣的方法
  • Jvm函數簽名的概念:函數名、參數列表 (不包含返回值!),也就是當兩個方法的方法名跟參數一致的時候,這個函數Jvm視爲一個函數。
  • kotlin中能夠爲函數參數設置默認值,來實現重載。
  • 函數調用產生混淆得時候就使用具名函數
//下面兩個函數爲同一函數簽名,函數同樣。
fun a():Int{}
fun a():String{}

//方法重載
fun a(s:Stirng){}
fun a(s:String,i:int){}

------------------例子-------------------------------------------------------------
//kotlin提供了默認函數,
//因此上面的方法重載其實能夠改爲使用默認參數來實現
fun a(s:String = "ss",i:Int){}

fun main() {
    a(str ="str")
}
複製代碼
  • java怎麼調用kotlin的默認參數函數?? 答案:使用 @JvmOverloads註解
//kolin代碼----------
@file:JvmName("TestKotlin1")
package com.demo.dan.imoc_voice

@JvmOverloads
fun a(int:Int=1,str: String){}

//java調用------------
public class TestJava {
    public static void main(String[] args) {
        TestKotlin1.a("dfsdf");
    }
}
複製代碼
  • java方法重載形成得bug ,例如List
List.remove(int)
List.remove(Object)
複製代碼

當List裏面傳入得是int類型得數據,那麼你remove()傳入一個 4那麼它是刪除第四位數據仍是刪除4這個對象呢?這裏它是默認調用了remove(int),而remove(Object)卻不會被調用到。

8. 擴展成員

爲現有類添加方法、屬性

  • 擴展方法格式 fun X.y():Z{...}fun 被擴展類.自定義擴展方法名():返回類型{方法體 }
  • 擴展屬性格式 var X.mvar 被擴展類.自定義擴展屬性名 ) 注意擴展屬性不能初始化,相似接口屬性(X爲被擴展類)
  • java調用擴展成員相似於調用靜態方法
@file:JvmName("TestKotlin1")
package com.demo.dan.voice

// 擴展String 增長multiply方法
fun String.multiply(int: Int): String {
    val stringBuilder = StringBuilder()
    for (i in 0 until int) {
        stringBuilder.append(this)
    }
    return stringBuilder.toString()
}
//擴展String 增長運算符 - 
operator fun String.minus(int: Int): String {
    return this.substring(0, length-int+1)
}
//擴展成員變量
val String.a: String
    get() = "擴展成員變量"
//擴展成員變量
var String.b: Int
    set(value){}
    get() = 6
複製代碼

kotlin調用

fun main(args: Array<String>) {
    println("我是碼農".multiply(3))
    println("我是碼農" - 2)
    println("".a)
    println("".b)
}
------------打印出來的Log--------------------------
我是碼農我是碼農我是碼農
我是碼
擴展成員變量
6
複製代碼

java調用

public static void main(String[] args) {
     String str =  TestKotlin1.minus("我是Java調用",2);
     String strpr = TestKotlin1.getA("");
     System.out.println(str);
     System.out.println(strpr);
    }
------------打印出來的Log--------------------------
我是Java調
擴展成員變量
複製代碼

9. 屬性代理

  • val delega1 by lazy { }
  • by解析
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
複製代碼

能夠看到by其實是一個操做符,這裏是Lazy擴展getValue方法。

  • lazy原理解析
@file:kotlin.jvm.JvmName("LazyKt")
//關鍵代碼1
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
  //關鍵代碼2
    private var initializer: (() -> T)? = initializer
//關鍵代碼3
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            //關鍵代碼4
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
          //關鍵代碼5
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}
複製代碼

剖析一下 lazy

關鍵代碼1:將lazy(initializer: () -> T)後面的代碼塊傳入到SynchronizedLazyImpl(initializer)

關鍵代碼2: 進入SynchronizedLazyImpl將傳入的代碼塊賦值於initializer

關鍵代碼3:_value 最開始爲未初始化。

關鍵代碼4:若是_value不爲空則直接返回(不執行咱們傳入的lazy後面代碼塊內的方法)

關鍵代碼5:若是_value爲空,作了一下同步的判斷若是不爲空則直接返回(不執行咱們傳入的lazy的方法),不然執行咱們的傳入的代碼塊initializer並拿返回值初始化_value

每次咱們調用的時候被lazy代理的對象的時候,其實是調用了裏面的valueget(),當第一次調用時就會跑咱們val delega1 by lazy { 代碼塊}代碼塊中的代碼,實例完裏面的SynchronizedLazyImpl#_value後,第二次開始就是直接返回對象。保持只有一個對象實例存在。

  • 上面的是val屬性的代理,lazy能代理var的屬性嗎?

    答案:不行。裏面並無實現setValue的方法。

  • 自定義實現代理 下面咱們舉一下例子 本身來實現一個代理吧。

class Delegates{
    val hello by lazy {
        "HelloWorld"
    }
    val hello2 by X()
  //由於X代理了
    var hello3 by X()
}

//實現了X這個代理,並實現了 setValue getValue
class X{
    private var value: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("getValue: $thisRef -> ${property.name}")
        return value?: ""
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String){
        println("setValue, $thisRef -> ${property.name} = $value")
        this.value = value
    }
}
fun main(args: Array<String>) {
    val delegates = Delegates()
    println(delegates.hello)
    println(delegates.hello2)
    println(delegates.hello3)
    delegates.hello3 = "value of hello3"
    println(delegates.hello3)
}
-------------打印出來Log
HelloWorld
getValue: com.demo.dan.imoc_voice.Delegates@7530d0a -> hello2

getValue: com.demo.dan.imoc_voice.Delegates@7530d0a -> hello3

setValue, com.demo.dan.imoc_voice.Delegates@7530d0a -> hello3 = value of hello3
getValue: com.demo.dan.imoc_voice.Delegates@7530d0a -> hello3
value of hello3
複製代碼

image.png

上面咱們實現一個代理類,並實現了setValuegetValue兩個方法。這樣就能夠代理var類型的成員變量。 能夠看到當咱們調用的時候其實是調用了代理類的setValuegetValue兩個方法。

10. 數據類data&&特殊寫法&&解決Javabean的問題

  • 默認實現了copy、toString,還有componentN等方法
  • 直接替代javaBean存在問題(可能出現沒法初始化,構造方法簽名不對等問題),由於javaBean類不是final類型可被繼承,且有一個無參數構造函數。

定義一個data數據類

data class TestKotlin(val name:String,val age:Int)
複製代碼

看一下轉換成java的實現

public final class TestKotlin {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public TestKotlin(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }

   @NotNull
   public final TestKotlin copy(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      return new TestKotlin(name, age);
   }

   // $FF: synthetic method
   public static TestKotlin copy$default(TestKotlin var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.age;
      }

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "TestKotlin(name=" + this.name + ", age=" + this.age + ")";
   }

   public int hashCode() {
      String var10000 = this.name;
      return (var10000 != null ? var10000.hashCode() : 0) * 31 + this.age;
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof TestKotlin) {
            TestKotlin var2 = (TestKotlin)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}
複製代碼
  1. 能夠看到TestKotlin.javafinal類型的,而且構造函數沒有無參的。
  2. 裏面實現的component1,component2實際上就是成員變量。
  3. 默認實現了toStringhashCodeequals等方法。

咱們來調用一下 咱們寫的TestKotlin數據類

fun main() {
    val t = TestKotlin("小丹", 24)
    println(t.component1())
    println(t.component2())
    val (name,age)=t
    println(name)
    println(age)
}
-----------打印出來的Log
小丹
24
小丹
24
複製代碼
  • val (name,age)=t 這種特殊寫法就是data類型的對象擁有的。

咱們在迭代Array數組的時候也有這種寫法:

fun main(args:Array<String>) {
    for ((index,value)in args.withIndex()){
    }
}
-------------------看一下 withIndex 的實現
public fun <T> Array<out T>.withIndex(): Iterable<IndexedValue<T>> {
    return IndexingIterable { iterator() }
}
---------------------看一下 返回的IndexedValue類型
public data class IndexedValue<out T>(public val index: Int, public val value: T)
複製代碼

最終能夠看到IndexedValue也是同樣的data類型。

  • 處理替代javaBean存在問題可使用,allOpennoArg插件,前者幫咱們去掉類前的final後者生成無參構造函數

使用方法:

  1. 在項目(Project)下的build.gradle
buildscript {//構件工具
    ext.kotlin_version = '1.3.50'
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
    dependencies {
        ......
        classpath "org.jetbrains.kotlin:kotlin-noarg:${kotlin_version}"
        classpath "org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}"
    }
}
複製代碼

2.在對應的模塊(module)下的build.gradle

apply plugin:'kotlin-noarg'
apply plugin:'kotlin-allopen'
noArg{
    //com.demo.dan.annotations.PoKo 
    //PoKo這個類是你本身建立的,要本身新建個annotations目錄  
    // 並在下面建立一個類,而後將路徑寫進來
    annotation("com.demo.dan.annotations.PoKo")
}
allOpen{
// 跟上面同樣
    annotation("com.demo.dan.annotations.PoKo")
}
複製代碼
  1. 新建對應的包名跟文件...\annotatios\PoKo.kt
annotation class PoKo
複製代碼
  1. data類的類名上面添加註解
@PoKo
data class TestKotlin(val name: String, val age: Int)
複製代碼
  1. 通過上面的註解操做,再rebuild一下 再轉換成java代碼
public class TestKotlin {
 ................省略其餘的代碼
   public TestKotlin() {
   }
}
複製代碼

能夠看到這裏已經幫咱們生成了無參構造函數跟去掉final修飾關鍵字。

注意:由於註解是在編譯期的時候生效的,也就是說咱們在寫代碼的時候仍是調用不了無參構造函數,可是能夠經過反射獲取到無參構造函數。

11. 內部類&&匿名內部類

  • kotlin默認是靜態內部類,非靜態內部類須要使用inner關鍵字
//靜態內部類
class TestKotlin {
    class Inner{
    }
}
//非靜態內部類
class TestKotlin1{
   inner class Inner1{ }
}

fun main() {
    val inner = TestKotlin.Inner()
    val inner1 = TestKotlin1().Inner1()
}
複製代碼

當內部類須要外部類的狀態時,則可使用非靜態內部類,由於非靜態內部類默認持有外部類的引用。若是不須要持有外部類的引用,則使用靜態內部類。

  • @Outter的使用,當非靜態內部類跟外部類有成員名字相同時,獲取外部類的成員能夠用this@外部類名.成員變量
class TestKotlin1 {
    val a = "TestKotlin1-string"
    inner class Inner1 {
        val a = "Inner1-string"
        fun aInnerfun(): String {
            return a
        }
        fun aTestKotlinfun(): String {
            return this@TestKotlin1.a
        }
    }
}
複製代碼
  • 匿名內部類(object)的使用
------------------kotlin的寫法
    val view =View(context)
    view.onClickListener = object : View.OnClickListener{
        override fun onClick(v: View?) {
        }
    }
----------------java的寫法
View view = null;
view.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  }
});
複製代碼

不一樣於java ,Ktolinobject:還支持同時實現單繼承多實現

interface TestInterface{
    fun test()
}
view.onClickListener = object: TestInterface,View.OnClickListener {
        override fun test() {
            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        }
        override fun onClick(v: View?) {
        }
}

複製代碼

12. 枚舉類

  • 實例有限(可數)的類,一個枚舉成員對應一個本類的實例,由於成員是你寫上去固定的數目,因此對應的實例也是固定的。
enum class LogLevel{
VERBOSE,DEBUG,INFO,WARN,ERROR
}
---相似於下面的kotlin代碼
class LogLevel2 protected constructor{
  companion object{
    val VERBOSE = LogLevel2()
    val DEBUG= LogLevel2()
    val INFO= LogLevel2()
    val WARN= LogLevel2()
    val ERROR= LogLevel2()
}
}
複製代碼
  • 若是在kotlinenum中要定義方法,記得要用;將成員跟方法隔開
enum class LogLevel(val id:Int){
VERBOSE(0),DEBUG(1),INFO(2),WARN(3),ERROR(4) ;

fun getTag():String{
    return "$id,$name"
}
}
複製代碼

13. 密封類sealed class

子類有限(可數)的類:子類只能在同一個文件中存在,因此其餘的文件繼承不了它,因此子類有限(可數)。

  • kotlin版本<v1.1,子類必須定義爲密封類的內部類
  • koltin版本>=v1.1,子類只須要於密封類在同一個文件中
  • 用於保護類不被外部繼承使用,其也是final。
sealed class PlayerCmd {
    class Play(val url: String, val position: Long = 0): PlayerCmd()
    class Seek(val position: Long): PlayerCmd()
    object Pause: PlayerCmd()
    object Resume: PlayerCmd()
    object Stop: PlayerCmd()
}
複製代碼
相關文章
相關標籤/搜索