Kotlin核心語法(二):kotlin程序結構、函數

博客主頁java

Kotlin 基礎

對比下Java的類Person與Kotlin的類Person區別:git

// java
public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }
    // 提供一個getter訪問器
    public String getName() {
        return name;
    }
}

// kotlin
// 在Kotlin中,public是默認的可見性,能夠省略
class Person(val name: String)

屬性

在Kotlin中,在類中聲明一個屬性和聲明一個變量同樣,使用 val(只讀的) 和 var(可變的) 關鍵字。正則表達式

若是屬性的名稱以 is 開頭,getter不會增長任何的前綴,而它的setter名稱中的is會被替換成setsegmentfault

class Person(
    // 只讀屬性,生成一個字段和一個簡單的getter
    val name: String,
    // 可寫屬性,一個字段,一個getter和一個setter
    var address: String,
    var isMarried: Boolean
)

// kotlin轉換爲java
public final class Person {
   @NotNull
   private final String name;
   @NotNull
   private String address;
   private boolean isMarried;

   @NotNull
   public final String getName() {
      return this.name;
   }

   @NotNull
   public final String getAddress() {
      return this.address;
   }

   public final boolean isMarried() {
      return this.isMarried;
   }

   public final void setMarried(boolean var1) {
      this.isMarried = var1;
   }

   public final void setAddress(@NotNull String var1) {
      this.address = var1;
   }

   public Person(@NotNull String name, @NotNull String address, boolean isMarried) {
      this.name = name;
      this.address = address;
      this.isMarried = isMarried;
   }
}

在java 和 kotlin 中調用Person類區別:數組

// java中使用Person
 Person person = new Person("kerwin", "anqing", false);
 person.setMarried(true);
 System.out.println(person.getName() + " : " + person.getAddress() + " : " + person.isMarried());
// kerwin : anqing : true


// kotlin中使用Person
val person = Person("kerwin", "anqing", false)
person.isMarried = true
println("${person.name} : ${person.address} : ${person.isMarried}")
// kerwin : anqing : true

Kotlin 屬性訪問器自定義

下面自定義一個屬性isSquare,實現getter,它的值是每次訪問屬性的時計算出來的。app

// kotlin
class Rectangle(
    val height: Int,
    val width: Int
) {
    // 自定義訪問器
    val isSquare: Boolean
        // 聲明屬性的getter
        // 也能夠這樣寫: get() = width == height 
        get() {
            return width == height
        }
}

// kotlin轉java
public final class Rectangle {
   private final int height;
   private final int width;

   public final boolean isSquare() {
      return this.width == this.height;
   }

   public final int getHeight() {
      return this.height;
   }

   public final int getWidth() {
      return this.width;
   }

   public Rectangle(int height, int width) {
      this.height = height;
      this.width = width;
   }
}

Kotlin 目錄和包

每個kotlin文件都能以一條package語句開頭,而文件中定義的全部的聲明(類、函數、屬性)都會被放到這個包中。若是包不相同,則須要導入它們,使用關鍵字import函數

kotlin不區分導入的是類仍是函數,且容許使用import關鍵字導入任何種類的聲明。可直接導入頂層函數的名稱工具

// 包聲明
package com.kerwin.kotlin.demo

class Rectangle(
    val height: Int,
    val width: Int
) {

    val isSquare: Boolean
        get() = width == height
}

// 在com.kerwin.kotlin.demo包中定義函數
fun createRectangle(): Rectangle {
    return Rectangle(12, 23)
}

導入其餘包中的函數佈局

// 包聲明
package com.kerwin.kotlin.demo1
// 導入函數名稱
import com.kerwin.kotlin.demo.createRectangle

fun main(args: Array<String>) {
    println(createRectangle().isSquare)
}

在kotlin中,能夠把多個類放在同一個文件中,文件的名字還能夠隨意選擇。kotlin也沒有對磁盤上源文件的佈局強加任何限制。包層級結構不須要遵循目錄層級結構。post

Kotlin 程序結構

1.選擇處理

聲明枚舉類

kotlin聲明一個枚舉類,使用 enum class 關鍵字。枚舉類中定義任何方法,就要使用分號(;)把枚舉常量列表和方法定義分開。

// 聲明一個帶有屬性的枚舉類
enum class Color(
    // 聲明枚舉常量的屬性
    val r: Int, val g: Int, val b: Int
) {
    // 在每一個常量建立時指定屬性值
    RED(255, 0, 0), ORANGE(255, 165, 0),
    YELLOW(255, 255, 0), GREEN(0, 255, 0),
    BLUE(0, 0, 255); // 必需要有分號

    // 給枚舉類定義一個方法
    fun rgb() = (r * 256 + g) * 256 + b
}

使用"when"處理枚舉類

when 是一個有返回值的表達式。

// kotlin中不須要在每一個分支都寫上break語句。若是匹配成功。只有對應的分支會執行
fun getColorString(color: Color) = when(color) {
    Color.RED -> "red"
    Color.BLUE -> "blue"
    Color.ORANGE -> "orange"
    Color.YELLOW -> "yellow"
    Color.GREEN -> "green"
}

能夠把多個值合併到同一個分支,只須要用逗號(,)隔開這些值就能夠

fun getWarmth(color: Color) = when(color) {
    Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
    Color.BLUE, Color.GREEN -> "cold"
}

在"when"結構中使用任意對象

when 容許使用任何對象做爲分支條件。

// 在when分支中使用不一樣的對象
fun mix(c1: Color, c2: Color) =
    // when 表達式的實參能夠是任何對象,它被檢查是否與分支條件相等
    when (setOf(c1, c2)) {
        setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
        setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
        else -> throw Exception("dirty color")
    }

使用不帶參數的"when"

若是沒有給when表達式提供參數,分支條件就是任意的布爾表達式.

fun mixOptimized(c1: Color, c2: Color) =
    when {
        (c1 == Color.RED && c2 == Color.YELLOW) ||
                (c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE

        (c1 == Color.YELLOW && c2 == Color.BLUE) ||
                (c1 == Color.BLUE && c2 == Color.YELLOW) -> Color.GREEN
        else -> throw Exception("dirty color")
    }

智能轉換:合併類型檢查和轉換

定義一個函數:(1 + 2) + 4 算術表達式求值。智能轉換隻在變量通過is檢查後再也不發生變化的狀況下有效。屬性必須是一個val屬性,且不能有自定義的訪問器。使用as關鍵字來表示到特定類型的顯示轉換。

// 僅做爲一個標記接口
interface Expr

// 實現接口使用冒號(:),後面跟上接口名稱
class Num(val value: Int) : Expr

class Sum(val left: Expr, val right: Expr) : Expr

// 使用if層疊對錶達式求值
fun eval(e: Expr): Int {
    // 使用is檢查來判斷一個變量是不是某種類型
    if (e is Num) {
        // 顯示的轉換成類型Num是多餘的
        val num = e as Num
        return num.value
    }

    if (e is Sum) {
        // 變量e智能轉換了類型
        return eval(e.left) + eval(e.right)
    }

    throw IllegalArgumentException("Unknown")
}

>>>  println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))

重構:用"when"代替"if"

kotlin沒有三元運算符,由於if表達式有返回值。若是if分支只有一個表達式,花括號能夠省略。若是if分支是一個代碼塊,代碼塊中的最後一個表達式會被做爲結果返回。

// 使用有返回值的if表達式
fun eval(e: Expr): Int {
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.left) + eval(e.right)
    } else {
        throw IllegalArgumentException("Unknown")
    }
}

可使用when代替if層疊

// when容許檢查實參值的類型
fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.left) + eval(e.right)
        else -> throw IllegalArgumentException("Unknown")
    }

代碼塊做爲 "if" 和 "when" 的分支

if 和 when 均可以使用代碼塊做爲分支體,那麼代碼塊中的最後一個表達式就是結果。

fun evalWithLogging(e: Expr): Int =
    when (e) {
        is Num -> {
            println("num: ${e.value}")
            e.value
        }

        is Sum -> {
            val left = evalWithLogging(e.left)
            val right = evalWithLogging(e.right)
            println("sum: $left + $right")
            left + right
        }

        else -> throw IllegalArgumentException("Unknown")
    }

2.循環

while循環

和java沒有區別,有while循環 和 do-while循環

迭代數字:區間和數列

區間:兩個值之間的間隔,這兩個值一般是數字,一個起始值,一個結束值。使用 .. 運算符表示區間。
kotlin區間是包含的或者閉合的。是包含結束值的。若是不包含結束值,使用 until 函數

val oneToTen = 1..10

// 不包含size
for (x in 0 until size)
等價於
for (x in 0..size - 1)

迭代1到100之間的全部數字

fun fizzBuzz(i: Int) = when {
    i % 15 == 0 -> "FizzBuzz "
    i % 3 == 0 -> "Fizz "
    i % 5 == 0 -> "Buzz "
    else -> "$i "
}

>>>  for (i in 1..100) {
        print(fizzBuzz(i))
    }

迭代帶步長的100到1的區間

// 100 downTo 1 是遞減的數列(步長爲-1)
 // step 2 把步長的絕對值變成2,可是方向保持不變(步長被設置成了爲-2)
 for (i in 100 downTo 1 step 2) {
    print(fizzBuzz(i))
 }

迭代map

初始化map並迭代。..語法能夠建立數字區間,也能夠建立字符區間。

// 初始化map,使用TreeMap讓鍵排序
    val binaryMap = TreeMap<Char, String>()

    // 使用字符區間迭代從A到F之間的字符
    for (c in 'A'..'F') {
        // 字符二進制
        val binary = Integer.toBinaryString(c.toInt())
        // 簡明語法:map[key] = value 設置值;map[key] 讀取值
        // 這行等價於:binaryMap.put(c, binary)
        binaryMap[c] = binary
    }

    // 迭代map,把鍵和值賦給兩個變量
    for ((letter, binary) in binaryMap) {
        println("$letter : $binary")
    }

能夠在迭代集合時,使用當前下標

val list = arrayListOf("10", "11", "12")
    // 迭代集合時使用下標
    for ((index, element) in list.withIndex()) {
        println("$index : $element")
    }

使用 "in" 檢查集合和區間的成員

關鍵字 in 能夠迭代區間或者集合,還能夠用來檢查區間或者集合是否包含了某個值。!in 檢查一個值是否不在區間中。

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'

// !in 檢查是否不在區間中
fun isNotDigit(c: Char) = c !in '0'..'9'

in 運算符 和 !in 檢查能夠做爲 when分支

fun recognize(c: Char) = when (c) {
    // 檢查值是否在0到9的區間以內
    in '0'..'9' -> "It's a digit."
    in 'a'..'z', in 'A'..'Z' -> "It's a letter."
    else -> "I don't know."
}

Kotlin 函數

1.在kotlin中建立集合

// 建立set
    val set = hashSetOf(1, 3, 5)
    // kotlin的javaClass等價於java的getClass
    println(set.javaClass)
    // class java.util.HashSet


   // 建立list
   val list = arrayListOf(1, 3, 5)
   println(list.javaClass)
   // class java.util.ArrayList

   // 建立map
   // to 並非一個特殊的結構,而是一個普通函數
   val map = hashMapOf(1 to "one", 3 to "three", 5 to "five")
   println(map.javaClass)
  // class java.util.HashMap

kotlin中能夠經過下面方式獲取一個列表中最後一個元素,或者獲得一個數字列表的最大值

val list = listOf("abc", "def", "ker")
    println(list.last())  // ker

    val set = setOf(1, 23, 4)
    println(set.max()) // 23

2.函數聲明

java集合都有一個默認的toString方法實現。假設不想使用默認的實現,須要本身實現或者使用第三方庫,如:guava 和Apache Commons。

// 本身實現joinToString函數
fun <T> joinToString(
    collection: Collection<T>,
    prefix: String,
    postfix: String,
    separator: String
): String {

    val result = StringBuilder(prefix)

    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator).append(" ")

        result.append(element)
    }

    result.append(postfix)

    return result.toString()
}

>>> val list = listOf("abc", "def", "ker")
>>> println(joinToString(list, "(", ")", ";"))
(abc; def; ker)

命名參數

// 這種方式不能知道每一個參數對應什麼含義
joinToString(list, "(", ")", ";")

// kotlin,能夠顯式的標明一些參數名稱
// 若是在調用一個函數時,指明瞭一個參數名稱,爲了不混淆,那它以後全部參數都須要標明名稱
joinToString(list, prefix = "(", postfix = ")", separator = ";")

默認參數值

java中存在一個廣泛的問題,一些類的重載函數不少,如:Thread類(8個構造方法)。

在kotlin中,能夠在聲明函數的時候,指定參數的默認值,就能夠避免建立重載的函數。

fun <T> joinToString(
    collection: Collection<T>,
    prefix: String = "",   // 有默認的參數
    postfix: String = "",
    separator: String = ", "
): String 

// 調用的時候,能夠省略只有排在末尾的參數
>>> joinToString(list)
>>> joinToString(list, "(", ")")
// 若是使用命名參數,能夠省略中間的一些參數
>>> joinToString(list, separator = "; ")
// 也能夠以任意順序只給定須要的參數
>>> joinToString(list, separator = "; ", prefix = "[")

消除靜態工具類:頂層函數和屬性

在kotlin中,不須要建立靜態工具類,能夠把函數直接放到代碼文件的頂層,不用從屬任何的類。

聲明joinToString做爲頂層函數

// join.kt
package strings
fun joinToString(...): String { ... }

// 當編譯join.kt這個文件時,會生成一些類,JoinKt.java。由於JVM只能執行類中的代碼。
// 且join.kt文件中的全部頂層函數編譯爲JoinKt.java這個類的靜態函數
// 從java中調用這些函數:JoinKt.joinToString(list, "", "", "");
public final class JoinKt {
     @NotNull
     public static final String joinToString(@NotNull Collection collection, @NotNull String prefix, @NotNull String postfix, @NotNull String separator) {
              ...
    }
}

修改文件類名:
要修改包含kotlin頂層函數的生成的類的名稱,須要爲這個文件添加 @file:JvmName 註解,將其放到這個文件的開頭,位於包名的前面

// 註解指定類名
@file:JvmName("StringUtils")

// 包的聲明跟在文件註解後
package strings

fun <T> joinToString(
    collection: Collection<T>,
    prefix: String = "",
    postfix: String = "",
    separator: String = ", "
): String { ... }

// 編譯後,生成StringUtils.class文件
// 在java代碼中調用這個函數:StringUtils.joinToString(list, "", "", "")

頂層屬性和函數同樣,屬性也能夠放到文件的頂層。

package strings

val LINE_SEPARATOR = "\n"

// 編譯後,私有的靜態常量。
private static final String LINE_SEPARATOR = "\n";

// 使用const修飾,const val LINE_SEPARATOR = "\n" 編譯後生成public的靜態常量 
public static final String LINE_SEPARATOR = "\n";

3.擴展函數和屬性

擴展函數就是把要擴展的類或者接口的名稱,放到即將添加的函數前面。這個類的名稱稱爲接受者類型,用來調用這個擴展函數的那個對象,叫做接受者對象。可是擴展函數不能訪問私有的或者受保護的成員。

package strings

// 爲String類添加本身的方法:字符串的最後一個字符
// String:接受者類型   this:接受者對象
fun String.lastChar(): Char = this[this.length - 1]


>>> println("abc".lastChar())

導入和擴展函數

kotlin容許用和導入類同樣的語法來導入單個函數。

import strings.lastChar
// 使用 * 來導入也能夠: import strings.*
// 還可使用關鍵字 as 來修改導入的類或者函數名稱
// import strings.lastChar as last
// 調用: val lastChar = "abc".last()

val lastChar = "abc".lastChar()

從java中調用擴展函數

擴展函數就是靜態函數,它把調用對象做爲了它的第一個參數。

// 把接受者對象做爲第一個參數傳進去便可
StringUtils.lastChar("abc")

做爲擴展函數的工具函數

爲元素的集合類Collection添加一個擴展函數,而後給全部的參數添加一個默認值。

@file:JvmName("StringUtils")

package strings

import java.lang.StringBuilder

// Collection<T>: 接受者類型
// 爲Collection<T> 聲明一個擴展函數
fun <T> Collection<T>.joinToString(
    prefix: String = "",
    postfix: String = "",
    separator: String = ", "
): String {

    val result = StringBuilder(prefix)

    // this: 接受者對象
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator).append(" ")

        result.append(element)
    }

    result.append(postfix)

    return result.toString()
}

>>> val list = listOf("abc", "def", "ker")
>>> println(list.joinToString(prefix = "{", postfix = "}", separator = "; "))
{abc;  def;  ker}

不可重寫的擴展函數

kotlin中不能重寫擴展函數,由於kotlin會把它們看成靜態函數對待。

擴展屬性

擴展屬性必須定義getter函數,由於沒有支持字段,所以沒有默認getter實現,初始化也不能夠,由於沒有地方存儲初始值。

// 聲明一個擴展屬性
val String.lastChar: Char
     get() = this[this.length - 1]

也能夠聲明一個可變的擴展屬性

var StringBuilder.lastChar: Char
    // getter屬性
    get() = this.get(this.length - 1)
    // setter屬性
    set(value) {
        this.setCharAt(length - 1, value)
    }


>>> val sb = StringBuilder("ab?")
>>> sb.lastChar = '!'
>>> println(sb) // ab!

// 若是從java中訪問擴展屬性,顯式的調用它的getter函數
StringUtils.getLastChar(new StringBuilder("abc"));

4.處理集合:可變參數、中綴調用和庫的支持

  1. 可變參數的關鍵字 vararg,聲明一個函數能夠接收任意數量的參數
  2. 一箇中綴表達法,當調用一些只有一個參數的函數時,可以讓代碼更簡練
  3. 解構聲明,用來把一個單獨的組合值展開到多個變量中

擴展java集合的API

val list = listOf("abc", "def", "ker")

// last函數被定義爲List的擴展函數
println(list.last()) // ker

// public fun <T> List<T>.last(): T { ... }

可變參數:讓函數支持任意數量的參數

kotlin中,在參數上使用vararg修飾符;java中使用三個點(...)

val list = listOf("abc", "def", "ker")

// listOf函數在庫中聲明
public fun <T> listOf(vararg elements: T): List<T>

還有一個區別:當須要傳遞的參數是已經包裝在數組中時,在java中,能夠按原樣傳遞數組;kotlin中要求顯式的解包數組,以便每一個數組元素在函數中能做爲單獨的參數來調用。

val array = arrayOf(11, 12, 13)
// 使用展開運算符(*)傳遞數組
val list = listOf("10", *array)

>>> println(list) // [10, 11, 12, 13]

鍵值對處理:中綴調用和解構聲明

val map = mapOf(1 to "one", 2 to "two")
// mapOf函數在庫中的聲明
public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

// 通常函數調用: 2.to("two"); 中綴符號調用to函數:2 to "two"
// to 不是內置結構,而是一種特殊的函數調用,稱爲中綴調用

// to 函數在庫中的聲明
// 中綴調用能夠與只有一個參數的函數一塊兒使用,使用中綴符號調用函數,須要使用**infix**修飾符標記。
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

能夠直接用Pair的內容來初始化兩個變量,稱爲解構聲明。解構聲明還可使用map的key和value內容來初始化兩個變量。也適用於循環,如: withIndex

val (number, name) = 1 to "one"


 val list = listOf("abc", "def", "ker")
 for ((index, element) in list.withIndex()) {
     println("$index : $element")
 }

5.字符串和正則表達式的處理

字符串模板

kotlin能夠在字符串字面值中引用局部變量,只須要在變量名稱前面加上字符$

val x = 12
   println("x = $x") // x = 12

   // 若是對它轉義,不會把x解析成變量的引用 
   println("x = \$x") //x = $x

   // 若是引用複雜的表達式,須要把表達式用花括號括起來
    val x= 1
    val y = 2
    println("x + y = ${x + y}")

分割字符串

// 在java中,指望獲得[12, 345-6, A],可是返回是一個空數組,由於點號(.)表示任何字符的正則表達式
 System.out.println(Arrays.toString("12.345-6.A".split("."))); // []

// 在kotlin中,能夠顯示的建立一個正則表達式分割字符串
// 須要轉義表示字面量,而不是通配符
// 使用擴展函數toRegex將字符串轉換爲正則表達式
println("12.345-6.A".split("\\.".toRegex()))  // [12, 345-6, A]

 // kotlin中的split擴展函數的其餘重載支持任意數量的純文本字符串分隔符;
 // 指定多個分隔符
 println("12.345-6.A".split(".", "-"))  // [12, 345, 6, A]

正則表達式和三重引號字符串

使用String的擴展函數來解析文件路徑。

fun parsePath(path: String) {
    val directory = path.substringBeforeLast("/")

    val fullName = path.substringAfterLast("/")

    val fileName = fullName.substringBeforeLast(".")

    val extension = fullName.substringAfterLast(".")
    println("Dir: $directory, fileName: $fileName, ext: $extension")

    //Dir: /User/kerwin/book, fileName: readme, ext: md
}

>>> parsePath("/User/kerwin/book/readme.md")

使用正則表達式解析文件路徑

fun parsePath(path: String) {
    // 正則表達式寫在一個 三重引號的字符串中,不須要對任何字符進行轉義,包括反斜線,因此能夠用\.而不是\\.表示點
    // (.+) 目錄,/ 最後一個斜線,(.+) 文件名,\. 最後一個點,(.+) 擴展名
    val regex = """(.+)/(.+)\.(.+)""".toRegex()
    val matchResult = regex.matchEntire(path)
    if (matchResult != null) {
        val (directory, fileName, extension) = matchResult.destructured
        println("Dir: $directory, fileName: $fileName, ext: $extension")
    }
}

多行三重引號的字符串

能夠避免轉義字符,且能夠包含任何字符,包括換行符,用於格式化代碼的縮進。

val string = """| //
                   .|//
                   .|/ \ """
// 能夠去掉縮進
// 先向字符串內容添加前綴,標記邊距的結尾,而後調用trimMargin來刪除每行中的前綴和前面的空格
>>> println(string.trimMargin("."))
| //
|//
|/ \

6. 局部函數和擴展

帶重複代碼的函數

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}, name is empty.")
    }

    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}, address is empty.")
    }

    // save user ...
}

提取局部函數來避免重複,在佈局函數中能夠訪問外層函數的參數

fun saveUser(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}, $fieldName is empty.")
        }
    }

    validate(user.name, "name")
    validate(user.address, "address")

    // save user ...
}

提取邏輯到擴展函數

class User(val id: Int, val name: String, val address: String)

fun User.validateBeforeSave(value: String, fieldName: String) {
    if (value.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${this.id}, $fieldName is empty.")
    }
}

fun saveUser(user: User) {
    user.validateBeforeSave(user.name, "name")
    user.validateBeforeSave(user.address, "address")
    // save user ...
}

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索