Kotlin細(xì)節(jié)文章筆記整理更新進(jìn)度:
Kotlin系列 - 基礎(chǔ)類型結(jié)構(gòu)細(xì)節(jié)小結(jié)(一)
1. 函數(shù)繼承與實現(xiàn)、復(fù)寫等
- 父類需要
open才可以被繼承(kotlin默認(rèn)為final) - 父類的方式、屬性需要
open才可以被覆寫 - 接口、接口方法、抽象類默認(rèn)為
open - 覆寫父類(接口)成員需要
override關(guān)鍵字 - 注意繼承類時,實際上是調(diào)用了父類的構(gòu)造方法
- 類只能單繼承,接口可以多實現(xiàn)
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("我是碼農(nóng),我在寫代碼")
}
}
class Doctor(override val age: Int): Person(age){
override fun work() {
println("我是醫(yī)生,我在給病人看病")
}
}
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)
}
-------------------------------------打印出來
我是碼農(nóng),我在寫代碼
0
我是醫(yī)生,我在給病人看病
20
2. 接口代理(by)
接口方法實現(xiàn)交給代理類實現(xiàn)
class Manager(diver:Driver): Driver by driver
3. 接口方法沖突(實現(xiàn)多接口)
- 接口方法可以有默認(rèn)實現(xiàn)
- 簽名一致(方法名、參數(shù)等一致)且返回值相同的沖突
- 實現(xiàn)類必須覆寫沖突方法
- super<[接口名]>.方法名(參數(shù)列表)
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:模塊內(nèi)可見
5. Object關(guān)鍵字
- 只有一個實例的類
- 不能自定義構(gòu)造函數(shù)
- 可以實現(xiàn)接口、繼承父類
- 本質(zhì)上就是單例模式最基本的實現(xiàn)
object TestKotlin
------------------------------轉(zhuǎn)化為java代碼
public final class TestKotlin {
public static final TestKotlin INSTANCE;
private TestKotlin() {
}
static {
TestKotlin var0 = new TestKotlin();
INSTANCE = var0;
}
}
6. companion object伴生對象、靜態(tài)變量成員還有 kotlin包級成員
-
kotlin允許不在類下面寫成員變量跟方法,稱為包級別對象/函數(shù) - 每個類可以對應(yīng)有一個伴生對象
companion object - 伴生對象
companion object與java的static靜態(tài)方法/靜態(tài)成員的效果類似相同。 -
kotlin中的靜態(tài)成員考慮用包級函數(shù)、變量代替 -
JvmField、JvmStatic、@file:JvmName(自己自定義的類名)
package com.demo.dan.voice
val Str = "包級成員"
fun packFun(): String {
return "包級函數(shù)"
}
class TestKotlin {
companion object {
fun compainFun(): String {
return "伴生對象方法"
}
var Str = "伴生對象成員"
}
}
在Koltin與java中調(diào)用
//--------------在kotlin中調(diào)用
fun main() {
println(Str)//包級成員
println(packFun())//包級函數(shù)
TestKotlin.Str
TestKotlin.compainFun()
}
//----------------在java中調(diào)用
public class TestJava {
public static void main(String[] args) {
TestKotlin.Companion.compainFun();
TestKotlin.Companion.getStr();
}
可以看到上面
java調(diào)用kotlin的代碼并不像我們java調(diào)用靜態(tài)對象一樣(中間多了層Companion)。
上面的java調(diào)用Kotlin可以在伴生對象中增加對應(yīng)的注解:
函數(shù)上加@JvmStatic,
變量上加@JvmField
實現(xiàn)類似調(diào)用java靜態(tài)成員/方法
class TestKotlin {
companion object {
@JvmStatic
fun compainFun(): String {
return "伴生對象方法"
}
@JvmField
var Str = "伴生對象成員"
}
}
//----------------在java中調(diào)用
public class TestJava {
public static void main(String[] args) {
TestKotlin.compainFun();
String str = TestKotlin.Str;
}
}
具體的大家可以轉(zhuǎn)一下
kotlin轉(zhuǎn)java看一下加了注解跟沒有加注解的區(qū)別,這里因為篇幅原因我就不加代碼了。
這里補(bǔ)充一點(diǎn):
java怎么調(diào)用kotlin的包級別對象呢???
答案:可以使用@file:JvmName(自己自定義的類名)
@file:JvmName("TestKotlin1")
package com.demo.dan.imoc_voice
val Str = "包級成員"
fun packFun(): String {
return "包級函數(shù)"
}
java調(diào)用:
public class TestJava {
public static void main(String[] args) {
TestKotlin1.getStr();
TestKotlin1.packFun();
}
}
7. 方法重載跟默認(rèn)參數(shù)
-
Overloads方法重載 - 名稱相同、參數(shù)不同的方法
-
Jvm函數(shù)簽名的概念:函數(shù)名、參數(shù)列表 (不包含返回值?。?,也就是當(dāng)兩個方法的方法名跟參數(shù)一致的時候,這個函數(shù)Jvm視為一個函數(shù)。 -
kotlin中可以為函數(shù)參數(shù)設(shè)置默認(rèn)值,來實現(xiàn)重載。 - 函數(shù)調(diào)用產(chǎn)生混淆得時候就使用具名函數(shù)
//下面兩個函數(shù)為同一函數(shù)簽名,函數(shù)一樣。
fun a():Int{}
fun a():String{}
//方法重載
fun a(s:Stirng){}
fun a(s:String,i:int){}
------------------例子-------------------------------------------------------------
//kotlin提供了默認(rèn)函數(shù),
//所以上面的方法重載其實可以改成使用默認(rèn)參數(shù)來實現(xiàn)
fun a(s:String = "ss",i:Int){}
fun main() {
a(str ="str")
}
-
java怎么調(diào)用kotlin的默認(rèn)參數(shù)函數(shù)??
答案:使用@JvmOverloads注解
//kolin代碼----------
@file:JvmName("TestKotlin1")
package com.demo.dan.imoc_voice
@JvmOverloads
fun a(int:Int=1,str: String){}
//java調(diào)用------------
public class TestJava {
public static void main(String[] args) {
TestKotlin1.a("dfsdf");
}
}
-
java方法重載造成得bug,例如List
List.remove(int)
List.remove(Object)
當(dāng)List里面?zhèn)魅氲檬莍nt類型得數(shù)據(jù),那么你
remove()傳入一個4那么它是刪除第四位數(shù)據(jù)還是刪除4這個對象呢?這里它是默認(rèn)調(diào)用了remove(int),而remove(Object)卻不會被調(diào)用到。
8. 擴(kuò)展成員
為現(xiàn)有類添加方法、屬性
- 擴(kuò)展方法格式
fun X.y():Z{...}(fun 被擴(kuò)展類.自定義擴(kuò)展方法名():返回類型{方法體 }) - 擴(kuò)展屬性格式
var X.m(var 被擴(kuò)展類.自定義擴(kuò)展屬性名)
注意擴(kuò)展屬性不能初始化,類似接口屬性(X為被擴(kuò)展類) -
java調(diào)用擴(kuò)展成員類似于調(diào)用靜態(tài)方法
@file:JvmName("TestKotlin1")
package com.demo.dan.voice
// 擴(kuò)展String 增加multiply方法
fun String.multiply(int: Int): String {
val stringBuilder = StringBuilder()
for (i in 0 until int) {
stringBuilder.append(this)
}
return stringBuilder.toString()
}
//擴(kuò)展String 增加運(yùn)算符 -
operator fun String.minus(int: Int): String {
return this.substring(0, length-int+1)
}
//擴(kuò)展成員變量
val String.a: String
get() = "擴(kuò)展成員變量"
//擴(kuò)展成員變量
var String.b: Int
set(value){}
get() = 6
kotlin調(diào)用
fun main(args: Array<String>) {
println("我是碼農(nóng)".multiply(3))
println("我是碼農(nóng)" - 2)
println("".a)
println("".b)
}
------------打印出來的Log--------------------------
我是碼農(nóng)我是碼農(nóng)我是碼農(nóng)
我是碼
擴(kuò)展成員變量
6
java調(diào)用
public static void main(String[] args) {
String str = TestKotlin1.minus("我是Java調(diào)用",2);
String strpr = TestKotlin1.getA("");
System.out.println(str);
System.out.println(strpr);
}
------------打印出來的Log--------------------------
我是Java調(diào)
擴(kuò)展成員變量
9. 屬性代理
val delega1 by lazy { }-
by解析
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
可以看到by實際上是一個操作符,這里是Lazy擴(kuò)展getValue方法。
-
lazy原理解析
@file:kotlin.jvm.JvmName("LazyKt")
//關(guān)鍵代碼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 {
//關(guān)鍵代碼2
private var initializer: (() -> T)? = initializer
//關(guān)鍵代碼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
//關(guān)鍵代碼4
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
//關(guān)鍵代碼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
關(guān)鍵代碼1:將lazy(initializer: () -> T)后面的代碼塊傳入到SynchronizedLazyImpl(initializer)
關(guān)鍵代碼2: 進(jìn)入SynchronizedLazyImpl將傳入的代碼塊賦值于initializer
關(guān)鍵代碼3:_value最開始為未初始化。
關(guān)鍵代碼4:如果_value不為空則直接返回(不執(zhí)行我們傳入的lazy后面代碼塊內(nèi)的方法)
關(guān)鍵代碼5:如果_value為空,做了一下同步的判斷如果不為空則直接返回(不執(zhí)行我們傳入的lazy的方法),否則執(zhí)行我們的傳入的代碼塊initializer并拿返回值初始化_value
每次我們調(diào)用的時候被
lazy代理的對象的時候,實際上是調(diào)用了里面的value的get(),當(dāng)?shù)谝淮握{(diào)用時就會跑我們val delega1 by lazy { 代碼塊}代碼塊中的代碼,實例完里面的SynchronizedLazyImpl#_value后,第二次開始就是直接返回對象。保持只有一個對象實例存在。
-
上面的是
val屬性的代理,lazy能代理var的屬性嗎?
答案:不行。里面并沒有實現(xiàn)setValue的方法。 - 自定義實現(xiàn)代理
下面我們舉一下例子 自己來實現(xiàn)一個代理吧。
class Delegates{
val hello by lazy {
"HelloWorld"
}
val hello2 by X()
//因為X代理了
var hello3 by X()
}
//實現(xiàn)了X這個代理,并實現(xiàn)了 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

上面我們實現(xiàn)一個代理類,并實現(xiàn)了
setValue跟getValue兩個方法。這樣就可以代理var類型的成員變量。
可以看到當(dāng)我們調(diào)用的時候?qū)嶋H上是調(diào)用了代理類的setValue跟getValue兩個方法。
10. 數(shù)據(jù)類data&&特殊寫法&&解決Javabean的問題
- 默認(rèn)實現(xiàn)了
copy、toString,還有componentN等方法 - 直接替代
javaBean存在問題(可能出現(xiàn)無法初始化,構(gòu)造方法簽名不對等問題),因為javaBean類不是final類型可被繼承,且有一個無參數(shù)構(gòu)造函數(shù)。
定義一個data數(shù)據(jù)類
data class TestKotlin(val name:String,val age:Int)
看一下轉(zhuǎn)換成java的實現(xiàn)
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;
}
}
}
- 可以看到
TestKotlin.java是final類型的,并且構(gòu)造函數(shù)沒有無參的。- 里面實現(xiàn)的
component1,component2實際上就是成員變量。- 默認(rèn)實現(xiàn)了
toString、hashCode、equals等方法。
我們來調(diào)用一下 我們寫的TestKotlin數(shù)據(jù)類
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數(shù)組的時候也有這種寫法:
fun main(args:Array<String>) {
for ((index,value)in args.withIndex()){
}
}
-------------------看一下 withIndex 的實現(xiàn)
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存在問題可以使用,allOpen跟noArg插件,前者幫我們?nèi)サ纛惽暗?code>final后者生成無參構(gòu)造函數(shù)
使用方法:
- 在項目(
Project)下的build.gradle
buildscript {//構(gòu)件工具
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.在對應(yīng)的模塊(module)下的build.gradle
apply plugin:'kotlin-noarg'
apply plugin:'kotlin-allopen'
noArg{
//com.demo.dan.annotations.PoKo
//PoKo這個類是你自己創(chuàng)建的,要自己新建個annotations目錄
// 并在下面創(chuàng)建一個類,然后將路徑寫進(jìn)來
annotation("com.demo.dan.annotations.PoKo")
}
allOpen{
// 跟上面一樣
annotation("com.demo.dan.annotations.PoKo")
}
- 新建對應(yīng)的包名跟文件
...\annotatios\PoKo.kt
annotation class PoKo
- 在
data類的類名上面添加注解
@PoKo
data class TestKotlin(val name: String, val age: Int)
- 經(jīng)過上面的注解操作,再
rebuild一下 再轉(zhuǎn)換成java代碼
public class TestKotlin {
................省略其他的代碼
public TestKotlin() {
}
}
可以看到這里已經(jīng)幫我們生成了無參構(gòu)造函數(shù)跟去掉final修飾關(guān)鍵字。
注意:因為注解是在編譯期的時候生效的,也就是說我們在寫代碼的時候還是調(diào)用不了無參構(gòu)造函數(shù),但是可以通過反射獲取到無參構(gòu)造函數(shù)。
11. 內(nèi)部類&&匿名內(nèi)部類
-
kotlin默認(rèn)是靜態(tài)內(nèi)部類,非靜態(tài)內(nèi)部類需要使用inner關(guān)鍵字
//靜態(tài)內(nèi)部類
class TestKotlin {
class Inner{
}
}
//非靜態(tài)內(nèi)部類
class TestKotlin1{
inner class Inner1{ }
}
fun main() {
val inner = TestKotlin.Inner()
val inner1 = TestKotlin1().Inner1()
}
當(dāng)內(nèi)部類需要外部類的狀態(tài)時,則可以使用非靜態(tài)內(nèi)部類,因為非靜態(tài)內(nèi)部類默認(rèn)持有外部類的引用。如果不需要持有外部類的引用,則使用靜態(tài)內(nèi)部類。
-
@Outter的使用,當(dāng)非靜態(tài)內(nèi)部類跟外部類有成員名字相同時,獲取外部類的成員可以用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
}
}
}
- 匿名內(nèi)部類(
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 ,Ktolin的object:還支持同時實現(xiàn)單繼承多實現(xiàn)
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. 枚舉類
- 實例有限(可數(shù))的類,一個枚舉成員對應(yīng)一個本類的實例,因為成員是你寫上去固定的數(shù)目,所以對應(yīng)的實例也是固定的。
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()
}
}
- 如果在
kotlin的enum中要定義方法,記得要用;將成員跟方法隔開
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
子類有限(可數(shù))的類:子類只能在同一個文件中存在,所以其他的文件繼承不了它,所以子類有限(可數(shù))。
- kotlin版本<v1.1,子類必須定義為密封類的內(nèi)部類
- koltin版本>=v1.1,子類只需要于密封類在同一個文件中
- 用于保護(hù)類不被外部繼承使用,其也是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()
}