「Kotlin初探 | 掘金技術徵文 」

Code4Android發表於2019-03-04

示例原始碼傳送門

前言

Kotlin是一種在 Java虛擬機器上執行的靜態型別程式語言,它主要是由俄羅斯聖彼得堡的JetBrains開發團隊所發展出來的程式語言。該語言有幾個優勢

  1. 簡潔
    它大大減少你需要寫的樣板程式碼的數量。
  2. 安全
    避免空指標異常等整個類的錯誤。
  3. 通用
    構建伺服器端程式、Android 應用程式或者在瀏覽器中執行的前端程式。
  4. 互操作性
    通過 100% Java 互操作性,利用 JVM 既有框架和庫。

    配置

    在我們的AndroidStudio開發工具中,要想使用Kotlin這個優秀的開發語言,我們需要安裝外掛,直接在安裝外掛介面搜尋Kotlin然後安裝。之後再gradle檔案增加如下配置
apply plugin:'kotlin-android'
apply plugin:'kotlin-android-extensions'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}複製程式碼

專案gradle檔案

buildscript {
    ext.kotlin_version = '1.1.1'
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}複製程式碼

完成上面的配置後,我們就可以愉快的玩耍了。

Kotlin示例

首先我們還和以前一樣,建立一個Android專案,自動建立一個Activity之後我們再建立一個java類

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}
public class Test {
    private static String str = null;

    public static void main(String[] args) {
        str = "Code4Android";
        System.out.println(str);
    }
}複製程式碼

那上面的程式碼如果用kotlin實現是什麼樣子呢。儘管現在我們還不能寫出Kotlin程式碼,但是在安裝外掛後AS中提供了自動轉換Kotlin程式碼的功能

「Kotlin初探 | 掘金技術徵文 」
這裡寫圖片描述

轉換後的Kotlin程式碼

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)

        val fab = findViewById(R.id.fab) as FloatingActionButton
        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId
        if (id == R.id.action_settings) {
            return true
        }
        return super.onOptionsItemSelected(item)
    }
}


object Test {
    private var str: String? = null

    @JvmStatic fun main(args: Array<String>) {
        str = "Code4Android"
        println(str)
    }
}複製程式碼

注意:AS提供的java程式碼自動轉換功能,我們不要輕易使用,更不要轉化我們成熟的專案,如果需要就需要我們自己去重構實現。否則會有意向不到的事情等著你,畢竟轉換不是那麼智慧。上面的程式碼只是讓你先簡單熟悉下Kotlin程式碼時什麼樣子的,接下來我們先去學習一下Kotlin的基本語法。相信很容易上手。

Hello World!

我們由一個簡單的"Hello World!"輸出程式開始。與新建java檔案類似,如下圖,我們選擇Kotlin File/Class.建立一個Kotlin檔案。

「Kotlin初探 | 掘金技術徵文 」
這裡寫圖片描述

package com.learnrecord

/**
 *Created by Code4Android on 2017/4/21.
 */

var str: String = ""

fun main(args: Array<String>) {
    str = "Hello World!"
    println(str)
}複製程式碼

上述程式碼就是簡單的輸出一個字串“Hello World”,package 後面跟的是包名,我們看出了和java檔案的區別,在包名後面沒有以分號“;”結尾。在Kotlin語法中,語句結尾都不在有分號“;”。

在Kotlin中變數宣告有兩種型別,val修飾變數是隻讀變數即只能賦值一次,再次賦值時就會編譯錯誤
,如果我們需要多次修改值就需要使用var。在上面的 var str: String = ""中,str是變數名,:String,表明該變數是String型別變數,後面就是賦值語句。我們也可以這樣寫var str= ""省略了生命變數型別,它可以根據賦的值而自動推斷出型別。如果我們使用下面賦值語句str=null,發現null是不能賦值的,這就是Kotlin的特性,如果我們想賦值null,可以修改為 var str:String?=""。
fun就是函式生命,而這個main函式就和我們java中的main方法一樣,是程式執行的入口。println就是一個列印輸出。

Kotlin宣告型別

在Kotlin中有如下幾種Number型別,他們都是繼承自Number抽象類。
Float(32位),Double(64),Int(32),Byte(8),Short(16),Long(64,型別用大寫L,如12L),Any(任意型別),陣列型別Array 根據傳入的泛型資料自動匹配型別,Kotlin還提供了指定型別的Array,如ByteArray,CharArray,ShortArray,IntArray,LongArray,FloatArray,DoubleArray,BooleanArray。在陣列型別中都提供了get(index),set(index,value)及iterator()方法供我們使用。

    val iArray: IntArray = intArrayOf(1, 2, 3)
    val sArray: Array<String> = Array<String>(3, { i -> i.toString() })
    val anyArray: Array<Any> = arrayOf(1, "2", 3.0, 4.1f) // 可將型別進行混排放入同一個陣列中
    val lArray: LongArray = longArrayOf(1L, 2L, 3L)複製程式碼

函式

我們先來實現一個簡單的數值求和的函式,通用實現方法如下

    fun sum(a: Int, b: Int): Int {
        return a + b
    }複製程式碼

傳入兩個Int型數值,sum是函式名,括號後面的:Int表示該函式返回Int的值,函式體中對兩個數字相加並返回。在Kotlin中表示式也可以作為函式體,編譯器可以推斷出返回型別,可以簡化為

    fun sum(a: Int, b: Int) = a + b複製程式碼

為了更好理解表示式可以作為函式體,我們可以建立一個函式獲取兩個數的最大值,如下:

  fun max1(a:Int,b:Int)=if (a>b) a else b複製程式碼

需要注意的是若if後有多個表示式,如下

    fun max1(a:Int,b:Int)= if (a > b) {
        println(a)
        a
    } else {
        println(b)
        //如果我們將println(b)放到b的下面,執行會返回kotlin.Unit為無型別,返回值總是最後一個表示式的返回值或值
        b
    }
    println(max1(1,3))複製程式碼

括號中的表示式順序決定了返回的值及其型別。

如果我們的方法體僅僅是列印字串,並不返回值則

    fun printInt(a: Int): Unit {
        println(a)
    }複製程式碼

Unit就類似我們java中的void,即沒有返回值,此時我們可以省略

    fun printInt(a: Int) {
        println(a)
    }複製程式碼

對於函式體,方法或者類等和java一樣也有一些修飾符,如下

  • abstract //抽象類標示
  • final //標示類不可繼承,預設屬性
  • enum //標示類為列舉
  • open //類可繼承,類預設是final的
  • annotation //註解類
  • private //僅在同一個檔案中可見
  • protected //同一個檔案中或子類可見,不可修飾類
  • public //所有呼叫的地方都可見
  • internal //同一個模組中可見,若類不加修飾符,則預設為該修飾符,作用域為同一個應用的所有模組,起保護作用,防止模組外被呼叫。

    操作符

    直接上程式碼如下
    //操作符  shl 下面對Int和Long
    var a: Int = 4
    var shl: Int = a shl (1)  //Java中的左移運算子 <<
    var shr: Int = a shr (1) //Java中的右移運算子 >>
    var ushr: Int = a ushr (3) //無符號右移,高位補0 >>>
    var and: Int = 2 and (4)   //按位與操作 &
    var or: Int = 2 or (4) //按位或操作 |
    var xor: Int = 2 xor (6)  //按位異或操作 ^
    print("\nshl:" + shl + "\nshr:" + shr + " \nushr:" + ushr + "\nand:" + and + "\nor:" + or + "\nxor:" + xor)複製程式碼

輸出資訊為

shl:8
shr:2 
ushr:0
and:0
or:6
xor:4複製程式碼

在上面的部分操作符可達到邏輯操作符, 當我們使用Boolean時,or() 相當於 ||,and() 相當於 &&, xor() 當操作符兩邊相反時為true,否則為false ,not()時取反。

陣列遍歷及控制語句

遍歷陣列

    fun forLoop(array: Array<String>) {
        //第一種方式直接輸出字元(類似java foreach)
        for (str in array) {
            println(str)
        }
        //Array提供了forEach函式
        array.forEach {
          println(it)
         }
        //array.indices是陣列索引
        for (i in array.indices) {
            println(array[i])
        }
        //(類似javafor(int i=0;i<arry.length;i++))
        var i = 0
        while (i < array.size) {
            println(array[i++])
        }
    }複製程式碼

使用when判斷型別

fun whenFun(obj: Any) {
        when (obj) {
            25 -> println("25")
            "Kotlin" -> println("Kotlin")
            !is String -> println("Not String")
            is Long -> println("Number is Long")
            else -> println("Nothing")
        }
    }複製程式碼

is 和java中instanceof是一個作用判斷是否為某個型別。!is即判斷不是某個型別。

//@定義label,一般用在內層迴圈中跳到外層迴圈:i in 0..2等價於java中 for(int i=0;i<=2;i++)效果
    loop@ for (i in 0..2) {
        for (j in 0..3) {
            println("i:" + i + "  j:" + j)
            if (j == 2)
            //continue@loop//跳到外層迴圈,繼續往下執行
                break@loop  //跳到外層迴圈label處,跳出改層迴圈
        }
    }複製程式碼

倒序輸出是downTo

    //倒序輸出5 4 3 2 1 0
    for (i in 5 downTo 0) {
        println(i)
    }
    //設定輸出資料步長
     for (i in 1..5 step 3) print(i) // 輸出 14
     //step和downTo混合使用
     for (i in 5 downTo 1 step 3) print(i) //輸出52複製程式碼

#類與列舉

/*** constructor:建構函式
 * constructor無修飾符(如:private)時,constructor可以省略:
 * 當是無參建構函式時,整個建構函式部分也可以省略,省略的建構函式預設是public的
 * 由於primary constructor不能包含任何程式碼,因此使用 init 程式碼塊對其初始化,
 * 同時可以在初始化程式碼塊中使用建構函式的引數
 */
open class People private constructor(var id: String, var name: String) {
    //可以類中初始化屬性:
    var customName = name.toUpperCase() //初始化屬性
    //次建構函式,使用constructor字首宣告,且必須呼叫primary constructor,使用this關鍵字
    constructor( id: String, name: String, age: Int) : this(id, name) {
        println("constructor")
    }
    init {
        println("初始化操作,可使用constructor引數")
    }
    //需要open修飾,子類才可以
    open fun study() {
        print("study")
    }
    //People前的冒號":"是繼承的意思,實現類介面的時候也是冒號
class Student(id: String, name: String) : People(id, name) {
    var test: Number = 3
    private var name1: String?
        get() {
            return name1
        }
        set(value) {
            name1 = value
        }
  //override修飾的方法,預設是可以被繼承的。若希望不被繼承,可以使用 final 關鍵詞修飾
    override fun study() {
        super.study()
    }
}

}複製程式碼

資料類用來儲存資料,類似於POJO類,使用data關鍵詞進行定義,編譯器預設會為資料類生成以下四個方法

  • equals()
  • hashCode()
  • toString()
  • copy()
    通過資料類你會看到Kotlin的簡潔性,我們建立一個Staff類,有String型別的name,position和泛型T(使用泛型僅僅是為了在Kotlin中接觸以下泛型)
    java實現程式碼:
public class StaffJ<T> {
    private String name;
    private String position;
    private T age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }
    public T getAge() {
        return age;
    }
    public void setAge(T age) {
        this.age = age;
    }
}複製程式碼

Kotlin資料類:

data class Staff<T>(var name: String,  val position: String,var age:T)複製程式碼

通過對比我們就看出了優點了,一行程式碼就實現了,具體使用

 var staff = Staff("code4Android","Android工程師","22")  //例項化物件複製程式碼

要獲取某個屬性如獲取名字staff.name,賦值就是staff.name="code4Android2",既然說了這樣可以賦值,但是動手的小夥伴說為什麼我敲的報錯啊,如下

    staff.position="前端"複製程式碼

編譯報錯了,在前面我們說過val修飾的屬性只能賦值一次,那在這裡val修飾的屬性我們是不能再次賦值的。

fun main(arg:Array<String>){
    var staff = Staff("code4Android","Android工程師","22")  //例項化物件
    staff.name="code4Android2"
    var staff1=staff.copy()
    //使用copy的時候可以指定預設值,可以指定任意個用逗號","隔開
    var staff2=staff.copy(name="ccc",position = "kotlin")
    println("name:${staff2.name} position:${staff2.position} age ${staff2.age}")
    //staff.position="Kotiln" //val不能再次賦值
    var anotherStaff= Staff("code4Android","Android工程師",22) //例項化物件

    println("staff toString(): ${staff.toString()} anotherStaff toString(): ${anotherStaff.toString()}")
    println("staff hashCode(): ${staff.hashCode()} anotherStaff hashCode(): ${anotherStaff.hashCode()}")
    println("staff is equals another staff ? ${staff.equals(anotherStaff)}")
}複製程式碼

上面使用了字元模板,在Kotlin中有兩種字元模板形式,\$<變數>、${<變數>}

    var name:String="Code4Android"
    println("我的名字叫$name")
    println("我的名字叫${name}")複製程式碼
/**
 * java允許使用匿名內部類;kotlin也有類似的概念,稱為物件表示式-object expressions
 */
open class KeyBord{
    open fun onKeyEvent(code:Int):Unit = Unit
}

/**匿名內部類**/
var key=object :KeyBord(){
    override open fun onKeyEvent(code:Int):Unit = Unit
}複製程式碼

列舉

enum class Color{
    RED,BLACK,BLUE,GREEN,WHITE
}
fun display(){
    var color:Color=Color.BLACK
    Color.valueOf("BLACK") // 轉換指定name為列舉值,若未匹配成功,會丟擲IllegalArgumentException
    Color.values() //已陣列的形式,返回列舉值
    println(color.name)////獲取列舉名稱
    println(color.ordinal)//獲取列舉值在所有列舉陣列中定義的順序,0開始
}複製程式碼

在Kotlin中列舉還支援方法。

擴充套件

/**
 * fun receiverType.functionName(params){
 *body
 *}
 * receiverType : 待擴充套件的類名
 * .            :修飾符為擴充套件符
 * functionName :為自定義的擴充套件函式名,
 * params       :為自定義的擴充套件函式引數,可為空
 * 擴充套件函式作用域,受函式的visibility modifiers影響
 * 擴充套件函式並沒有對原類做修改,而是為被擴充套件類的物件新增新的函式。
 * 有一條規則,若擴充套件函式和類原有函式一致,則使用該函式時,會優先使用類本身的函式。
 */
class Employee(var name: String) {
    fun print() {
        println("Employee")
    }
}

//擴充套件函式
fun Employee.println() {
    print("println:Employee name is $name")
}


/**
 * 可以擴充套件一個空物件
 */
fun Any?.toString1(): String {
    if (this == null)
        return "toString1:null"
    else {
        return "toString1" + toString()
    }
}

/**
 * 擴充套件屬性
 * 由於擴充套件屬性實際上不會向類新增新的成員,
 * 因此無法讓一個擴充套件屬性擁有一個後端域變數. 所以,對於擴充套件屬性不允許存在初始化器.
 * 擴充套件屬性的行為只能通過明確給定的取值方法與設值方法來定義,也就意味著擴充套件屬性只
 * 能被宣告為val而不能被宣告為var.如果強制宣告為var,即使進行了初始化,
 * 在執行也會報異常錯誤,提示該屬性沒有後端域變數。
 */
val Employee.lastName: String
    get() {
        return "get:"+name
    }複製程式碼

使用

fun main(arg: Array<String>) {
    var employee = Employee("Code4Android")
    employee.print()
    employee.println()
    println(employee.toString1())
    println(employee.lastName)
}複製程式碼

代理

**
 * 被代理介面
 */
interface Base {
    fun display()
}

/**
 * 被代理類
 */
open class BaseImpl : Base {
    override fun display() = print("just display me.")
}

/**
 * 代理類,使用:以及關鍵詞by進行宣告
 * 許代理屬性和其他繼承屬性共用
 * class Drived(base: Base) : Base by base,BaseImpl()
 */
class Drived(base: Base) : Base by base

//使用
fun show() {
    var b = BaseImpl()
    var drived = Drived(b)
    drived.display()

}

**
 * 代理型別:
 * 懶載入:Lazy
 * 觀察者:Delegates.observable()
 * 非空屬性:Delegates.notNull<>()
 */
class Water {

    public var weight:Int by Delegates.notNull()
    /**
     * 代理屬性
     */
    public val name: String by lazy {
        println("Lazy.......")
        "Code4Android"
    }
    public var value: String by Delegates.observable("init value") {
        d, old, new ->
        println("$d-->$old->$new")
    }
}

fun main(arg: Array<String>) {
    show()
    var water = Water()
    println(water.name)
    println(water.name)
    water.value = "11111"
    water.value = "22222"
    water.value = "33333"
    println(water.value)
    println(water.value)
    //必須先賦值否則IllegalStateException: Property weight should be initialized before get.
    water.weight=2
    print(water.weight)
}複製程式碼

操作符::

val String.lastChar: Char
    get() = this[this.length - 1]

class A(val p: Int)複製程式碼
   //1,反射得到執行時的類引用:
    val c = Student::class
    //2,函式引用
    fun isOdd(x: Int) = x % 2 != 0
    val numbers = listOf(1, 2, 3)
    println(numbers.filter(::isOdd)) 

    //3,屬性引用(在此引用main函式主體外宣告的變數)
    println(::x.get())
    ::x.set(2)
    println(x)
    //4,::x 表示式評估為 KProperty<Int> 型別的屬性,它允許我們使用 get() 讀它的值或者使用名字取回它的屬性
    val prop = A::p
    println(prop.get(A(1))) 

    //5,對於擴充套件屬性
    println(String::lastChar.get("abc")) 

    //6,與 java 反射呼叫
    println(A::p.javaClass),
    var f: Array<Field> = A::p.javaClass.declaredFields複製程式碼

伴生物件

伴生物件(companion object )類似於java中的靜態關鍵字static。在Kotlin沒有這個關鍵字,而是伴生物件,具體用法

open class People constructor(var id: String,  var name: String){
    //可以類中初始化屬性:
    var customName = name.toUpperCase() //初始化屬性

    //使用constructor字首宣告,且必須呼叫primary constructor,使用this關鍵字
    constructor( id: String, name: String, age: Int) : this(id, name) {
        println("constructor")
    }

    init {
        println("初始化操作,可使用constructor引數")
    }
   //,Kotlin的class並不支援static變數,所以需要使用companion object來宣告static變數,
   // 其實這個platformStatic變數也不是真正的static變數,而是一個伴生物件,
    companion object {
        val ID = 1
    }
}複製程式碼

使用的話直接People.ID。

單例模式

在Kotlin中使用object修飾類的時候,。該類是單例物件。

/**
 * 使用object定義類,該類的例項即為單例,訪問單例直接使用類名,不能通過建構函式進行訪問,不允許有建構函式
 * Place.doSomething() // 訪問單例物件
 */
object Singleton {
    fun doSomething() {
        println("doSomething")
    }
}


/**
 * 例項化的時候,單例是懶載入,當使用的時候才去載入;而物件表示式是在初始化的地方去載入。
 *
 * 當在類內部使用 object 關鍵詞定義物件時,允許直接通過外部類的類名訪問內部物件進而訪問其相關屬性和方法,相當於靜態變數
 * 可以使用companion修飾單例,則訪問其屬性或方法時,允許省略單例名
 * MyClass.doSomething() // 訪問內部單例物件方法
 */
class MyClass {
    companion object Singleton {
        fun doSomething() {
            println("doSomething")
        }
    }
}複製程式碼

好了,今天就介紹到這裡,文中若有錯誤歡迎指出,Have a wonderful day.

Kotlin英文官網
Kotlin學習中文官網
線上學習示例

本次徵文活動的連結: juejin.im/post/58d8e9…

相關文章