Kotlin項目實踐指南(上)

關於做者html

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。java

文章目錄android

  • 一 表達式與語句
  • 二 函數和變量
  • 三 類、對象和接口
  • 四 集合
  • 五 註解與泛型
  • 六 類型系統
  • 七 Lambda與高階函數
  • 八 DSL構建
  • 項目實踐

注:文章中"舉例"字樣表明所舉的例子,"區別"字樣代碼Kotlin與Java不一樣的地方,git

更多文章請參見文章目錄程序員

本篇文章是本系列文章的開篇文章,主要介紹Java中的編程概念(語句、函數、類等)是如何映射到Kotlin的代碼中的。本文的目的在於幫助你們快速理解Java裏的語法在Kotlin中如何實現,以及
它們之間有什麼區別。github

Kotlin是一種在Java虛擬機上運行的靜態類型編程語言。express

  • Kotlin是靜態語言而且支持類型推導,容許維護正確性與性能的同時保證了源代碼的簡潔性。
  • Kotlin支持函數式和麪向對象兩種編程風格,經過頭等函數使得更高級別的抽象成爲了可能,經過支持不可變值簡化了測試和多線程併發。
  • Kotlin與Java具備良好的互操做性,Kotlin能夠去使用Java的API、繼承Java的類型、實現Java的接口,一樣的,Java也能夠像調用其餘Java代碼那樣調用Kotlin,這爲語言混用打下了堅實的基礎。

寫在前面的總結,從Java過分到Kotlin,有哪些被替換的地方。🤔apache

  • 用Kotlin的頂層函數和頂層屬性代替Java裏一堆堆的Utils工具類的寫法
  • 用Kotlin的data數據類代替Java裏的Bean類裏的大量模板代碼
  • 用object對象代替Java裏的匿名內部類
  • 用companion object代替Java裏的靜態方法和靜態變量的調用,這種用來須要訪問類的private成員的狀況,其餘狀況仍是能夠用頂層函數和頂層屬性。
  • 類和方法默認都是final的
  • Java裏的Builder寫法很好的避免了臃腫的構造函數列表,而在Kotlin首先構造函數參數能夠有默認值,另外apply擴展原生支持Builder模式。

Android Stduio 3.0已經正式支持Kotlin,若是你是第一次接觸Kotlin,你能夠看一下下面3篇文章,跑一下Demo,體會一下Kotlin。編程

一 表達式與運算符

1.1 控制表達式

Kotlin常見的控制語句有:bash

  • 條件表達式if、when
  • 異常處理try/catch、throw
  • 循環表達式for/in、while與do/while

Kotlin的控制語句和Java有所區別🤔,它們具有更強大的功能,在介紹控制語句以前,咱們要先理解兩條規則:

在Kotlin中,if是表達式,而在Java中if是語句。

  • 表達式:表達式有值,而且能夠做爲另外一個表達式的一部分使用。
  • 語句:語句是包圍着它的代碼塊的底層元素,並且它沒有值。

關於Kotlin的表達式,有兩條規則須要咱們理解:

  • 出來循環語句外,其餘表達式是有有返回值的
  • 在if、when、try等代碼塊中,最後的表達式就是要返回的結果

在Java中全部的控制語句都是表達式,而在Kotlin中,除了循環(for、do與do/while)是語句外,其餘大部分控制結構都是表達式,都有值。
而偏偏在Java中是表達式的賦值操做(a = 1)在Kotlin中倒是語句,這樣有助於避免比較操做(==)和賦值(=)操做之間的混淆。

有了上面兩條規則,咱們不難理解如下例子。

舉例

val a = 1
val b = 2

val ifResult = if (a > b) a else b

val whenResult = when (a > b) {
    true -> a
    else -> b
}

val number = try {
    Integer.parseInt("1")
} catch (e: NumberFormatException) {
    null
}

for (i: Int in 1..100){
    Logger.d(i)
}

for((key, value) in map){
````//使用key,value
}

//變量 a 和 b 的值取自對集合中的元素上調用 component1() 和 component2() 的返回值。
for ((a, b) in collection) { …… }複製代碼

1.2 操做符

  • ==:調用的是對象的equals()方法進行想等性比較。
  • ===:進行對象引用的比較,這和Java的==類似。
  • is:相似於Java的Instanceof,用於類型檢查,並且當檢查類型成功後,會自動將變量轉換爲這個類型。

Kotlin提供了很是多相似於RxJava的操做符,爲平常的開發提供了極大的方便。更多操做符能夠參考操做符列表

1.3 中綴調用

函數還能夠用中綴表示法調用,當它們是成員函數或擴展函數;它們只有一個參數;它們用 infix 關鍵字標註。

/** * 用infix定義一箇中綴函數 */
infix fun Int.shl(x: Int) {
    println("I am a infix method")
}

/** * 中綴調用 */
fun useInfixMethod() {
    1 shl 2
}複製代碼

1.4 解構聲明

解構聲明是將一個對象解構成多個變量,一個解構聲明會同時建立多個變量。

要實現解構聲明,咱們須要添加componentN函數,componentN函數指定要返回的域,並使用operator標記

class Teacher(val name: String, val age: Int) : Person(name, age), IBehavior {

    override fun talk() {
        super<Person>.talk()
        super<IBehavior>.talk()
    }

    operator fun component1(): Any {
        return name
    }

    operator fun component2(): Any {
        return age
    }
}複製代碼

而後咱們就可使用解構聲明批量的建立變量,解構賦值是按照omponentN函數的順序來執行的。

val teacher = Teacher("LiLei", 20)
val (name, age) = teacher

Logger.d("name: " + name)
Logger.d("age: " + age)複製代碼

若是咱們在解構聲明中不須要某個變量,用下劃線代替便可。

val (_, age) = teacher複製代碼

二 函數和變量

函數的聲明以fun關鍵字開始,函數名稱緊跟其後,而後是形參列表,形參列表後面是返回值,若是沒有指定返回值,默認的返回值是Unit。

舉例

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}複製代碼

若是函數中只有一個表達式,能夠將這個表達式做爲完整的函數體,稱爲表達式函數體,例如:

fun max(a: Int, b: Int): Int = if (a > b) a else b複製代碼

Kotlin變量的聲明從關鍵字var與val開始,Kotlin會自動進行類型推導,所以你無需顯式的聲明類型。

  • val:不可變引用,變量在初始化以後不能再被修改,也就是val引用自身不能再被修改,可是它指向的對象是可變的。
  • var:可變引用,變量在初始化以後能夠再被修改,var引用自身能夠被修改,可是它的類型不能夠改變。

舉例

val name =  "Nick"
var age = 10複製代碼

默認狀況下,應該儘量的使用val關鍵字來聲明變量,僅在必要的時候使用var,使用不可變引用,不能夠對象以及無反作用的函數可讓你的代碼更加接近函數式編程風格。

注:這裏提一下Kotlin的package結構,和Java同樣Kotlin也有包的概念,可是Kotlin對源碼的文件組織更爲自由,多個類能夠在同一個文件中,文件的名字和目錄也能夠隨意選擇,可是咱們仍是
會按照Java的目錄佈局去組織源碼,這是一種良好的實踐,但在Kotlin中不少類都很是小,例如數據類,這種狀況下,咱們一般把它放在一個文件裏。

2.1 默認參數值

在Java中定義函數時,有些參數有默認值,這就致使會定義大量的重載函數(參考Thread的構造函數),而在Kotlin中能夠利用參數的默認值來避免這個問題。

/** * 函數能夠帶默認參數,@JvmOverloads註解能夠在編譯時生成重載函數 */
@JvmOverloads
fun maxWithDefault(a: Int = 1, b: Int = 2): Int {
    return if (a > b) a else b
}複製代碼

注:可是Java中沒有參數默認值的概念,爲了方便Java調用Kotlin,能夠用@JvmOverloads去註解它,@JvmOverloads註解能夠在編譯時生成重載函數。

2.2 頂層函數和屬性

回想一下,咱們的項目中是否是有不少Utils類,它們一種靜態的方式被咱們調用。這些Utils類自己沒有實際意義,只是做爲函數的容器,而在Kotlin中咱們無需
這麼作,咱們能夠把這些函數和屬性直接定義在代碼文件的頂層,不用從屬任何類,而後用一種導包的方式使用它。

定義

/** * 頂層函數,能夠直接導包而後使用 */
val topProperty = "I am a top property"

/** * 頂層函數,能夠直接導包而後使用 */
fun topMethod(property: String) {
    println("I am a top method")
}

class Method {

}複製代碼

調用

import com.guoxiaoxing.kotlin.demo._02_method.topMethod
import com.guoxiaoxing.kotlin.demo._02_method.topProperty

object MethodClient {

    @JvmStatic
    fun main(args: Array<String>) {
        topMethod(topProperty)
    }
}複製代碼

2.3 擴展函數和屬性

Kotlin的一大優點就在於它能夠平滑的與Java進行集成,例如咱們想要用Kotlin爲Java裏的類添加一個方法,這時咱們不要去修改Java的源碼,而是可使用擴展函數與屬性。

  • 擴展函數能夠訪問被擴展類的public屬性與方法,不能夠訪問private、proteced的屬性與方法。
  • 擴展函數最終會被編譯成靜態函數,所以Java裏能夠用調用靜態方法的方式來調用它。
  • 擴展函數不能夠被重寫
  • 擴展函數若是和被擴展類的成員函數重名,會優先使用被擴展類的成員函數。
/** * 爲String類添加一個擴展屬性extensionProperty,這個String類能夠是Kotlin、Java甚至其餘任何JVM語言編寫的類 */
val String.extensionProperty
    get() = "I am a extension property"

/** * 爲String類添加一個擴展函數extensionMethod(),這個String類能夠是Kotlin、Java甚至其餘任何JVM語言編寫的類 */
fun String.extensionMethod() {
    println("I am a extension method")
}


/** * 使用擴展函數 */
fun useExtensionMethod() {
    val a = "string"
    a.extensionProperty
    a.extensionMethod()
}複製代碼

2.4 局部函數

好的代碼設計就是儘可能減小重複代碼,不少時候咱們會把重複的代碼提取成一個函數進行調用,Kotlin更進一步,它容許你在函數的內部定義函數進行調用。

/** * 爲了代碼的簡潔性,咱們還能夠定義局部函數 */
fun outerMethod() {
    fun innerMethod() {

    }
}複製代碼

三 類、對象和接口

Kotlin中類使用class關鍵聲明、接口使用Interface聲明,使用冒號(:)代替了extends與implements關鍵字,能夠實現多個接口,可是隻能繼承一個類。

Kotlin中也有和Java中類似的類與接口的概念,可是有所區別🤔

  • Kotlin中的類默認是public、final的,這樣就解決了Java中"脆弱的基類"的問題,若是咱們想要這個類能夠被繼承,須要使用open關鍵字標記這個類。
  • Kotlin中也有抽象類的概念,使用abstract關鍵字標記。
  • 接口能夠定義本身的成員變量,也能夠爲定義的方法提供默認的實現。

訪問修飾符

  • final:不能被重寫
  • open:能夠被重寫
  • bstract:必須被重寫
  • override:重寫父類或者接口中的方法或者變量

可見性

  • public:默認,全部地方可見
  • internal:模塊內可見,一個模塊指的是在一塊兒編譯的一組Kotlin文件(一個maven項目、一個gradle項目等),它實現了完整的模塊星封裝,而不像Java那樣,只有把代碼定義在相同的包名裏就能夠
    獲得訪問你的包私有聲明的權限。
  • protected:子類中可見,Java中能夠從同一個包中訪問protected成員,Kotlin完全規範了這個行爲,只能在子類中可見。
  • private:類中可見
open class BaseClass {

    open fun extendClass() {

    }
}

interface BaseInterface {

    val name: String

    fun implementInterface()
}

class Clasz(override val name: String) : BaseClass(), BaseInterface {

    override fun extendClass() {
        println("I override the BaseClass")
    }


    override fun implementInterface() {
        println("I implment the BaseInterface")
    }
}複製代碼

構造方法

Kotlin裏的構造方法分爲主構造函數和從構造方法,主構造函數是類頭的一部分,它跟在類名後,類初始化的代碼能夠放在init代碼塊裏完成。Kotlin也支持參數默認值,因此咱們無需像Java那樣提供多個重載的構造方法來提供參數默認值。

  • 如同Java同樣,Kotlin也會提供一個沒有任何參數的構造方法,這也是咱們在繼承類的時候會在類後面加一個空的括號,這表示調用父類的構造方法。
  • 若是類有一個主構造函數,每一個次構造函數須要委託給主構造函數, 能夠直接委託或者經過別的次構造函數間接委託。委託到同一個類的另外一個構造函數用 this 關鍵字便可。
class Clasz(override val name: String){

    /**************************************** 構造方法與初始化 *********************************************/

    init {
        //TODO 初始化類,在類建立時被調用
    }

    /** * 從構造函數 * * 若是類有一個主構造函數,每一個次構造函數須要委託給主構造函數, 能夠直接委託或者經過別 * 的次構造函數間接委託。委託到同一個類的另外一個構造函數用 this 關鍵字便可 */
    constructor(name: String, age: Int) : this(name) {
        println("I am a secondary constructor")
    }
}複製代碼

3.1 嵌套類與內部類

嵌套的類默認不是內部類,不持有外部類引用,也沒法使用外部類成員變量,若是想定義成內部類,須要用關鍵字inner聲明,內部類是持有外部類引用的,可使用
外部類成員變量。

class Clasz {

    private var outerProperty = "I am a outer property"

    /**************************************** 嵌套類與內部類 ***********************************************/

    /** * 嵌套類,不持有外部類引用,也沒法使用外部類成員變量 */
    class nestClass() {

        fun nestMethod() {
        }
    }

    /** * 內部類,持有外部類引用,可使用外部類成員變量 */
    inner class innerClass() {

        fun nestMethod() {
            outerProperty = "I cam use outerProperty"
        }
    }
}複製代碼

3.2 數據類

在Java中咱們會用到協議只保存數據的類,咱們呀爲它們寫大量重複的方法,在Kotlin只須要用用data關鍵字標記。

data class Model(val name: String)複製代碼

就這麼簡單的一行代碼,你將獲得:

  • equals()/hashCode()
  • toString() 格式是 "Model(name=John, age=42)
  • componentN() 函數 按聲明順序對應於全部屬性,
  • copy() 函數

爲了保證數據類的一致性,數據類必須知足:

  • 主構造函數須要至少有一個參數;
  • 主構造函數的全部參數須要標記爲 val 或 var;
  • 數據類不能是抽象、開放、密封或者內部的;
  • (在1.1以前)數據類只能實現接口。

3.3 object對象

Kotlin沒有static的概念,可是提供了object關鍵字,它表明了在建立一個類的同時並提供一個對象。

object關鍵字一般用在:

  • object表達式實現單例
  • object表達式用來代替Java的匿名內部類,注意匿名對象不是單例的,每次執行代碼,都會建立一個新的對象。
  • object對象也能夠繼承自類和接口
  • object對象會被編譯成靜態字段,若是使用Java調用,能夠經過objectName.INSTANCE的方式進行調用。
object Single {

}複製代碼
/** * 匿名內部類 */
view.setOnClickListener(object : View.OnClickListener{
        override fun onClick(v: View?) {
        }
    })複製代碼

另外還有一種companion object(伴生對象),伴生對象是一種聲明在類中的普通對象,它也能夠有本身的名字,實現一個接口或者有擴展函數和屬性。

Kotlin的伴生對象會被編譯成常見靜態字段,能夠經過ClassName.Companion來訪問它,若是伴生對象有名字,則用這個名字替換掉Companion。

class Singleton {

    /** * 單例 */
    companion object {
        val instance by lazy { Singleton() }
    }
}複製代碼

四 項目實踐

若是你尚未嘗試過Kotlin,Android Stduio 3.0已經正式支持Kotlin,若是你是第一次接觸Kotlin,你能夠看一下下面3篇文章,跑一下Demo,體會一下Kotlin。

4.1 findViewById

利用kotlin-android-extensions,咱們能夠直接使用xml文件裏的文件id,和findViewById說再見。

注:目前只能在Activity、Fragment、Adapter上使用。

依賴配置

apply plugin: 'kotlin-android-extensions'複製代碼

導入屬性

// 使用來自主代碼集的 R.layout.activity_main
import kotlinx.android.synthetic.main.activity_main.*

// 若是是Adapter則在後面多加個View R.layout.adapter_layout
import kotlinx.android.synthetic.main.adapter_layout.view.*

class MyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView.setText("Hello, world!")
        // 而不是 findViewById(R.id.textView) as TextView
    }
}複製代碼

更多細節:kotlinlang.org/docs/tutori…

4.2 Anko

Anke是一個Android開發的工具庫,它能夠經過Anko DSL代碼書寫代替XML來進行UI佈局。這是一個很是有效的特性,固然它也有許多有用的functions,能夠大大提高咱們的開發效率。

例如:

  • find()代替findViewById()
  • longToast()代替原生Toast()
  • startActivity(vararg params: Pair代替原生startActivity()
  • Anko SQLite簡化原生的SQLite的使用

更多細節:github.com/Kotlin/anko

以上就是本篇文章上半部分的所有內容,下半部分咱們會來深刻探討泛型、註解、反射、Kotlin類型系統、Lambda與高階函數以及DSL構建等方面的內容。

--------------------------------------我是Kotlin項目的分割線😆------------------------------------------

項目地址:github.com/guoxiaoxing…

Phoenix Phoenix

功能介紹

Jitpack version
Jitpack version

License
License

Android平臺上圖片/視頻選擇,編輯和壓縮的一站式解決方案。

圖片/視頻的選擇,編輯和壓縮是業務中的常見需求,Phoenix完整的實現了這些功能,並提供了優雅的調用方式。Phoenix的核心功能基於Kotlin實現,外層接口基於Java實現,方便Kotlin與Java雙方的調用。

特色

  • 功能相互獨立,各個功能的實現依賴於約定的接口,彼此互不依賴,開發者沒必要爲了引入某一個功能而帶入一堆依賴。
  • 高度的UI定製性,內置四種配色方案,開發者也能夠經過簡單的style文件的簡單配置來定製本身的UI。
  • 調用的便利性,開啓某個功能只須要調用enableXXX(true)方法,結果統一在MediaEntity裏獲取。
  • RxJava良好的支持性,每一個功能都提供了同步與異步兩種實現,便於開發者利用RxJava進行功能的組合與嵌套。
  • 良好的版本兼容性,運行時權限等內容都作了兼容性處理。




功能

  • 拍照
  • 圖片選擇
  • 圖片預覽
  • 圖片壓縮
  • 圖片標記、貼圖、塗抹與裁剪
  • 視頻選擇
  • 視頻預覽
  • 視頻壓縮






主題

  • 默認主題
  • 橙色主圖
  • 紅色主題
  • 藍色主題






快遞開始

添加依賴

在項目根目錄build.gradle文件裏添加

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}複製代碼

添加依賴

//圖片/視頻選擇、拍照、圖片/視頻預覽
compile 'com.github.guoxiaoxing.phoenix:phoenix-ui:0.0.11'

//選填 - 圖片壓縮,開啓功能:Phoenix.with().enableCompress(true),獲取結果:MediaEntity.getCompressPath()
compile 'com.github.guoxiaoxing.phoenix:phoenix-compress-picture:0.0.11'

//選填 - 視頻壓縮,開啓功能:Phoenix.with().enableCompress(true),獲取結果:MediaEntity.getCompressPath()
compile 'com.github.guoxiaoxing.phoenix:phoenix-compress-video:0.0.11'複製代碼

調用功能

Phoenix.with()
        .theme(PhoenixOption.THEME_DEFAULT)// 主題
        .fileType(MimeType.ofAll())//顯示的文件類型圖片、視頻、圖片和視頻
        .maxPickNumber(10)// 最大選擇數量
        .minPickNumber(0)// 最小選擇數量
        .spanCount(4)// 每行顯示個數
        .pickMode(PhoenixConstant.MULTIPLE)// 多選/單選
        .enablePreview(true)// 是否開啓預覽
        .enableCamera(true)// 是否開啓拍照
        .enableAnimation(true)// 選擇界面圖片點擊效果
        .enableCompress(true)// 是否開啓壓縮
        .thumbnailHeight(160)// 選擇界面圖片高度
        .thumbnailWidth(160)// 選擇界面圖片寬度
        .enableClickSound(true)//ƒ 是否開啓點擊聲音
        .pickedMediaList(pickList)// 已選圖片數據
        .videoSecond(0)//顯示多少秒之內的視頻
        .onPickerListener(new OnPickerListener() {
            @Override
            public void onPickSuccess(List<MediaEntity> pickList) {
                adapter.setList(pickList);
                adapter.notifyDataSetChanged();
            }

            @Override
            public void onPickFailed(String errorMessage) {

            }
        }).start(MainActivity.this, PhoenixOption.TYPE_PICK_MEDIA);複製代碼

最後的start()方法用來完成啓動某項功能,根據type不一樣啓動不一樣的功能,具體含義以下:

//功能 - 選擇圖片/視頻/音頻
public static final int TYPE_PICK_MEDIA = 0x000001;
//功能 - 拍照
public static final int TYPE_TAKE_PICTURE = 0x000002;
//功能 - 預覽
public static final int TYPE_BROWSER_PICTURE = 0x000003;複製代碼

更新日誌

貢獻代碼

歡迎加入改進本項目

License

Copyright 2017 Guoxiaoxing

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.複製代碼
相關文章
相關標籤/搜索