您好,登錄后才能下訂單哦!
這篇文章主要介紹如何使用Kotlin去提高生產力,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
推薦一個Kotlin的實踐項目 debug_view_kotlin ,用kotlin實現的Android浮層調試控制臺,實時的顯示內存、FPS、文字log
Tip1- 更簡潔的字符串
詳見案例代碼 KotlinTip1
Kotlin中的字符串基本Java中的類似,有一點區別是加入了三個引號"""來方便長篇字符的編寫。 而在Java中,這些都需要轉義,先看看java中的式例
public void testString1() { String str1 = "abc"; String str2 = "line1\n" + "line2\n" + "line3"; String js = "function myFunction()\n" + "{\n" + " document.getElementById(\"demo\").innerHTML=\"My First JavaScript Function\";\n" + "}"; System.out.println(str1); System.out.println(str2); System.out.println(js); }
kotlin除了有單個雙引號的字符串,還對字符串的加強,引入了 三個引號 ,"""中可以包含換行、反斜杠等等特殊字符:
/* * kotlin對字符串的加強,三個引號"""中可以包含換行、反斜杠等等特殊字符 * */ fun testString() { val str1 = "abc" val str2 = """line1\n line2 line3 """ val js = """ function myFunction() { document.getElementById("demo").innerHTML="My First JavaScript Function"; } """.trimIndent() println(str1) println(str2) println(js) }
同時,Kotlin中引入了 字符串模版 ,方便字符串的拼接,可以用$符號拼接變量和表達式
/* * kotlin字符串模版,可以用$符號拼接變量和表達式 * */ fun testString2() { val strings = arrayListOf("abc", "efd", "gfg") println("First content is $strings") println("First content is ${strings[0]}") println("First content is ${if (strings.size > 0) strings[0] else "null"}") }
值得注意的是,在Kotlin中,美元符號$是特殊字符,在字符串中不能直接顯示,必須經過轉義,方法1是用反斜杠,方法二是${'$'}
/* *Kotlin中,美元符號$是特殊字符,在字符串中不能直接顯示,必須經過轉義,方法1是用反斜杠,方法二是${'$'} * */ fun testString3() { println("First content is \$strings") println("First content is ${'$'}strings") }
Tip2- Kotlin中大多數控制結構都是表達式
首先,需要弄清楚一個概念 語句和表達式 ,然后會介紹控制結構表達式的優點: 簡潔
語句和表達式是什么?
表達式有值,并且能作為另一個表達式的一部分使用
語句總是包圍著它的代碼塊中的頂層元素,并且沒有自己的值
Kotlin與Java的區別
Java中,所有的控制結構都是語句,也就是控制結構都沒有值
Kotlin中,除了循環(for、do和do/while)以外,大多數控制結構都是表達式(if/when等)
詳見案例代碼 tip2
Example1:if語句
java中,if 是語句,沒有值,必須顯示的return
/* * java中的if語句 * */ public int max(int a, int b) { if (a > b) { return a; } else { return b; } }
kotlin中,if 是表達式,不是語句,因為表達式有值,可以作為值return出去
/* * kotlin中,if 是表達式,不是語句,類似于java中的三木運算符a > b ? a : b * */ fun max(a: Int, b: Int): Int { return if (a > b) a else b }
上面的if中的分支***一行語句就是該分支的值,會作為函數的返回值。這其實跟java中的三元運算符類似,
/* * java的三元運算符 * */ public int max2(int a, int b) { return a > b ? a : b; }
上面是java中的三元運算符,kotlin中if是表達式有值,完全可以替代, 故kotlin中已沒有三元運算符了 ,用if來替代。 上面的max函數還可以簡化成下面的形式
/* * kotlin簡化版本 * */ fun max2(a: Int, b: Int) = if (a > b) a else b
Example2:when語句
Kotlin中的when非常強大,完全可以取代Java中的switch和if/else,同時, when也是表達式 ,when的每個分支的***一行為當前分支的值 先看一下java中的switch
/* * java中的switch * */ public String getPoint(char grade) { switch (grade) { case 'A': return "GOOD"; case 'B': case 'C': return "OK"; case 'D': return "BAD"; default: return "UN_KNOW"; } }
java中的switch有太多限制,我們再看看Kotlin怎樣去簡化的
/* * kotlin中,when是表達式,可以取代Java 中的switch,when的每個分支的***一行為當前分支的值 * */ fun getPoint(grade: Char) = when (grade) { 'A' -> "GOOD" 'B', 'C' -> { println("test when") "OK" } 'D' -> "BAD" else -> "UN_KNOW" }
同樣的,when語句還可以取代java中的if/else if,其是表達式有值,并且更佳簡潔
/* * java中的if else * */ public String getPoint2(Integer point) { if (point > 100) { return "GOOD"; } else if (point > 60) { return "OK"; } else if (point.hashCode() == 0x100) { //... return "STH"; } else {a return "UN_KNOW"; } }
再看看kotlin的版本,使用 不帶參數的when ,只需要6行代碼
/* * kotlin中,when是表達式,可以取代java的if/else,when的每個分支的***一行為當前分支的值 * */ fun getPoint2(grade: Int) = when { grade > 90 -> "GOOD" grade > 60 -> "OK" grade.hashCode() == 0x100 -> "STH" else -> "UN_KNOW" }
Tip3- 更好調用的函數:顯示參數名/默認參數值
Kotlin的函數更加好調用,主要是表現在兩個方面:1,顯示的 標示參數名 ,可以方便代碼閱讀;2,函數可以有 默認參數值 ,可以大大 減少Java中的函數重載 。 例如現在需要實現一個工具函數,打印列表的內容: 詳見案例代碼 KotlinTip3
/* * 打印列表的內容 * */ fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String { val result = StringBuilder(prefix) for ((index, element) in collection.withIndex()) { if (index > 0) result.append(separator) result.append(element) } result.append(postfix) return result.toString() } /* * 測試 * */ fun printList() { val list = listOf(2, 4, 0) /*不標明參數名*/ println(joinToString(list, " - ", "[", "]")) /*顯示的標明參數名稱*/ println(joinToString(list, separator = " - ", prefix = "[", postfix = "]")) }
如上面的代碼所示,函數joinToString想要打印列表的內容,需要傳人四個參數:列表、分隔符、前綴和后綴。 由于參數很多,在后續使用該函數的時候不是很直觀的知道每個參數是干什么用的,這時候可以顯示的標明參數名稱,增加代碼可讀性。 同時,定義函數的時候還可以給函數默認的參數,如下所示:
/* * 打印列表的內容,帶有默認的參數,可以避免java的函數重載 * */ fun <T> joinToString2(collection: Collection<T>, separator: String = ", ", prefix: String = "", postfix: String = ""): String { val result = StringBuilder(prefix) for ((index, element) in collection.withIndex()) { if (index > 0) result.append(separator) result.append(element) } result.append(postfix) return result.toString() } /* * 測試 * */ fun printList3() { val list = listOf(2, 4, 0) println(joinToString2(list, " - ")) println(joinToString2(list, " , ", "["))
這樣有了默認參數后,在使用函數時,如果不傳入該參數,默認會使用默認的值,這樣可以避免Java中大量的函數重載。
Tip4- 擴展函數和屬性
擴展函數和屬性是Kotlin非常方便實用的一個功能,它可以讓我們隨意的擴展第三方的庫,你如果覺得別人給的SDK的api不好用,或者不能滿足你的需求,這時候你可以用擴展函數完全去自定義。 例如String類中,我們想獲取***一個字符,String中沒有這樣的直接函數,你可以用.后聲明這樣一個擴展函數: 詳見案例代碼 KotlinTip4
/* * 擴展函數 * */ fun String.lastChar(): Char = this.get(this.length - 1) /* *測試 * */ fun testFunExtension() { val str = "test extension fun"; println(str.lastChar()) }
這樣定義好lastChar()函數后,之后只需要import進來后,就可以用String類直接調用該函數了,跟調用它自己的方法沒有區別。這樣可以避免重復代碼和一些靜態工具類,而且代碼更加簡潔明了。 例如我們可以改造上面tip3中的打印列表內容的函數:
/* * 用擴展函數改造Tip3中的列表打印內容函數 * */ fun <T> Collection<T>.joinToString3(separator: String = ", ", prefix: String = "", postfix: String = ""): String { val result = StringBuilder(prefix) for ((index, element) in withIndex()) { if (index > 0) result.append(separator) result.append(element) } result.append(postfix) return result.toString() } fun printList4() { val list = listOf(2, 4, 0) println(list.joinToString3("/")) }
除了擴展函數,還可以擴展屬性,例如我想實現String和StringBuilder通過屬性去直接獲得***字符:
/* * 擴展屬性 lastChar獲取String的***一個字符 * */ val String.lastChar: Char get() = get(length - 1) /* * 擴展屬性 lastChar獲取StringBuilder的***一個字符 * */ var StringBuilder.lastChar: Char get() = get(length - 1) set(value: Char) { setCharAt(length - 1, value) } /* * 測試 * */ fun testExtension(){ val s = "abc" println(s.lastChar) val sb = StringBuilder("abc") println(sb.lastChar) }
定義好擴展屬性后,之后只需import完了就跟使用自己的屬性一樣方便了。
Why?Kotlin為什么能實現擴展函數和屬性這樣的特性?
在Kotlin中要理解一些語法,只要認識到 Kotlin語言***需要編譯為class字節碼,Java也是編譯為class執行,也就是可以大致理解為Kotlin需要轉成Java一樣的語法結構 , Kotlin就是一種 強大的語法糖 而已,Java不具備的功能Kotlin也不能越界的。
那Kotlin的擴展函數怎么實現的呢?介紹一種***的辦法去理解Kotlin的語法: 將Kotlin代碼轉化成Java語言 去理解,步驟如下:
1、在Android Studio中選擇Tools ---> Kotlin ---> Show Kotlin Bytecode 這樣就把Kotlin轉化為class字節碼了
class碼閱讀不太友好,點擊左上角的Decompile就轉化為Java
2、再介紹一個小竅門,在前期對Kotlin語法不熟悉的時候,可以先用Java寫好代碼,再利用AndroidStudio工具 將Java代碼轉化為Kotlin代碼 ,步驟如下:
在Android Studio中選中要轉換的Java代碼 ---> 選擇Code ---> Convert Java File to Kotlin File
我們看看將上面的擴展函數轉成Java后的代碼
/* * 擴展函數會轉化為一個靜態的函數,同時這個靜態函數的***個參數就是該類的實例對象 * */ public static final char lastChar(@NotNull String $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); return $receiver.charAt($receiver.length() - 1); } /* * 獲取的擴展屬性會轉化為一個靜態的get函數,同時這個靜態函數的***個參數就是該類的實例對象 * */ public static final char getLastChar(@NotNull StringBuilder $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); return $receiver.charAt($receiver.length() - 1); } /* * 設置的擴展屬性會轉化為一個靜態的set函數,同時這個靜態函數的***個參數就是該類的實例對象 * */ public static final void setLastChar(@NotNull StringBuilder $receiver, char value) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); $receiver.setCharAt($receiver.length() - 1, value); }
查看上面的代碼可知:對于擴展函數,轉化為Java的時候其實就是一個靜態的函數,同時這個靜態函數的***個參數就是該類的實例對象,這樣把類的實例傳人函數以后,函數內部就可以訪問到類的公有方法。 對于擴展屬性也類似,獲取的擴展屬性會轉化為一個靜態的get函數,同時這個靜態函數的***個參數就是該類的實例對象,設置的擴展屬性會轉化為一個靜態的set函數,同時這個靜態函數的***個參數就是該類的實例對象。 函數內部可以訪問公有的方法和屬性。
從上面轉換的源碼其實可以看到 擴展函數和擴展屬性適用的地方和缺陷 ,有兩點:
擴展函數和擴展屬性內 只能訪問到類的公有方法和屬性 ,私有的和protected是訪問不了的
擴展函數 不能被override ,因為Java中它是靜態的函數
下面再舉幾個擴展函數的例子,讓大家感受一下擴展函數的方便:
/* * show toast in activity * */ fun Activity.toast(msg: String){ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() } val Context.inputMethodManager: InputMethodManager? get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager /* * hide soft input * */ fun Context.hideSoftInput(view: View) { inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0) } /** * screen width in pixels */ val Context.screenWidth get() = resources.displayMetrics.widthPixels /** * screen height in pixels */ val Context.screenHeight get() = resources.displayMetrics.heightPixels /** * returns dip(dp) dimension value in pixels * @param value dp */ fun Context.dip2px(value: Int): Int = (value * resources.displayMetrics.densi
Tip5- 懶初始化by lazy 和 延遲初始化lateinit
懶初始化by lazy
懶初始化是指推遲一個變量的初始化時機,變量在使用的時候才去實例化,這樣會更加的高效。因為我們通常會遇到這樣的情況,一個變量直到使用時才需要被初始化,或者僅僅是它的初始化依賴于某些無法立即獲得的上下文。 詳見案例代碼 KotlinTip5
/* * 懶初始化api實例 * */ val purchasingApi: PurchasingApi by lazy { val retrofit: Retrofit = Retrofit.Builder() .baseUrl(API_URL) .addConverterFactory(MoshiConverterFactory.create()) .build() retrofit.create(PurchasingApi::class.java) }
像上面的代碼,retrofit生成的api實例會在***使用到的時候才去實例化。需要注意的是by lazy一般只能修飾val不變的對象,不能修飾var可變對象。
class User(var name: String, var age: Int) /* * 懶初始化by lazy * */ val user1: User by lazy { User("jack", 15) }
延遲初始化lateinit
另外,對于var的變量,如果類型是非空的,是必須初始化的,不然編譯不通過,這時候需要用到lateinit延遲初始化,使用的時候再去實例化。
/* * 延遲初始化lateinit * */ lateinit var user2: User fun testLateInit() { user2 = User("Lily", 14) }
by lazy 和 lateinit 的區別
by lazy 修飾val的變量
lateinit 修飾var的變量,且變量是非空的類型
Tip6- 不用再手寫findViewById
在Android的View中,會有很多代碼是在聲明一個View,然后通過findViewById后從xml中實例化賦值給對應的View。在kotlin中可以完全解放出來了,利用kotlin-android-extensions插件,不用再手寫findViewById。步驟如下: 詳見案例代碼 KotlinTip6
步驟1,在項目的gradle中 apply plugin: 'kotlin-android-extensions'
步驟2,按照原來的習慣書寫布局xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tip6Tv" android:layout_width="match_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/tip6Img" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/tip6Btn" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
步驟3,在java代碼中import對應的布局就可以開始使用了,View不用提前聲明,插件會自動根據布局的id生成對應的View成員(其實沒有生成屬性,原理見下面)
import com.sw.kotlin.tips.R /* * 導入插件生成的View * */ import kotlinx.android.synthetic.main.activity_tip6.* class KotlinTip6 : Activity(){ /* * 自動根據layout的id生成對應的view * */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_tip6) tip6Tv.text = "Auto find view for TextView" tip6Img.setImageBitmap(null) tip6Btn.setOnClickListener{ test() } } private fun test(){ tip6Tv.text = "update" } }
像上面代碼這樣,Activity里的三個View自動生成了,不用再去聲明,然后findViewById,然后轉型賦值,是不是減少了很多沒必要的代碼,讓代碼非常的干凈。
Why?原理是什么?插件幫我們做了什么?
要看原理還是將上面的代碼轉為java語言來理解,參照tips4提供的方式轉換為如下的java代碼:
public final class KotlinTip6 extends Activity { private HashMap _$_findViewCache; protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(2131296284); TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv); Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv"); var10000.setText((CharSequence)"Auto find view for TextView"); ((ImageView)this._$_findCachedViewById(id.tip6Img)).setImageBitmap((Bitmap)null); ((Button)this._$_findCachedViewById(id.tip6Btn)).setOnClickListener((OnClickListener)(new OnClickListener() { public final void onClick(View it) { KotlinTip6.this.test(); } })); } private final void test() { TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv); Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv"); var10000.setText((CharSequence)"update"); } public View _$_findCachedViewById(int var1) { if(this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1)); if(var2 == null) { var2 = this.findViewById(var1); this._$_findViewCache.put(Integer.valueOf(var1), var2); } return var2; } public void _$_clearFindViewByIdCache() { if(this._$_findViewCache != null) { this._$_findViewCache.clear(); } } }
如上面的代碼所示,在編譯階段,插件會幫我們生成視圖緩存,視圖由一個Hashmap結構的_$_findViewCache變量緩存, 會根據對應的id先從緩存里查找,緩存沒***再去真正調用findViewById查找出來,再存在HashMap中。
在fragment中findViewByID
在fragment中也類似,有一點區別,例子如下:
class Tip6Fragment : Fragment() { override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater?.inflate(R.layout.fragment_tip6, container, false) /* * 這時候不能在onCreateView方法里用view,需要在onViewCreate里,原理是插件用了getView來findViewById * */ //tip6Tv.text = "test2" return view } /* * 需要在onViewCreate里,原理是插件用了getView來findViewById * */ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) tip6Tv.text = "test" } }
如上所示,Fragment需要注意,不能在onCreateView方法里用view,不然會出現空指針異常,需要在onViewCreate里,原理是插件用了getView來findViewById, 我們看看將上面的代碼轉成java后的代碼:
public final class Tip6Fragment extends Fragment { private HashMap _$_findViewCache; @Nullable public View onCreateView(@Nullable LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater != null?inflater.inflate(2131296286, container, false):null; return view; } public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv); Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv"); var10000.setText((CharSequence)"test"); } public View _$_findCachedViewById(int var1) { if(this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1)); if(var2 == null) { View var10000 = this.getView(); if(var10000 == null) { return null; } var2 = var10000.findViewById(var1); this._$_findViewCache.put(Integer.valueOf(var1), var2); } return var2; } public void _$_clearFindViewByIdCache() { if(this._$_findViewCache != null) { this._$_findViewCache.clear(); } } // $FF: synthetic method public void onDestroyView() { super.onDestroyView(); this._$_clearFindViewByIdCache(); } }
跟Activity中類似,會有一個View的HashMap,關鍵不同的地方在_$ findCachedViewById里面,需要getView或者當前Fragment的View, 故在onViewCreated中getView還是空的,原理就好理解了。另外在onDestroyView會調用 $_clearFindViewByIdCache方法清掉緩存。
Tip7- 利用局部函數抽取重復代碼
Kotlin中提供了函數的嵌套,在函數內部還可以定義新的函數。這樣我們可以在函數中嵌套這些提前的函數,來抽取重復代碼。如下面的案例所示: 詳見案例代碼 KotlinTip7
class User(val id: Int, val name: String, val address: String, val email: String) fun saveUser(user: User) { if (user.name.isEmpty()) { throw IllegalArgumentException("Can't save user ${user.id}: empty Name") } if (user.address.isEmpty()) { throw IllegalArgumentException("Can't save user ${user.id}: empty Address") } if (user.email.isEmpty()) { throw IllegalArgumentException("Can't save user ${user.id}: empty Email") } //save to db ... }
上面的代碼在判斷name、address等是否為空的處理其實很類似。這時候,我們可以利用在函數內部嵌套的聲明一個通用的判空函數將相同的代碼抽取到一起:
/* *利用局部函數抽取相同的邏輯,去除重復的代碼 * */ fun saveUser2(user: User) { fun validate(value: String, fildName: String) { if (value.isEmpty()) { throw IllegalArgumentException("Can't save user ${user.id}: empty $fildName") } } validate(user.name, "Name") validate(user.address, "Address") validate(user.email, "Email") //save to db ... }
除了利用嵌套函數去抽取,此時,其實也可以用擴展函數來抽取,如下所示:
/* * 利用擴展函數抽取邏輯 * */ fun User.validateAll() { fun validate(value: String, fildName: String) { if (value.isEmpty()) { throw IllegalArgumentException("Can't save user $id: empty $fildName") } } validate(name, "Name") validate(address, "Address") validate(email, "Email") } fun saveUser3(user: User) { user.validateAll() //save to db ... }
Tip8- 使用數據類來快速實現model類
在java中要聲明一個model類需要實現很多的代碼,首先需要將變量聲明為private,然后需要實現get和set方法,還要實現對應的hashcode equals toString方法等,如下所示: 詳見案例代碼 Tip8
public static class User{ private String name; private int age; private int gender; private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", gender=" + gender + ", address='" + address + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (age != user.age) return false; if (gender != user.gender) return false; if (name != null ? !name.equals(user.name) : user.name != null) return false; return address != null ? address.equals(user.address) : user.address == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; result = 31 * result + gender; result = 31 * result + (address != null ? address.hashCode() : 0); return result; } }
這段代碼Java需要70行左右,而如果用kotlin,只需要一行代碼就可以做到。
/* * Kotlin會為類的參數自動實現get set方法 * */ class User(val name: String, val age: Int, val gender: Int, var address: String) /* * 用data關鍵詞來聲明一個數據類,除了會自動實現get set,還會自動生成equals hashcode toString * */ data class User2(val name: String, val age: Int, val gender: Int, var address
對于Kotlin中的類,會為它的參數自動實現get set方法。而如果加上data關鍵字,還會自動生成equals hashcode toString。原理其實數據類中的大部分代碼都是模版代碼,Kotlin聰明的將這個模版代碼的實現放在了編譯器處理的階段。
Tip9- 用類委托來快速實現裝飾器模式
通過繼承的實現容易導致脆弱性,例如如果需要修改其他類的一些行為,這時候Java中的一種策略是采用 裝飾器模式 :創建一個新類,實現與原始類一樣的接口并將原來的類的實例作為一個成員變量。 與原始類擁有相同行為的方法不用修改,只需要直接轉發給原始類的實例。如下所示: 詳見案例代碼 KotlinTip9
/* * 常見的裝飾器模式,為了修改部分的函數,卻需要實現所有的接口函數 * */ class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> { var objectAdded = 0 override val size: Int get() = innerSet.size /* * 需要修改的方法 * */ override fun add(element: T): Boolean { objectAdded++ return innerSet.add(element) } /* * 需要修改的方法 * */ override fun addAll(elements: Collection<T>): Boolean { objectAdded += elements.size return innerSet.addAll(elements) } override fun contains(element: T): Boolean { return innerSet.contains(element) } override fun containsAll(elements: Collection<T>): Boolean { return innerSet.containsAll(elements) } override fun isEmpty(): Boolean { return innerSet.isEmpty() } override fun clear() { innerSet.clear() } override fun iterator(): MutableIterator<T> { return innerSet.iterator() } override fun remove(element: T): Boolean { return innerSet.remove(element) } override fun removeAll(elements: Collection<T>): Boolean { return innerSet.removeAll(elements) } override fun retainAll(elements: Collection<T>): Boolean { return innerSet.retainAll(elements) } }
如上所示,想要修改HashSet的某些行為函數add和addAll,需要實現MutableCollection接口的所有方法,將這些方法轉發給innerSet去具體的實現。雖然只需要修改其中的兩個方法,其他代碼都是模版代碼。 只要是重復的模版代碼,Kotlin這種全新的語法糖就會想辦法將它放在編譯階段再去生成。 這時候可以用到 類委托by關鍵字 ,如下所示:
/* * 通過by關鍵字將接口的實現委托給innerSet成員變量,需要修改的函數再去override就可以了 * */ class CountingSet2<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet { var objectAdded = 0 override fun add(element: T): Boolean { objectAdded++ return innerSet.add(element) } override fun addAll(elements: Collection<T>): Boolean { objectAdded += elements.size return innerSet.addAll(elements) } }
通過by關鍵字將接口的實現委托給innerSet成員變量,需要修改的函數再去override就可以了,通過類委托將10行代碼就可以實現上面接近100行的功能,簡潔明了,去掉了模版代碼。
Tip10- Lambda表達式簡化代碼
詳見案例代碼 KotlinTip10 lambda表達式可以簡化我們的代碼。以Android中常見的OnClickListener來說明,在Java中我們一般這樣設置:
TextView textView = new TextView(context); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //handle click } });
Java中需要聲明一個匿名內部類去處理,這種情況可以用lambda表達式來簡化。
lambda表達式一般長這樣
{ x:Int, y:Int -> x+y }
參數 -> 表達式 并且始終在大括號中
it作為默認參數名
lambda捕捉,當捕捉final變量時,它的值和lambda代碼一起存儲
非final變量,它的值被封裝在一個特殊的包裝器中,這個包裝器的引用會和lambda代碼一起存儲
我們來看看Kotlin中的例子:
val textView = TextView(context) /* * 傳統方式 * */ textView.setOnClickListener(object : android.view.View.OnClickListener { override fun onClick(v: android.view.View?) { //handle click } }) /* * lambda的方式 * */ textView.setOnClickListener({ v -> { //handle click } })
當lambda的參數沒有使用時可以省略,省略的時候用it來替代
/* * lambda的參數如果沒有使用可以省略,省略的時候用it來替代 * */ textView.setOnClickListener({ //handle click })
lambda在參數的***一個的情況可以將之提出去
/* * lambda在參數的***一個的情況可以將之提出去 * */ textView.setOnClickListener() { //handle click }
lambda提出去之后,函數如果沒有其他參數括號可以省略
/* * lambda提出去之后,函數如果沒有其他參數括號可以省略 * */ textView.setOnClickListener { //handle click }
我們再看看如果自己去實現一個帶lambda參數的函數應該怎么去定義:
interface OnClickListener { fun onClick() } class View { var listener: OnClickListener? = null; /* * 傳統方式 * */ fun setOnClickListener(listener: OnClickListener) { this.listener = listener } fun doSth() { //some case: listener?.onClick() } /* * 聲明lambda方式,listener: () -> Unit * */ fun setOnClickListener(listener: () -> Unit) { } }
在函數參數中需要聲明lambda的類型后,再調用該函數的時候就可以傳人一個lambda表達式了。
Tip11- with語句來簡化代碼
with 函數原型:
inline fun with(receiver: T, block: T.() -> R): R = receiver.block()
with函數并不是擴展函數,返回值是***一行,可以直接調用對象的方法
Kotlin中可以用with語句來省略同一個變量的多次聲明,例如下面的函數 詳見案例代碼 KotlinTip11
/* *打印字母表函數,在函數內result變量在好幾處有使用到 * */ fun alphabet(): String { val result = StringBuilder() result.append("START\n") for (letter in 'A'..'Z') { result.append(letter) } result.append("\nEND") return result.toString() }
在上面的函數中,result變量出現了5次,如果用with語句,可以將這5次都不用再出現了,我們來一步一步地看是怎么實現的:
/* * 通過with語句,將result作為參數傳人,在內部就可以通過this來表示result變量了 * */ fun alphabet2(): String { val result = StringBuilder() return with(result) { this.append("START\n") for (letter in 'A'..'Z') { this.append(letter) } this.append("\nEND") this.toString() } }
通過with語句,將result作為參數傳人,在內部就可以通過this來表示result變量了,而且這個this是可以省略的
/* * 通過with語句,將result參數作為參數,在內部this也可以省略掉 * */ fun alphabet3(): String { val result = StringBuilder() return with(result) { append("START\n") for (letter in 'A'..'Z') { append(letter) } append("\nEND") toString() } }
在內部this省略掉后,現在只有一個result了,這個其實也是沒必要的,于是出現了下面的最終版本:
/* * 通過with語句,可以直接將對象傳人,省掉對象的聲明 * */ fun alphabet4(): String { return with(StringBuilder()) { append("START\n") for (letter in 'A'..'Z') { append(letter) } append("\nEND") toString() } }
像上面這樣,我們可以把同一個變量的顯式調用從5次變為0次,發現Kotlin的魅力了吧。
Tip12- apply語句來簡化代碼
apply 函數原型:
inline fun T.apply(block: T.() -> Unit): T { block(); return this }
apply函數,在函數范圍內,可以任意調用該對象的任意方法,并返回該對象
除了用上面的with可以簡化同一個變量的多次聲明,還可以用apply關鍵字,我們來改造一下tip11中的函數: 詳見案例代碼 KotlinTip12
/* * 用apply語句簡化代碼,在apply的大括號里可以訪問類的公有屬性和方法 * */ fun alphabet5() = StringBuilder().apply { append("START\n") for (letter in 'A'..'Z') { append(letter) } append("\nEND") }.toString()
像上面這樣的,通過apply后,在apply的大括號里可以訪問類的公有屬性和方法。這在對應類的初始化是非常方便的,例如下面的例子
/* * 用apply語句簡化類的初始化,在類實例化的時候,就可以通過apply把需要初始化的步驟全部實現,非常的簡潔 * */ fun testApply(context: Context) { var imgView = ImageView(context).apply { setBackgroundColor(0) setImageBitmap(null) } var textView = TextView(context).apply { text = "content" textSize = 20.0f setPadding(10, 0, 0, 0) } var user = User().apply { age = 15 name = "Jack" val a = address address = "bbb" } }
在類實例化的時候,就可以通過apply把需要初始化的步驟全部實現,非常的簡潔
Tip13- 在編譯階段避免掉NullPointerException
NullPointerException是Java程序員非常頭痛的一個問題,我們知道Java 中分受檢異常和非受檢異常,NullPointerException是非受檢異常,也就是說NullPointerException不需要顯示的去catch住, 往往在運行期間,程序就可能報出一個NullPointerException然后crash掉,Kotlin作為一門高效安全的語言,它嘗試在編譯階段就把空指針問題顯式的檢測出來,把問題留在了編譯階段,讓程序更加健壯。 詳見案例代碼 KotlinTip13
Kotlin中類型分為可空類型和不可空類型,通過?代表可空,不帶?代表不可為空
fun testNullType() { val a: String = "aa" /* * a是非空類型,下面的給a賦值為null將會編譯不通過 * */ //a = null a.length /* * ?聲明是可空類型,可以賦值為null * */ var b: String? = "bb" b = null /* * b是可空類型,直接訪問可空類型將編譯不通過,需要通過?.或者!!.來訪問 * */ //b.length b?.length b!!.length }
對于一個不可為空類型:如果直接給不可為空類型賦值一個可能為空的對象就在編譯階段就不能通過
對于一個可空類型:通過?聲明,在訪問該類型的時候直接訪問不能編譯通過,需要通過?.或者!!.
?. 代表著如果該類型為空的話就返回null不做后續的操作,如果不為空的話才會去訪問對應的方法或者屬性
!!. 代表著如果該類型為空的話就拋出NullPointerException,如果不為空就去訪問對應的方法或者屬性, 所以只有在很少的特定場景才用這種符號,代表著程序不處理這種異常的case了,會像java代碼一樣拋出NullPointerException。 而且代碼中一定不用出現下面這種代碼,會讓代碼可讀性很差而且如果有空指針異常,我們也不能馬上發現是哪空了:
/* * 不用鏈式的連續用!!. * */ val user = User() user!!.name!!.subSequence(0,5)!!.length
對應一個可空類型,每次對它的訪問都需要帶上?.判斷
val user: User? = User() /* * 每次訪問都用用?.判斷 * */ user?.name user?.age user?.toString()
但這樣多了很多代碼,kotlin做了一些優化,
/* * 或者提前判斷是否為空,如果不為空在這個分支里會自動轉化為非空類型就可以直接訪問了 * */ if (user != null) { user.name user.age user.toString() }
通過if提前判斷類型是否為空,如果不為空在這個分支里會 自動轉化為非空類型 就可以直接訪問了。
let語句簡化對可空對象對訪問
let 函數原型:
inline fun T.let(block: (T) -> R): R = block(this)
let函數默認當前這個對象作為閉包的it參數,返回值是函數里面***一行,或者指定return。
上面的代碼還可以用?.let語句進行,如下所示:
/* * 通過let語句,在?.let之后,如果為空不會有任何操作,只有在非空的時候才會執行let之后的操作 * */ user?.let { it.name it.age it.toString() }
通過let語句,在?.let之后,如果為空不會有任何操作,只有在非空的時候才會執行let之后的操作
Elvis操作符 ?: 簡化對空值的處理
如果值可能為空,對空值的處理可能會比較麻煩,像下面這樣:
/* * 對空值的處理 * */ fun testElvis(input: String?, user: User?) { val a: Int? if (input == null) { a = input?.length } else { a = -1; } if (user == null) { var newOne = User() newOne.save() } else { user.save() } }
Elvis操作符?:能夠簡化上面的操作,?:符號會在對于空的情況才會進行下面的處理, 跟?.let正好相反 ,例如我們可以用兩行代碼來簡化上面從操作:
/** * Elvis操作符 ?: 簡化對空值的處理 */ fun testElvis2(input: String?, user: User?) { val b = input?.length ?: -1; user?.save() ?: User().save() }
Tip14- 運算符重載
Kotlin支持對運算符的重載,這對于對一些對象的操作更加靈活直觀。
使用operator來修飾plus\minus函數
可重載的二元算術符
A * B times
A / B div
A % B mod
A + B plus
A - B minus
以下面對坐標點Point的案例說明怎么去重載運算符
class Point(val x: Int, val y: Int) { /* * plus函數重載對Point對象的加法運算符 * */ operator fun plus(other: Point): Point { return Point(x + other.x, y + other.y) } /* * minus函數重載對Point對象的減法運算符 * */ operator fun minus(other: Point): Point { return Point(x - other.x, y - other.y) } override fun toString(): String { return "[x:$x, y:$y]" } }
如上所示,通過plus函數重載對Point對象的加法運算符,通過minus函數重載對Point對象的減法運算符,然后就可以用+、-號對兩個對象進行操作了:
fun testOperator() { val point1 = Point(10, 10) val point2 = Point(4, 4) val point3 = point1 + point2 println(point3) println(point1 - point2) }
Tip15- 高階函數簡化代碼
高階函數:以另一個函數作為參數或者返回值的函數
函數類型
(Int, String) -> Unit
參數類型->返回類型 Unit不能省略
val list = listOf(2, 5, 10) /* * 傳人函數來過濾 * */ println(list.filter { it > 4 }) /* * 定義函數類型 * */ val sum = { x: Int, y: Int -> x + y } val action = { println(42) } val sum2: (Int, Int) -> Int = { x: Int, y: Int -> x + y } val action2: () -> Unit = { println(42) }
函數作為參數
函數作為參數,即高階函數中,函數的參數可以是一個函數類型,例如要定義一個函數,該函數根據傳人的操作函數來對2和3做相應的處理。 詳見案例代碼 KotlinTip15
/* * 定義對2和3的操作函數 * */ fun twoAndThree(operator: (Int, Int) -> Int) { val result = operator(2, 3) println("Result:$result") } fun test03() { twoAndThree { a, b -> a + b } twoAndThree { a, b -> a * b } }
operator是函數類型,函數的具體類型為(Int, Int) -> Int,即輸入兩個Int返回一個Int值。定義完了后就可以像上面這樣使用了。 再舉一個例子,實現String類的字符過濾:
/* * 函數作為參數,實現String類的字符過濾 * */ fun String.filter(predicate: (Char) -> Boolean): String { val sb = StringBuilder() for (index in 0 until length) { val element = get(index) if (predicate(element)) sb.append(element) } return sb.toString() } fun test04() { println("12eafsfsfdbzzsa".filter { it in 'a'..'f' }) }
像上面這樣predicate是函數類型,它會根據傳人的char來判斷得到一個Boolean值。
函數作為返回值
函數作為返回值也非常實用,例如我們的需求是根據不同的快遞類型返回不同計價公式,普通快遞和高級快遞的計價規則不一樣,這時候我們可以將計價規則函數作為返回值:
enum class Delivery { STANDARD, EXPEDITED } /* * 根據不同的運輸類型返回不同的快遞方式 * */ fun getShippingCostCalculator(delivery: Delivery): (Int) -> Double { if (delivery == Delivery.EXPEDITED) { return { 6 + 2.1 * it } } return { 1.3 * it } } fun test05(){ val calculator1 = getShippingCostCalculator(Delivery.EXPEDITED) val calculator2 = getShippingCostCalculator(Delivery.STANDARD) println("Ex costs ${calculator1(5)}") println("St costs ${calculator2(5)}") }
如果是普通快遞,采用6 + 2.1 * it的規則計算價格,如果是高級快遞按照6 + 2.1 * it計算價格,根據不同的類型返回不同的計價函數。
Tip16- 用Lambda來簡化策略模式
策略模式是常見的模式之一,java的例子如下。 詳見案例代碼 Tip16
/** * 定義策略接口 */ public interface Strategy { void doSth(); } /** * A策略 */ public static class AStrategy implements Strategy { @Override public void doSth() { System.out.println("Do A Strategy"); } } /** * B策略 */ public static class BStrategy implements Strategy { @Override public void doSth() { System.out.println("Do B Strategy"); } } /** * 策略實施者 */ public static class Worker { private Strategy strategy; public Worker(Strategy strategy) { this.strategy = strategy; } public void work() { System.out.println("START"); if (strategy != null) { strategy.doSth(); } System.out.println("END"); } }
如上面的例子所示,有A、B兩種策略,Worker根據不同的策略做不同的工作,使用策略時:
Worker worker1 = new Worker(new AStrategy()); Worker worker2 = new Worker(new BStrategy()); worker1.work(); worker2.work();
在java中實現這種策略模式難免需要先定義好策略的接口,然后根據接口實現不同的策略, 在Kotlin中完全可以用用Lambda來簡化策略模式,上面的例子用Kotlin實現:
/** * 策略實施者 * @param strategy lambda類型的策略 */ class Worker(private val strategy: () -> Unit) { fun work() { println("START") strategy.invoke() println("END") } } /* * 測試 * */ fun testStrategy() { val worker1 = Worker({ println("Do A Strategy") }) val bStrategy = { println("Do B Strategy") } val worker2 = Worker(bStrategy) worker1.work() worker2.work() }
不需要先定義策略的接口,直接把策略以lambda表達式的形式傳進來就行了。
以上是“如何使用Kotlin去提高生產力”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。