1. 背景
Jetbrains早在2010年推出Kotlin,在今年(2017)5月18,谷歌在今日舉行的I/O開發者大會上宣佈,將Kotlin語言作為安卓開發的一級程式語言,這個可愛的語言可於Java進行無縫混編。之前做過一段時間的C#和python開發,感覺三者之間總兩兩相似,不清楚是誰在模仿誰,誰在吃著誰的語法糖。
2. 差異與對比
2.1 構造器
java | Kotlin |
---|---|
構造器 | 主構造器 |
- | 次構造器 |
- java版:
public class Model {
public Model(){}
public Model(String name){}
}
複製程式碼
- Kotlin版:
- 主構造器
class KtModel constructor(name: String)
ps:如果不私有化構造器或者註解構造器,constructor可以省略,即:
-> class KtModel (name: String)
複製程式碼
由於Kotlin主構造器寫法的出現,可以簡化Java中JavaBean,關鍵字:data
data class KtModel(var id: Int, var name: String)
複製程式碼
- Kotlin版
- 次構造器
class Model{
var mName:String? = null
constructor(name:String){
mName = name
}
}
//由於Kotlin中有引數預設寫法,比如:
class Model{
var mName:String? = null
constructor(name:String,age:Int = 0){
mName = name
}
}
//在java中例項化這個Model還是需要:
Model model = new Model("Inke",20)
//為了讓java也享受到這個福利,Kotlin支援@JvmOverloads註解,為Java過載構造器
class Model{
var mName:String? = null
@JvmOverloads
constructor(name:String,age:Int = 0){
mName = name
}
}
//此時:java呼叫
Model model = new Model("Inke",20);
Model model = new Model("Inke");
複製程式碼
2.2 自定義setter和getter
由data宣告的javabean預設實現了getter方法,如果變數為var型別也同時實現了getter方法,並且像equal() hashcode() toString()方法也自動複寫了。除此,也支援自定義setter和getter方法,寫法和c#的方式一樣。
class Student(var name: String, var grade: Int) {
// Kotlin中的enum
enum class LEVEL {GOOD, JUSTSOSO }
//預設規則賦值
var level: LEVEL = if (grade > 90) LEVEL.GOOD else LEVEL.JUSTSOSO
set(value) {
field = value
}
get() = field
}
ps: 通過field代表set get的屬性
複製程式碼
2.3 條件篩選
java中對某一欄位型別根據不同的值進行篩選,同使用Switch“語句”,如:
switch (itemViewType) {
case ITEM_VIEWTYPE_LINKMSG:// 帶有link欄位的連結訊息
return new LinkMsgViewHolder(inflater);
case ITEM_VIEWTYPE_TEXTMSG_BYME:// 自己的文字訊息
return new TextMsgByMeViewHolder(inflater);
case ITEM_VIEWTYPE_TEXTMSG_BYOTHER:// 別人的文字訊息
return new TextMsgByOtherViewHolder(inflater);
case ITEM_VIEWTYPE_PIC_MSG_ME:// 自己的圖片訊息
return new PicMsgByMeViewHolder(inflater);
case ITEM_VIEWTYPE_PIC_MSG_OTHER:// 別人的圖片訊息
return new PicMsgByOtherViewHolder(inflater);
case ITEM_VIEWTYPE_AUDIO_MSG_ME:// 自己的語音訊息
...
}
複製程式碼
kotlin中對某一欄位型別篩選的關鍵字when,但它不屬於語句,它屬於表示式,真的是表示式哦。
fun getWeekInfo(day: Int): String {
//因為是函式 所以可以直接使用在return後面
return when (day) {
1, 2, 3, 4, 5 -> "工作日"
6, 7 -> "週末"
else -> "unknow"
}
}
在Kotlin中幾乎一切都是表示式,所以上面更有簡化版:
fun getWeekInfo(day: Int) = when (day) {
1, 2, 3, 4, 5 -> "工作日"
6, 7 -> "週末"
else -> "unknow" //與java的default不能,else必須寫,除非密封類(sealed class)可以不寫,後面會詳細講解。
}
複製程式碼
難道僅僅這些就夠了,錯,還有厲害的!前方高能。
fun getWeekInfo(student: Student) = when {
student.name.equals("馬雲") -> "考什麼試!"
student.grade >= 60 -> "Pass"
student.grade < 60 -> "fail"
student is Student -> "is 相當於 instanceof"
else -> "unknow"
}
1. when可以不一定帶引數,就使用方法引數
2. when中的條件不需要同一種方式判斷,只需要返回boolean型別
複製程式碼
2.4 迴圈結構
語言 | 次數迭代 | 物件池迭代 |
---|---|---|
java | 支援 | 支援 |
kotlin | 強支援 | 支援 |
java版本兩種迴圈方式的寫法
for (int i = 0; i < 10; i++) {
//TODO
}
for (String arg : args) {
//TODO
}
複製程式碼
kotlin版本,與python寫法相似
// 次數迭代
for(i in 1..10){ //[1,10]區間往上遍歷
print(i)
}
for(i in 10 downTo 1){ // [1,10]區間往下遍歷
print(i)
}
for(i in 1 until 10){ // [1,10)區間往上遍歷
print(i)
}
for(i in 1..10 step 2){ //設定步數往上走
print(i)
}
//物件池迭代
for (arg in args){
print(arg)
}
複製程式碼
2.5 引入靜態方法
引入靜態方法的目的:
java | Kotlin |
---|---|
工具方法 | 類的擴充套件 |
引入方式:
- java: import static com.meelive.ingkee.base.utils.guava.Preconditions.checkArgument;
- Kotlin: import com.inke.utils.showToast
靜態方法的寫法
- java:純工具類的寫法
- kotlin:
fun Context.showToast(message: String) : Toast {
var toast : Toast = Toast.makeText(this,message,Toast.LENGTH_SHORT)
toast.setGravity(Gravity.CENTER,0,0)
toast.show()
return toast
}
作為Context類的擴充方法,在Activity,Service中,可以直接使用,物件導向的封裝性顯得更加的嚴實(個人看法)。
複製程式碼
2.6 型別轉換
java中對於型別的轉化,寫的特別想吐的語句如下:
Person p = new Person();
if(p instanceof Student){
Student student = (Student) p;
String id = student.uid;
}
複製程式碼
然後看看Kotlin的模仿版:
val p = Person()
if (p is Student) {
val student = p as Student
var uid = student.uid
}
複製程式碼
瞪大眼睛看看簡化版,找不同的時間到了!
val p = Person()
if (p is Student) {
var uid = p.uid
}
is 進行判斷後,Person類例項就已經裝成Student類例項了,這就是Kotlin的智慧型別轉化。
複製程式碼
2.7 單例
- java版單例(懶漢式和餓漢式),懶漢式如下:
public static RoomManager ins() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final RoomManager INSTANCE = new RoomManager();
}
複製程式碼
- kotlin版
class AppManager private constructor(var info: String) {
companion object {
private var INSTANCE: AppManager? = null
fun getInstance(info: String): AppManager {
if (INSTANCE == null) {
synchronized(AppManager::class.java) {
if (INSTANCE == null) {
INSTANCE = AppManager(info)
}
}
}
return INSTANCE!!
}
}
fun getPhoneInfo(arg: String): String? {
return null
}
}
複製程式碼
如果是無參單例,有更為簡單的寫法
/**
* Created by YangLang on 2017/7/29.
*/
object AppManager {
fun getPhoneInfo(arg: String): String? {
return null
}
}
複製程式碼
2.8 正規表示式
- java的寫法
String info = "12,34,56.789";
String[] split = info.split("[,.]");
for (String s : split) {
System.out.println(s);
}
java預設使用正則去切割
複製程式碼
- Kotlin寫法
var info = "12,34,56.789"
for (s in info.split(",",".")) {
println(s)
}
kotlin 為了程式設計更加口頭化,生活化,預設不使用正則,上面寫法的理解是,把info用","或者"."進行分割。
//進一步說明
info.split(".")
1. 在java中輸出為空,因為.在正則規則中為統配符
2. 在kotlin中輸出為12,34,56和789,因為Kotlin預設不使用正則,所以.只是一個普通的字元。
複製程式碼
2.9 巢狀類和內部類
說明: 巢狀類不含有外部類的引用,內部類擁有外部類的引用,正因為這樣,也可以造成Activity沒有釋放,而記憶體洩漏問題。
- java
public class Outter {
//包含Outter類的引用,可以使用Outter的方法
class Intter{}
// 不包含Outter類引用,不能使用Outter的方法
static class NotIntter{}
}
複製程式碼
- Kotlin
class Outter {
inner class Intter
class NotIntter
}
複製程式碼
java預設為內部類,Kotlin預設為巢狀類,一定程度的避免造成記憶體洩漏無意識寫法。
2.10 密封類
密封類的概念和C#一樣,密封類可以被繼承,但本身不能被例項化。所有的子類必須在同一檔案中,相當於把密封類的子類都封裝在一塊,可以配合when使用。
sealed class BaseModel{}
class FirstModel:BaseModel(){}
class SecondModel:BaseModel(){}
複製程式碼
fun justDo(baseModel: BaseModel) = when(baseModel){
is FirstModel -> ""
is SecondModel -> ""
}
沒有使用else,但是必須要寫全baseModel的子類,如果編寫期間新建立一個類繼承BaseModel,此處when直接報錯,密封類的這種特性,保證編寫when表示式時,可以無一疏漏的考慮到每一種情況,約束程式碼的編寫。
複製程式碼
2.11 代理
代理方式\語言 | java | Kotlin |
---|---|---|
靜態代理 | √ | √ |
動態代理 | √ | √ |
屬性代理 | × | √ |
標準代理 | × | √ |
這裡不講java的設計模式,主要關注Kotlin的代理模式,從類代理、屬性代理、標準代理三個方面
- 類代理
假如我們需要建立AnimatorListener的例項,Java中最常見的方式可能是匿名內部類,我們需要過載它的四個方法,雖然我們不都用,這樣程式碼就顯得多餘。Kotlin中就可以用該介面得一個實現類代為過載,並處理自己的TODO。
class MyAnimatorListener(var animatorListener: Animator.AnimatorListener):Animator.AnimatorListener by animatorListener{
override fun onAnimationEnd(animation: Animator?) {
//TODO 可以進行預處理
animatorListener.onAnimationEnd(animation)
}
}
介面Animator.AnimatorListener需要的方法由傳入引數animatorListener實現了,該MyAnimatorListener也通過關鍵字by,將需要實現的方法讓animatorListener代為實現,過載方法後可以做預處理操作。
複製程式碼
- 屬性代理
當使用val修飾屬性時,表明屬性為不可修改,代理類繼承ReadOnlyProperty<被代理類,型別>類
當使用var修飾時,屬性可修改,代理類繼承ReadWriteProperty<Water,Boolean>
/**
* Created by YangLang on 2017/7/29.
*/
fun main(args: Array<String>) {
var p = Person("inke", 21)
println("是否已經成人: ${p.isAdult}")
}
class Person(var name: String, var age: Int) {
var isAdult: Boolean by isAdultProperty()
}
class isAdultProperty : ReadWriteProperty<Person, Boolean> {
override fun setValue(thisRef: Person, property: KProperty<*>, value: Boolean) {
}
override fun getValue(thisRef: Person, property: KProperty<*>): Boolean {
return thisRef.age > 18
}
}
複製程式碼
- 標準代理
Kotlin標準代理有lazy(),observable(),notNull().. 所有這樣Kotlin自建代理都在delegates類下,主要講lazy(懶載入) 在java中使用supplier和suppliers進行懶載入(延時求值),在kotlin中通過方法lazy直接延時求值,更快更強!!
class InfoConfig {
val info: String by lazy {
"Inke info"
}
}
fun main(args: Array<String>) {
print(InfoConfig().info)
}
第一次獲取info值時,會通過lazy函式傳入的函式引數進行求值,以後再獲取該值時,直接返回,所以懶載入可進行復雜計算。
複製程式碼
2.12 屬性賦初值問題
屬性不管申明為val還是var都必須賦初值,我們只能被迫寫成 var info:String? = null。但是實際情況下有些屬性不允許賦予無意義的初值如null,我總結有三種方式處理(可能不全面):
- lateinit var info: String(只能用在var型別上)
- class Student(var name: String, var age: Int){ val isAudit = age > 18} (可用在var或者val上,但只是在初始化時賦一次值~)
- val info: String by lazy { "Inke info" } (只能用在val型別上)
2.13 lambda的概要(物件引用,陣列用法)
用lambda表示式建立函式“變形體”
var add = { x: Int, y: Int -> x + y }
print(add(1,2))
複製程式碼
lambda縮寫演史
// 1. 匿名內部類
rb_home.setOnClickListener(object:View.OnClickListener{
override fun onClick(v: View?) {
}
})
// 2. 如果內部類中只有一個方法,縮寫成lambda表示式
rb_home.setOnClickListener({v -> Unit})
// 3. 如果函式引數最後一個引數為lambda表示式,可以提取放在函式引數括號外面
rb_home.setOnClickListener(){v -> Unit}
// 4. 如果函式引數括號為空,可以省略
rb_home.setOnClickListener{v -> Unit}
// 5. 如果lambda表示式形參咩有使用,可以省略
rb_home.setOnClickListener{Unit} <==> rb_home.setOnClickListener{//TODO}
複製程式碼
可以根據以上的省略原則可以定義我們自己的的函式。如在java中做版本的判斷,我們會寫:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
這樣寫起來不流程,而且不易複用
複製程式碼
Kotlin上面可以這樣定義函式
inline fun UP_VERSIONJELLY_BEAN(function: () -> Unit){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEA){
function()
}
}
在使用時,可以毫不猶豫的寫成這樣:
UP_VERSIONJELLY_BEAN{
//TODO
}
複製程式碼
2.14 反射中優質提升
public class Test<T> {
public Test() {}
}
複製程式碼
以上的泛型寫法,在java中執行時由於型別擦除的原因,無法確定T的型別,很難獲取該物件的位元組碼物件,不過在早兩天看到Guava支援執行時獲取泛型型別,通過TypeToken工具類。但是在Kotlin可以輕鬆解決這樣問題,通過標註關鍵字reified。
- TypeToken
- reified關鍵字使用
inline fun <reified T: Activity> Activity.newIntent() {
val intent = Intent(this, T::class.java)
startActivity(intent)
}
複製程式碼