什麼是Kotlin
Kotlin
,它是JetBrains
開發的基於JVM的物件導向的語言。2017年的時候被Google
推薦Android
的官方語言,同時Android studio 3.0
正式支援這門語言,在這個編譯器上建立一個Kotlin
專案,非常方便,甚至可以Java
轉為Kotlin
。
我主要是在通過實現自定義View
過程中,說一下Kotlin
與Java
的異同,其實兩者非常相似
對Kotlin
語法不是太瞭解的,可以先去看看它的官方翻譯文件
以Barchart-Kotlin開始說起
Barchart-Kotlin是我用Kotlin
寫的一個簡易靈活的柱狀相簿,喜歡的可以點個star!
1.類的屬性
在一個類裡面我們需要定義一些屬性來儲存資料和狀態
我們先來看看Java
程式碼,在BarChartView
定義了一些屬性
private SpeedLinearLayoutManger mLayoutManager;
private BarChartAdapter mAdapter;
private ItemOnClickListener mClickListener;
private int mDefaultWidth = 150;
複製程式碼
然後我們再看看Kotlin
是怎麼定義這些屬性的,下面的是Kotlin
程式碼
private lateinit var mLayoutManager: SpeedLinearLayoutManger
private lateinit var mAdapter: BarChartAdapter
private var mClickListener: ItemOnClickListener? = null
private val mDefaultWidth = 150
複製程式碼
你會發現不一樣的宣告方式,但重要的是var
和val
這兩個關鍵字
var
代表的是可變的變數,相當於現在Java
宣告變數的方式
val
代表的是不可變的變數,初始化後不能再修改,相當於加了final
關鍵字的變數
而且在Kotlin
中屬性是需要初始化的,沒有值的時候你可以賦值null
,不然編譯會報錯。加上?
的意思是你不確定是否是這個型別,或者說是否為null
。如果覺得實在是不方便你的使用邏輯,你可以使用這兩種方式延遲初始化。
懶初始化 by lazy
lazy
是指推遲一個變數的初始化時機,只有在使用的時候才會去例項化它。適用於一個變數直到使用時才需要被初始化。在我這個專案裡面沒有使用by lazy
,它大致的用法是這樣的
val data: Data by lazy {
Data(number,string)
}
複製程式碼
延遲初始化 lateinit
lateinit
是指你保證接下來在使用之前或者使用的時候會例項化它,不然你就會crash掉,這不就跟我們使用Java
屬性的方式一樣麼。。。它適用於一些view
和必須用到資料結構的初始化,我覺得還是謹慎使用比較好。
2.空安全
Kotlin
可以說是分了兩個大型別,可空型別和不可空型別,這樣做的原因是它希望在編譯階段就把空指標這問題顯式的檢測出來,把問題留在了編譯階段,讓程式更加健壯。它通過?
來表達可為空。
mClickListener?.invoke1(position)
mClickListener?.invoke2(position)
mClickListener?.invoke3(position)
複製程式碼
如果mClickListener
為null
的話,後面的語句是不會執行的。而且Kotlin
提供了更加簡潔的操作符let
mListener?.let {
it.invoke1(position)
it.invoke2(position)
it.invoke3(position)
}
複製程式碼
只有在非空的情況下才執行let
裡面的操作,非常簡潔。
3.構造器
通過簡單瞭解之後,我們開始寫一個自定義View,我們需要繼承View
,Java
的實現方式是這樣的
public class BarChart extends View {
public BarChart(Context context) {
super(context);
}
public BarChart(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
複製程式碼
用Kotlin
你可以實現的更簡潔
class BarChart @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
private val mContext: Context = context
init { }
複製程式碼
你可以在init
程式碼塊裡面獲得建構函式的傳參,當然你也可以直接在宣告屬性的時候獲得,@JvmOverloads
如果你沒有加上這個註解,它只能過載相匹配的的建構函式,而不是全部。
而且可能你也發現了,你可以在傳參裡面初始化,這相對於Java
來說,靈活太多
fun shadow(width:Int=100,height:Int = 180){ }
//你可以這麼使用
shadow()
shadow(140)
shadow(140,200)
複製程式碼
4.UI佈局
一般用我們創造view
的佈局是xml
,Kotlin
也是支援的,但是它更推薦你使用Anko
的佈局方式。
那是什麼是Anko
呢,Anko
是JetBrains
開發的一個強大的庫,它主要的目的是用來替代以前xml
的方式來使用程式碼生成UI佈局的,它包含了很多的非常有幫助的函式和屬性來避免讓你寫很多的模版程式碼。
有興趣的你可以去看看它的原始碼與更多使用方式 -- Anko
首先你要在Gradle
裡新增Anko
的引用
// Anko Layouts
compile "org.jetbrains.anko:anko-recyclerview-v7:$anko_version"
compile "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version"
compile "org.jetbrains.anko:anko-sdk25:$anko_version"
compile "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
// Coroutine listeners for Anko Layouts
compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
compile "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"
複製程式碼
然後你可以在程式碼裡寫UI佈局的程式碼了,就是用Kotlin
程式碼替代xml
private fun createView(attrs: AttributeSet? = null, defStyleAttr: Int = 0) {
var height = dip(150)
attrs?.let {
val typeArray = mContext.obtainStyledAttributes(it, R.styleable.BarChartView, defStyleAttr, 0)
height = typeArray.getDimension(R.styleable.BarChartView_chart_height, dip(150).toFloat()).toInt()
typeArray.recycle()
}
verticalLayout {
lparams(width = matchParent, height = matchParent)
frameLayout {
lparams(width = matchParent, height = wrapContent)
mLineView = view {
backgroundColor = R.color.gray_light
}.lparams(width = matchParent, height = dip(0.5f)) {
gravity = Gravity.BOTTOM
bottomMargin = dip(9)
}
mBarView = recyclerView {
lparams(width = matchParent, height = height)
}
}
mDateView = relativeLayout {
lparams(width = matchParent, height = wrapContent) {
leftPadding = dip(10)
rightPadding = dip(10)
}
mLeftTv = textView {
textSize = 15f
}
mRightTv = textView {
textSize = 15f
}.lparams(width = wrapContent, height = wrapContent) {
alignParentRight()
}
}
}
}
複製程式碼
可以從程式碼裡看見verticalLayout
(其實就是LinearLayout
的vertical
模式)包裹了frameLayout
和relativeLayout
,裡面又有各自的子view
,而且你會發現xml
有的屬性這裡也有,呼叫起來非常的簡潔明瞭,熟練xml
的來寫這個,我覺得上手應該會很快,它的缺點就是沒有預覽效果,以及實現複雜的view結構的時候會比較繁瑣,考驗盲寫的功力了。。。。
5.擴充套件函式
擴充套件函式是Kotlin非常方便實用的一個功能,它可以讓我們隨意的擴充套件SDK的庫,你如果覺得SDK的api不夠用,這個時候你可以用擴充套件函式完全去自定義。
例如你需要這樣來獲取顏色,每次你都需要一個上下文context
mColor = ContextCompat.getColor(mContext, R.color.primary)
複製程式碼
那你可以通過擴充套件Context
這個SDK的類來實現更方便的使用
fun Context.color(colorRes: Int) = ContextCompat.getColor(this, colorRes)
fun View.color(colorRes: Int) = context.color(colorRes)
複製程式碼
而且為了更方便在View
裡面使用,又擴充套件了View
。在第一個方法裡面可以發現getColor
所需要的this
上下文,就是Context
。同樣,View
裡面的context
是getContext()
所得到,也就是View
裡面本身具有的公有方法。
這其實是很驚豔的功能
那我們可以想一想為啥可以這樣做,我們知道的是Kotlin
和Java
都是在Jvm
上執行的,既然都是編譯成class
位元組碼,那我們是不是可以通過位元組碼來了解一些事情。
通過Android studio 3.0
上的Tools
的工具Show Kotlin Bytecode
,可以將剛才的擴充套件函式的程式碼編譯成位元組碼
// access flags 0x19
public final static color(Landroid/content/Context;I)I
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "$receiver"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 12 L1
ALOAD 0
ILOAD 1
INVOKESTATIC android/support/v4/content/ContextCompat.getColor (Landroid/content/Context;I)I
IRETURN
L2
LOCALVARIABLE $receiver Landroid/content/Context; L0 L2 0
LOCALVARIABLE colorRes I L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x19
public final static color(Landroid/view/View;I)I
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "$receiver"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 14 L1
ALOAD 0
INVOKEVIRTUAL android/view/View.getContext ()Landroid/content/Context;
DUP
LDC "context"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
ILOAD 1
INVOKESTATIC shadow/barchart/ExtensionsKt.color (Landroid/content/Context;I)I
IRETURN
L2
LOCALVARIABLE $receiver Landroid/view/View; L0 L2 0
LOCALVARIABLE colorRes I L0 L2 1
MAXSTACK = 3
MAXLOCALS = 2
複製程式碼
這就是編譯後的位元組碼,看不懂是吧,我也看不懂。。但是我們可以反編譯啊,生成Java
程式碼
public static final int color(@NotNull Context $receiver, int colorRes) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return ContextCompat.getColor($receiver, colorRes);
}
public static final int color(@NotNull View $receiver, int colorRes) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
Context var10000 = $receiver.getContext();
Intrinsics.checkExpressionValueIsNotNull(var10000, "context");
return color(var10000, colorRes);
}
複製程式碼
通過反編譯我們可以知道這是個靜態函式,Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
這個函式只起到了判空的作用,真正的程式碼是return ContextCompat.getColor($receiver, colorRes);
這個不就是我們剛剛用的Java
程式碼嘛。
重點是$receiver
接收的物件,接收的是Context
例項,這樣的話就可以呼叫這個類的所有公有方法和公有屬性,而且它是靜態函式,它可以通過類直接呼叫。所以擴充套件函式的實現只不過是加了一個需要當前物件的靜態方法,呼叫的時候傳入一個當前物件而已。
我們剛剛用到了反編譯,因為我們知道Kotlin
和Java
的生成位元組碼是一樣的,那我們可以瞭解一下Kotlin
的編譯過程,它跟Java
的區別是什麼。可以看一下這篇文章Kotlin編譯過程分析
通過這篇文章你可以瞭解到Kotlin
在編譯過程中,與Java
是大致相同的,只是在最後生成目的碼的時候做了很多類似於封裝的事情,生成相同的語法結構,Kotlin
將我們本來在程式碼層做的一些封裝工作轉移到了編譯後端階段。那我們可不可以在學習Kotlin
的時候去這樣理解,其實Kotlin
是一種封裝了Java
的強大的語法糖,Java
做不到的事情,Kotlin
其實也做不到,例如物件只能訪問公有屬性。
6.資料類
在Kotlin
中你要實現資料類是非常簡單的,並不需要手動加上get/set
方法
data class BarItem(
private val barData: BarData,
var select: Boolean = false) {
fun getData(): Double {
return barData.getData()
}
fun getTag(): String {
return barData.getTag()
}
}
複製程式碼
在這個類裡面你會發現,我還宣告瞭兩個方法,我需要的是BarData
裡的資料,但又不僅僅只需要這個資料,所以我宣告瞭一個類來封裝它,其實這個相當於裝飾者模式了。Kotlin
有更好的方式實現這個模式
data class BarItem(
private val barData: BarData,
var select: Boolean = false) : BarData by barData
複製程式碼
7.when
在BarChartView
裡用到一個與switch
語法類似的語句
mSelectPosition = when (mStyle) {
ScrollStyle.DEFAULT -> mDataList.size - 1
ScrollStyle.START -> 0
ScrollStyle.NONE -> -1
ScrollStyle.CUSTOM -> mSelectPosition
else -> { }
}
複製程式碼
它是起到了跟switch
一樣的作用,並且更強大,因為它是表示式,所以是有返回值的,在Kotlin
中控制流大都是表示式,都是可以有返回值的。
8.集合
Kotlin
是區分可變集合和不可變集合的,它給你提供這兩種選擇。
//不可變
Set<out T>
Map<K, out V>
List<out T>
//可變
MutableSet<T>
MutableMap<K, V>
MutableList<T>
複製程式碼
不可變的集合提供只讀屬性,例如size
,get
等,Kotlin
不提供專門的語法結構建立list
或者set
,是用標準庫獲取的,我們可以看一下它的原始碼是怎樣實現。
/**
* Returns an immutable list containing only the specified object [element].
* The returned list is serializable.
* @sample samples.collections.Collections.Lists.singletonReadOnlyList
*/
@JvmVersion
public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)
/**
* Returns an empty new [MutableList].
* @sample samples.collections.Collections.Lists.emptyMutableList
*/
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
複製程式碼
從原始碼可以看見這是Java
的java.util.Collections.singletonList
和ArrayList
,這就可以理解為啥不可變和可變的了。。。
總結
Kotlin
相對於Java
,更像是封裝了Java
的強大語法糖,使用了更簡潔的語法提高了生產力。