Kotlin の概要 (完全版)

1. ハローワールド

国際的な慣例によれば、新しい言語の学習は通常、「Hello World」を印刷することから始まります。

package main

fun main() {
    
    
    val msg: String = "Hello World"
    println(msg)
}

この単純な関数から、kotlin と Java のいくつかの違いを列挙できます。

  1. 関数はクラスに入れずにファイルの最外部レベルで定義できます。
  2. キーワードfun を使用して関数を宣言します
  3. mainメソッドのパラメータは省略可能
  4. パラメータの型は変数名の後に記述されるため、型が自動的に推定されるときに型宣言を省略できます。
  5. println代わりにSystem.out.println、これは kotlin 標準ライブラリによって提供される Java 標準ライブラリ関数の単純なラッパーを使用してください
  6. コード末尾のセミコロンは省略可能

2. パッケージ

すべての kotlin ファイルはpackageで始まり、同じファイル内で定義されたすべての宣言 (クラス、関数、プロパティ) がこのパッケージに配置されます。同じパッケージ内の宣言は直接使用できますが、異なるパッケージ内の宣言はインポートして使用する必要があります

パッケージ宣言はファイルの先頭に配置され、その後に import ステートメントが続きます

package base

import java.text.SimpleDateFormat
import java.util.*

Kotlin はインポートがクラスであるか関数であるかを区別しません。importキーワードを使用してあらゆる種類の宣言をインポートできます。さらに、パッケージ名の後に . * を追加すると、パッケージ内で定義されたすべての宣言をインポートできるため、パッケージ内で定義されたクラス、トップレベル関数、およびプロパティを直接参照できます

package learn.package2

val index = 10

fun Test(status: Boolean) = println(status)

class Point(val x: Int, val y: Int) {
    
    

    val isEquals1: Boolean
        get() {
    
    
            return x == y
        }

    val isEquals2
        get() = x == y

    var isEquals3 = false
        get() = x > y
        set(value) {
    
    
            field = !value
        }

}
package learn.package1

import learn.package2.Point
import learn.package2.Test
import learn.package2.index

fun main() {
    
    
    val point = Point(10, index)
    Test(true)
}

Java 言語では、クラスはパッケージ構造と一致するフォルダー ディレクトリ構造に配置し、ファイル名はクラス名と同じにする必要があると規定されています。Kotlin では複数のクラスを同じファイルに配置することができ、ファイル名も任意に選択できます。Kotlin はフォルダー ディレクトリに制限を課しておらず、パッケージ階層も同じディレクトリ階層に従う必要はありませんが、Kotlin 関係者は依然として、パッケージ宣言に従ってソース コード ファイルを対応するディレクトリに配置することを推奨しています。

パッケージ名に名前の競合がある場合は、as キーワードを使用して、競合する項目の名前をローカルで変更して曖昧さを解消できます。

import learn.package1.Point
import learn.package2.Point as PointTemp //PointTemp 可以用来表示 learn.package2.Point 了

kotlin にも、タイプ エイリアスと呼ばれる、既存のタイプの名前を変更するために使用できる同様の概念があります。型エイリアスは、既存の型に代替名を提供するために使用されます。型名が比較的長い場合は、より短い、またはより簡潔な名前を導入することで覚えやすくなります。

型エイリアスは新しい型を導入せず、基になる型に引き続き対応するため、次のコードで出力されるクラス型は一貫しています。

class Base {
    
    

    class InnerClass

}

typealias BaseInner = Base.InnerClass

fun main() {
    
    
    val baseInner1 = Base.InnerClass()
    val baseInner2 = BaseInner()
    println(baseInner1.javaClass) //class test2.Base$InnerClass
    println(baseInner2.javaClass) //class test2.Base$InnerClass
}

3. 変数とデータ型

Java では、ほとんどの変数は変更可能 (非 Final) です。つまり、変数にアクセスできるコードはすべてその変数を変更できます。kotlin では、変数は可変変数 (var)不変変数 (val)の2 種類に分類できます。

変数を宣言するためのキーワードは 2 つあります。

  • val(value / variable+final) – 不変参照。val を使用して宣言された変数は、Java の最終変数に相当する、初期化後に再割り当てすることはできません。
  • var (変数) - 変数参照。var 変数の値は変更できます。Java の非final変数に対応します。

不変変数は代入後に状態を変更できなくなるため、変更できず、すべてのスレッドがアクセスするオブジェクトが同じであるため、アクセス制御を行う必要がないため、不変変数はスレッドセーフであると言えます。開発者は可能な限り不変変数を使用する必要があります。そうすることで、コードを関数型プログラミングのスタイルに近づけることができます。

プログラミング分野でも、val、不変オブジェクト、純粋関数を可能な限り使用してプログラムを設計するという開発原則が提唱されています。これにより、副作用の影響を可能な限り回避することができます。

fun main() {
    
    
    //只读变量即赋值后不可以改变值的变量,用 val 声明
    //声明一个整数类型的不可变变量
    val intValue: Int = 100

    //声明一个双精度类型的可变变量
    var doubleValue: Double = 100.0
}

変数を宣言する場合、通常は変数の型を明示的に指定する必要はありません。変数の型はコンテキストに基づいてコンパイラによって自動的に推定されます。宣言時に読み取り専用変数に初期デフォルト値がない場合は、変数の型を指定する必要があり、使用前に各分岐条件で変数を初期化する必要があります。そうしないと、コンパイラによって例外が報告されます。

fun main() {
    
    
    val intValue = 1002222222222
    val doubleValue = 100.0
    val longValue = 100L

    //如果只读变量在声明时没有初始值,则必须指明变量类型
    val intValue2: Int
    if (false) {
    
    
        intValue2 = 10
    }
    println(intValue2) //error, Variable 'intValue2' must be initialized
}

1. 基本的なデータ型

Java とは異なり、kotlin はプリミティブ データ型とそのラッパー クラスを区別しません。kotlin 内のすべてのものはオブジェクトであり、そのメンバー関数とプロパティは任意の変数で呼び出すことができます。Kotlin には Java のような元の基本型はありませんが、byte、char、integer、float、boolean などの型は予約されていますが、すべてオブジェクトとして存在します。

基本的な型の場合、kotlin には Java と比較していくつかの特別な機能があります

  • 数値、文字、およびブール値は実行時にプリミティブ値として表現できますが、開発者にとっては通常のクラスのように見えます。
  • Java では int を暗黙的に long に変換できますが、数値には暗黙的な拡張変換はありません。
  • Int の最大値を超えない整数値で初期化された変数はすべて自動的に Int 型として推定され、初期値がその最大値を超える場合は Long 型として推定されます。値を明示的に指定する必要がある場合は、 Long 型の場合は、値に設定できます。末尾に L を付加します
  • 文字を数値と見なすことはできません
  • 8 進数はサポートされていません
//在 kotlin 中,int、long、float 等类型仍然存在,但是是作为对象存在的

val intIndex: Int = 100
//等价于,编译器会自动进行类型推导
val intIndex = 100

//数字类型不会自动转型,必须要进行明确的类型转换
val doubleIndex: Double = intIndex.toDouble()
//以下代码会提示错误,需要进行明确的类型转换
//val doubleIndex: Double = intIndex

val intValue: Int = 1
val longValue: Long = 1
//以下代码会提示错误,因为两者的数据类型不一致,需要转换为同一类型后才能进行比较
//println(intValue == longValue)

//Char 不能直接作为数字来处理,需要主动转换
val ch: Char = 'c'
val charValue: Int = ch.toInt()
//以下代码会提示错误
//val charValue: Int = ch

//二进制
val value1 = 0b00101
//十六进制
val value2 = 0x123

また、kotlin の null 許容型は Java のプリミティブ データ型で表すことができません。null は Java の参照型の変数にのみ格納できるためです。つまり、プリミティブ データ型の null 許容バージョンが使用されている限り、それはコンパイルされて対応する包装タイプ

//基本数据类型
val intValue_1: Int = 200
//包装类
val intValue_2: Int? = intValue_1
val intValue_3: Int? = intValue_1
//== 比较的是数值相等性,因此结果是 true
println(intValue_2 == intValue_3)
//=== 比较的是引用是否相等,因此结果是 false
println(intValue_2 === intValue_3)

intValue_1 の値が 100 の場合、intValue_2 === intValue_3 の比較結果が true であることがわかります。これには、Java によるラッパー オブジェクトの再利用が含まれます。

2.文字列

Java と同様、kotlin は String 型を使用して文字列を表します。文字列は不変です。インデックス演算子を使用して、文字列に含まれる[]個々の文字にアクセスしたり、for ループを使用して文字列を反復したりできます。また、+ を使用して文字列を接続することもできます

val str = "leavesCZY"
println(str[1])
for (c in str) {
    
    
    println(c)
}
val str1 = str + " hello"

kotlin は文字列リテラルで参照されるローカル変数をサポートしています。変数名の前に文字 $ を追加するだけで済みます。また、中括弧で囲まれた式を含めることもできます。式は自動的に評価され、結果は文字列中央にマージされます。

val intValue = 100
//可以直接包含变量
println("intValue value is $intValue") //intValue value is 100
//也可以包含表达式
println("(intValue + 100) value is ${
      
      intValue + 100}")   //(intValue + 100) value is 200

生の文字列でリテラル ($) 文字 (バックスラッシュ エスケープをサポートしていない) を表す必要がある場合は、次の構文を使用します。

val price = "${
      
      '$'}100.99"
println(price)  //$100.99

3. アレイ

kotlin の配列は、要素の型が対応する型パラメーターとして指定される型パラメーターを持つクラスであり、Array クラスで表され、get 関数と set 関数を定義します (演算子のオーバーロード規則に従って、これは [] に変換されます)。サイズ属性など

配列を作成するにはいくつかの方法があります。

  1. arrayOf 関数を使用して、要素が関数の引数として指定される配列を作成します。
  2. arrayOfNulls を使用して、指定されたサイズの配列を作成します。含まれるすべての要素は null であり、null にできる要素タイプを含む配列を作成する場合にのみ使用できます。
  3. Array クラスのコンストラクターを呼び出し、配列のサイズとラムダ式を渡し、ラムダ式を呼び出して各配列要素を作成します
//包含给定元素的字符串数组
val array1 = arrayOf("leavesCZY", "叶", "https://github.com/leavesCZY")

array1[0] = "leavesCZY"
println(array1[1])
println(array1.size)

//初始元素均为 null ,大小为 10 的字符数组
val array2 = arrayOfNulls<String>(10)

//创建从 “a” 到 “z” 的字符串数组
val array3 = Array(26) {
    
     i -> ('a' + i).toString() }

配列型の型パラメーターは常にオブジェクト型になるため、 Array<Int>の宣言はボックス型 (java.lang.Integer) を含む配列になることに注意することが重要です。ボックス化せずにプリミティブ型の配列を作成する場合は、プリミティブ型の配列に特別なクラスを使用する必要があります。

基本データ型の配列を表すために、kotlin は各基本データ型に対応するクラスをいくつか提供し、特別な最適化を行います。たとえば、IntArray 、 ByteArray 、 BooleanArrayなどの型があります。これらの型は、 int[]、byte[]、boolean[] などの一般的な Java 基本データ型の配列にコンパイルされ、その値はこれらの配列はボックス化されていませんが、可能な限り最も効率的な方法です。IntArrayなどはArrayのサブクラスではないことに注意してください。

プリミティブ データ型の配列を作成するには、いくつかの方法があります。

  1. 配列サイズを、対応するタイプのクラス (IntArray など) のコンストラクターに渡します。これにより、対応する基本データ型のデフォルト値で初期化された配列が返されます。
  2. 各要素の初期化に使用される配列サイズとラムダを、対応するタイプのクラス (IntArray など) のコンストラクターに渡します。
  3. 可変長パラメーターの値をファクトリ関数 (charArrayOf など) に渡して、指定された要素値の配列を取得します。
//指定数组大小,包含的元素将是对应基本数据类型的默认值(int 的默认值是 0)
val intArray = IntArray(5)
//指定数组大小以及用于初始化每个元素的 lambda
val doubleArray = DoubleArray(5) {
    
     Random().nextDouble() }
//接收变长参数的值来创建存储这些值的数组
val charArray = charArrayOf('H', 'e', 'l', 'l', 'o')

4. どれも、そしてどれも?

Any 型は、Int のようなプリミティブ データ型を含む、kotlin のすべての null 非許容型のスーパータイプです。

基本データ型の値が Any 型の変数に割り当てられている場合、その値は自動的にボックス化されます。

val any: Any = 100
println(any.javaClass) //class java.lang.Integer

null を含むすべての可能な値を変数に格納したい場合は、Any? を使用する必要があります。

val any: Any? = null

5、ユニット

kotlin の Unit 型は Java の void に似ており、関数に戻り値がない場合に使用できます。

fun check(): Unit {
    
    

}

//如果返回值为 Unit,则可以省略该声明
fun check() {
    
    

}

Unit は完全な型であり、型パラメーターとして使用できますが、void は使用できません。

interface Test<T> {
    
    
    fun test(): T
}

class NoResultClass : Test<Unit> {
    
    
    
    //返回 Unit,但可以省略类型说明,函数也不需要显式地 return 
    override fun test() {
    
    

    }

}

6、何もない

Nothing 型には値がありません。関数の戻り値として、またはジェネリック関数の戻り値の型パラメーターとして使用される場合にのみ意味を持ちます。Nothing を使用すると、関数が正常に終了しないことを示すことができます。コンパイラが診断用のコードを理解できるように支援する

コンパイラは、戻り値が Nothing 型の関数は決して正常に終了しないことを知っているため、name1 が null の場合の分岐処理は常に例外をスローするため、コンパイラは name1 の型が null ではないと推論します。

data class User(val name: String?)

fun fail(message: String): Nothing {
    
    
    throw IllegalStateException(message)
}

fun main() {
    
    
    val user = User("leavesC")
    val name = user.name ?: fail("no name")
    println(name) //leavesC

    val user1 = User(null)
    val name1 = user1.name ?: fail("no name")
    println(name1.length) //IllegalStateException
}

4. 機能

kotlin の関数はキーワード fun で始まり、その後に関数名、その後に括弧で囲まれたパラメーター リストが続きます。関数に戻り値がある場合は、戻り値の型が追加され、パラメーター リストからコロンで区切られます。

//fun 用于表示声明一个函数,getNameLastChar 是函数名
//空括号表示该函数无传入参数,Char 表示函数的返回值类型是字符
fun getNameLastChar(): Char {
    
    
    return name.get(name.length - 1)
}

//带有两个不同类型的参数,一个是 String 类型,一个是 Int 类型
//返回值为 Int 类型
fun test1(str: String, int: Int): Int {
    
    
    return str.length + int
}

また、式関数本体の戻り値の型は省略可能であり、戻り値の型を自動的に推測することができ、このように単一行の式と等号で定義された関数を式関数本体と呼びますただし、通常の状況で戻り値を持つコード ブロック関数本体の場合、戻り値の型と return ステートメントを明示的に記述する必要があります。

//getNameLastChar 函数的返回值类型以及 return 关键字是可以省略的
//返回值类型可以由编译器根据上下文进行推导
//因此,函数可以简写为以下形式
fun getNameLastChar() = name.get(name.length - 1)

関数に意味のある戻り値がない場合は、Unit として宣言するか、Unit を省略できます。

以下の 3 つの書き方は同等です

fun test(str: String, int: Int): Unit {
    
    
    println(str.length + int)
}

fun test(str: String, int: Int) {
    
    
    println(str.length + int)
}

fun test(str: String, int: Int) = println(str.length + int)

1. 名前付きパラメータ

コードの可読性を高めるために、kotlin では名前付きパラメーターを使用できます。つまり、関数を呼び出すときに、パラメーターの意味と機能を明確に表現するために関数のパラメーター名を一緒にマークできますが、パラメータ名の指定 その後、後続のすべてのパラメータに名前を付ける必要があります。

fun main() {
    
    
    //错误,在指定了一个参数的名称后,之后的所有参数都需要标明名称
    //compute(index = 110, "leavesC")
    compute(index = 120, value = "leavesC")
    compute(130, value = "leavesC")
}

fun compute(index: Int, value: String) {
    
    

}

2. パラメータのデフォルト値

オーバーロードされた関数の作成を避けるために、関数を宣言するときにパラメーターのデフォルト値を指定できます。

fun main() {
    
    
    compute(24)
    compute(24, "leavesC")
}

fun compute(age: Int, name: String = "leavesC") {
    
    

}

上記の例の場合、従来の呼び出し構文を使用する場合、パラメータは関数宣言で定義されたパラメータの順序で指定する必要があり、最後のパラメータのみを省略できます。

fun main() {
    
    
    //错误,不能省略参数 name
    // compute(24)
    // compute(24,100)
    //可以省略参数 value
    compute("leavesC", 24)
}

fun compute(name: String = "leavesC", age: Int, value: Int = 100) {
    
    }

名前付きパラメータを使用する場合、デフォルト値を持つパラメータを省略したり、必須パラメータを任意の順序で渡すこともできます。

fun main() {
    
    
    compute(age = 24)
    compute(age = 24, name = "leavesC")
    compute(age = 24, value = 90, name = "leavesC")
    compute(value = 90, age = 24, name = "leavesC")
}

fun compute(name: String = "leavesC", age: Int, value: Int = 100) {
    
    

}

3. 可変パラメータ

変数パラメーターを使用すると、任意の数のパラメーターを配列にパックして関数に渡すことができます。Kotlin の構文は Java の構文とは異なります。代わりに、vararg キーワードを使用して変数パラメーターを宣言します。

たとえば、次の関数呼び出しはすべて正しいです。

fun main() {
    
    
    compute()
    compute("leavesC")
    compute("leavesC", "leavesc")
    compute("leavesC", "leavesc", "叶")
}

fun compute(vararg name: String) {
    
    
    name.forEach {
    
     println(it) }
}

Java では、配列を変数パラメータに直接渡すことができますが、kotlin では、各配列要素を関数内の個別のパラメータとして呼び出すことができるように、配列を明示的にアンパックする必要があります。この機能はスプレッド演算子と呼ばれ、 * 配列パラメータの前

fun main() {
    
    
    val names = arrayOf("leavesC", "leavesc", "叶")
    compute(* names)
}

fun compute(vararg name: String) {
    
    
    name.forEach {
    
     println(it) }
}

4. ローカル機能

Kotlin は関数内で関数のネストをサポートしており、ネストされた関数はローカル関数と呼ばれます

fun main() {
    
    
    compute("leavesC", "country")
}

fun compute(name: String, country: String) {
    
    
    fun check(string: String) {
    
    
        if (string.isEmpty()) {
    
    
            throw IllegalArgumentException("参数错误")
        }
    }
    check(name)
    check(country)
}

5. 式と条件ループ

1. ステートメントと式

ここで、「ステートメント」と「式」という 2 つの概念を区別する必要があります。ステートメントとは、独立して実行して実際の効果をもたらすことができるコードであり、代入ロジック、印刷動作​​、フロー制御などの形で表現されます。式には、値、変数、定数、演算子、またはそれらの組み合わせを指定できます。式は、戻り値を含むステートメントとみなすことができます。

たとえば、次の代入操作、フロー制御、および出力はすべてステートメントであり、全体として存在し、戻り値は含まれません。

val a = 10
for (i in 0..a step 2) {
    
    
    println(i)
}

表現の例をいくつか見てみましょう

1       //字面表达式,返回 1

++1     //也属于表达式,自增会返回 2

//与 Java 不同,kotlin 中的 if 是作为表达式存在的,其可以拥有返回值
fun getLength(str: String?): Int {
    
    
    return if (str.isNullOrBlank()) 0 else str.length
}

2. if 式

if の分岐はコード ブロックにすることができ、最後の式はブロックの戻り値として使用されます。

val maxValue = if (20 > 10) {
    
    
    println("maxValue is 20")
    20
} else {
    
    
    println("maxValue is 10")
    10
}
println(maxValue) //20

次のコードは、 if の戻り値をJava の三項演算子の置き換えに使用できるため、kotlin には三項演算子がないことが明確にわかります。

val list = listOf(1, 4, 10, 4, 10, 30)
val value = if (list.size > 0) list.size else null
println(value)  //6

あるコマンドを実行するために if 式分岐を使用した場合、このときの戻り値の型は Unit となり、このときの if 文は Java と同じになります。

val value1 = if (list.size > 0) println("1") else println("2")
println(value1.javaClass)   //class kotlin.Unit

if をステートメントではなく式として使用する場合 (例: 値を返す、または変数に代入する)、式には else 分岐が必要です。

3.表現するとき

when 式はJava のswitch/caseに似ていますが、より強力です。when は式またはステートメントとして使用できます。パラメータをすべての分岐条件と順番に比較し、1 つの分岐が条件を満たすまで、右側の式を実行します。when が式として使用される場合、修飾されたブランチの値は式全体の値となり、ステートメントとして使用される場合、個々のブランチの値は無視されます。Java の switch/case との違いは、when 式のパラメータは任意の型にでき、分岐も条件にできることです。

if と同様に、when 式の各分岐はコード ブロックにすることができ、その値はコード ブロック内の最後の式の値になります。他の分岐が条件を満たさない場合、else 分岐で評価されます。

when を式として使用する場合は、考えられるすべてのケースがカバーされていることをコンパイラーが検出できない限り、else 分岐が必要です。多くの分岐を同じ方法で処理する必要がある場合は、複数の分岐条件をまとめてカンマで区切ることができます。

val value = 2
when (value) {
    
    
    in 4..9 -> println("in 4..9") //区间判断
    3 -> println("value is 3")    //相等性判断
    2, 6 -> println("value is 2 or 6")    //多值相等性判断
    is Int -> println("is Int")   //类型判断
    else -> println("else")       //如果以上条件都不满足,则执行 else
}
fun main() {
    
    
    //返回 when 表达式
    fun parser(obj: Any): String =
            when (obj) {
    
    
                1 -> "value is 1"
                "4" -> "value is string 4"
                is Long -> "value type is long"
                else -> "unknown"
            }
    println(parser(1))
    println(parser(1L))
    println(parser("4"))
    println(parser(100L))
    println(parser(100.0))
}

value is 1
value type is long
value is string 4
value type is long
unknown

さらに、when ステートメントはパラメーターなしで使用することもできます。

when {
    
    
    1 > 5 -> println("1 > 5")
    3 > 1 -> println("3 > 1")
}

4.forループ

Java の for ループの最も類似した形式は次のとおりです。

val list = listOf(1, 4, 10, 34, 10)
for (value in list) {
    
    
    println(value)
}

インデックスによるトラバース

val items = listOf("H", "e", "l", "l", "o")
//通过索引来遍历List
for (index in items.indices) {
    
    
    println("${
      
      index}对应的值是:${
      
      items[index]}")
}

各ループで現在のインデックスと対応する値を取得することも可能です

val list = listOf(1, 4, 10, 34, 10)
for ((index, value) in list.withIndex()) {
    
    
    println("index : $index , value :$value")
}

サイクル間隔をカスタマイズすることもできます

for (index in 2..10) {
    
    
    println(index)
}

5. while および do/while ループ

while と do/while は Java とあまり変わりません

val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
while (index < list.size) {
    
    
    println(list[index])
    index++
}
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
do {
    
    
    println(list[index])
    index++
} while (index < list.size)

6. 戻ってジャンプ

Kotlin には 3 つの構造化されたジャンプ式があります。

  • return のデフォルトは、直前に囲んでいる関数または無名関数からの戻り値です。
  • Break は、ループを直接囲むループを終了します。
  • continue は、次に近いループを囲むループに続きます。

kotlin の任意の式はラベルでマークできます。ラベルの形式は、識別子の後に @ 記号が続きます。例: abc@、fooBar@は有効なラベルです

fun main() {
    
    
    fun1()
}

fun fun1() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    loop@ for (it in list) {
    
    
        if (it == 8) {
    
    
            continue
        }
        if (it == 23) {
    
    
            break@loop
        }
        println("value is $it")
    }
    println("function end")
}
value is 1
value is 4
value is 6
value is 12
function end

kotlin には関数リテラル、ローカル関数、オブジェクト式があります。したがって、kotlin関数はネストできます

タグ制限された戻り値を使用すると、外部関数から戻ることができます。最も重要な用途の 1 つは、ラムダ式から戻ることです。多くの場合、ラムダを受け入れる関数と同じ名前の暗黙的なラベルを使用する方が便利です。

fun main() {
    
    
    fun1()
    fun2()
    fun3()
    fun4()
    fun5()
}

fun fun1() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
    
    
        if (it == 8) {
    
    
            return
        }
        println("value is $it")
    }
    println("function end")

//    value is 1
//    value is 4
//    value is 6
}

fun fun2() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
    
    
        if (it == 8) {
    
    
            return@fun2
        }
        println("value is $it")
    }
    println("function end")

//    value is 1
//    value is 4
//    value is 6
}

//fun3() 和 fun4() 中使用的局部返回类似于在常规循环中使用 continue
fun fun3() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
    
    
        if (it == 8) {
    
    
            return@forEach
        }
        println("value is $it")
    }
    println("function end")
    
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun4() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach loop@{
    
    
        if (it == 8) {
    
    
            return@loop
        }
        println("value is $it")
    }
    println("function end")

//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun5() {
    
    
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
    
    
        if (value == 3) {
    
    
            //局部返回到匿名函数的调用者,即 forEach 循环
            return
        }
        println("value is $value")
    })
    println("function end")
}

6. インターバル

範囲式では、...演算子を使用して閉じた範囲を宣言します。これは、RangTo メソッドの実装を定義するために使用されます。

次の 3 つの宣言は同等です

var index = 5

if (index >= 0 && index <= 10) {
    
    

}

if (index in 0..10) {
    
    

}

if (index in 0.rangeTo(10)) {
    
    

}

数値型の範囲が反復されると、コンパイラは最適化のために Java のインデックスを使用して、数値型を for ループと同じバイトコードに変換します。

範囲はデフォルトで自動増加するため、次のようなコードは実行されません。

for (index in 10..0) {
    
    
    println(index)
}

downTo 関数を使用して、減少するように変更できます。

for (index in 10 downTo 0) {
    
    
    println(index)
}

範囲内のステップを使用して、ループを通過するたびに増加する長さを定義できます。

for (index in 1..8 step 2){
    
    
    println(index)
}
for (index in 8 downTo 1 step 2) {
    
    
    println(index)
}

上記の宣言はすべて閉じた間隔ですが、開いた間隔を宣言したい場合は、until 関数を使用できます。

for (index in 0 until 4){
    
    
    println(index)
}

拡張関数をreversed()使用すると、間隔を反転した後にシーケンスを返すことができます。

val rangeTo = 1.rangeTo(3)
for (i in rangeTo) {
    
    
    println(i) //1  2  3
}
for (i in rangeTo.reversed()) {
    
    
    println(i) //3  2  1
}

7、修飾子

1. 決勝とオープン

kotlin のクラスとメソッドはデフォルトで Final、つまり継承不可能です。クラスのサブクラスの作成を許可したい場合は、open 修飾子を使用してこのクラスを識別する必要があります。また、次のことも必要です。オーバーライドする各属性を指定し、オープン修飾子を追加するメソッドを追加します

open class View {
    
    
    open fun click() {
    
    

    }
	//不能在子类中被重写
    fun longClick() {
    
    

    }
}

class Button : View() {
    
    
    override fun click() {
    
    
        super.click()
    }
}

基本クラスまたはインターフェイスのメンバーがオーバーライドされると、オーバーライドされたメンバーもデフォルトでオープンになります。たとえば、Button クラスが開いている場合、そのサブクラスも click() メソッドをオーバーライドできます。

open class Button : View() {
    
    
    override fun click() {
    
    
        super.click()
    }
}

class CompatButton : Button() {
    
    
    override fun click() {
    
    
        super.click()
    }
}

クラスのサブクラスでこのメソッドの実装をオーバーライドしたい場合は、オーバーライドされたメンバーを明示的に Final としてマークできます。

open class Button : View() {
    
    
    override final fun click() {
    
    
        super.click()
    }
}

2、公共

public 修飾子は最も制限の少ない修飾子であり、デフォルトの修飾子です。public として定義されたメンバーが変更された private クラスに含まれている場合、このメンバーはこのクラスの外部には表示されません。

3、保護されている

protected 修飾子は、クラスまたはインターフェイスのメンバーに対してのみ使用できます。Java では、保護されたメンバーは同じパッケージからアクセスできますが、kotlin の場合、保護されたメンバーはそのクラスとそのサブクラスでのみ表示されます。また、protected はトップレベルの宣言には適用されません。

4、内部

内部として定義されたパッケージ メンバーは、それが含まれるモジュール全体には表示されますが、他のモジュールには表示されません。たとえば、特定のクラスを含むオープン ソース ライブラリを公開したいとします。このクラスはライブラリ自体からグローバルに参照できるようにしたいと考えていますが、外部ユーザーは参照できません。このとき、次のようにすることを選択できます。この目的を達成するために内部として宣言されました

Jetbrains の定義によれば、モジュールは単一の機能単位である必要があり、一緒にコンパイルされた kotlin ファイルのコレクションとみなすことができ、個別にコンパイル、実行、テスト、デバッグできる必要があります。これは、Android Studio のメイン プロジェクト、および Eclipse のワークスペース内のさまざまなプロジェクトによって参照されるモジュールに相当します。

5、プライベート

The private 修飾子は最も制限的な修飾子です。Kotlin では、クラス、関数、プロパティなどのトップレベルの宣言でプライベートの可視性を使用できます。つまり、それが配置されているファイル内でのみ可視になります。このクラスが定義されている場所以外では使用できません。また、クラス内で private 修飾子を使用した場合、アクセス権はそのクラスに限定され、このクラスを継承するサブクラスでは使用できなくなります。したがって、クラス、オブジェクト、インターフェイスなどがプライベートとして定義されている場合、それらは定義されているファイルにのみ表示されます。クラスまたはインターフェイスで定義されている場合、それらはそのクラスまたはインターフェイスにのみ表示されます

6. まとめ

修飾子 クラスのメンバー トップレベルの宣言
パブリック (デフォルト) どこでも見える どこでも見える
内部 モジュール内に表示されます モジュール内に表示されます
保護された サブクラスで表示される
プライベート クラスで見える ファイル内に表示される

8. 航空の安全

1. null 可能性

kotlin では、型システムは参照を、null を保持できる (null 参照) か、null を保持できない (非 null 参照) の 2 つの型に分割します。たとえば、String 型の通常の変数は null を指すことはできません。

var name: String = "leavesC"
//编译错误
//name = null

変数に null 参照を格納したい場合は、型名の後に明示的に疑問符を追加する必要があります。

var name: String? = "leavesC"
name = null

疑問符を任意の型の後に追加して、この型の変数が null 参照を格納できることを示すことができますInt?、Doubld? 、Long?

Kotlin の Nullable 型に対する明示的なサポートは、NullPointerExceptionによって引き起こされる例外を防ぐのに役立ちます。コンパイラは、プロパティの null をチェックせずに null 許容変数を直接呼び出すことを許可しません。この強制的な規制により、開発者はコーディングの初期段階で変数の割り当て可能な範囲を考慮し、状況に応じて分岐を行う必要があります。

fun check(name: String?): Boolean {
    
    
    //error,编译器不允许不对 name 做 null 检查就直接调用其属性
    return name.isNotEmpty()
}

正しいアプローチは、null を明示的にチェックすることです。

fun check(name: String?): Boolean {
    
    
    if (name != null) {
    
    
        return name.isNotEmpty()
    }
    return false
}

2. 安全な通話オペレーター: ?。

安全な呼び出し演算子: ?.null チェックとメソッド呼び出しを 1 つの操作に組み合わせることができます。変数値が空でない場合はメソッドまたはプロパティが呼び出され、そうでない場合は直接 null を返します。

たとえば、次の 2 つの書き方は完全に同等です。

fun check(name: String?) {
    
    
    if (name != null) {
    
    
        println(name.toUpperCase())
    } else {
    
    
        println(null)
    }
}

fun check(name: String?) {
    
    
    println(name?.toUpperCase())
}

3. エルヴィス演算子: ?:

Elvis 演算子:デフォルト値 null を直接返すケースを置き換えるために使用されます。Elvis 演算子は 2 つのオペランドを受け取ります。最初のオペランドが null でない場合、演算結果はその演算結果の値です。最初のオペランドが null の場合?:?.演算の結果は 2 番目のオペランドです

たとえば、次の 2 つの書き方は完全に同等です。

fun check(name: String?) {
    
    
    if (name != null) {
    
    
        println(name)
    } else {
    
    
        println("default")
    }
}

fun check(name: String?) {
    
    
    println(name ?: "default")
}

4. 安全な変換演算子: as?

安全な変換演算子:as?値を指定された型に変換するために使用され、値がその型に適さない場合は null を返します。

fun check(any: Any?) {
    
    
    val result = any as? String
    println(result ?: println("is not String"))
}

5. 非 null アサーション:!!

非 null アサーションは、任意の値を非 null 型に変換するために使用されます。null 値に対して非 null アサーションが作成されると、例外がスローされます。

fun main() {
    
    
    var name: String? = "leavesC"
    check(name) //7

    name = null
    check(name) //kotlinNullPointerException
}

fun check(name: String?) {
    
    
    println(name!!.length)
}

6. Null 許容型の拡張

null 許容型の拡張関数を定義すると、null 値を処理するためのより強力な方法となり、呼び出し元を null にすることができ、変数が null でないことを確認してからメソッドを呼び出すのではなく、関数内で null を処理できます。

たとえば、次のメソッドは null ポインタ例外を発生させずに正常に呼び出すことができます。

val name: String? = null
println(name.isNullOrEmpty()) //true

isNullOrEmpty()メソッドのシグネチャは次のとおりです。これは、null 許容型CharSequence?に対して定義された拡張関数であり、メソッド呼び出し元が null である場合をメソッドがすでに処理していることがわかります。

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    
    
    contract {
    
    
        returns(false) implies (this@isNullOrEmpty != null)
    }
    return this == null || this.length == 0
}

7. プラットフォームの種類

プラットフォームタイプはJava用のkotlinで作られたバランス設計です。Kotlin はオブジェクト タイプを null 許容型と null 非許容型に分割しますが、Java プラットフォーム上のすべてのオブジェクト タイプは null 許容です。Kotlin で Java 変数を参照するときに、すべての変数が null 許容型として分類されている場合、余分な null が大量に発生することになります。チェック; それらをすべて null 非許容型として扱うと、NPE のリスクを無視したコードを簡単に作成できます。2 つのバランスを取るために、kotlin はプラットフォーム型を導入します。つまり、kotlin で Java 変数値を参照する場合、null 許容型または非 null 許容型のいずれかとみなされ、開発者が決定します。 nullチェックを実装するかどうか

9. 型チェックと変換

1. 型チェック

is および !is演算子は、実行時にオブジェクトが指定された型に準拠しているかどうかをチェックするために使用されます。

fun main() {
    
    
    val strValue = "leavesC"
    parserType(strValue) //value is String , length : 7
    val intValue = 100
    parserType(intValue) //value is Int , toLong : 100
    val doubleValue = 100.22
    parserType(doubleValue) //value !is Long
    val longValue = 200L
    parserType(longValue) //unknown
}

fun parserType(value: Any) {
    
    
    when (value) {
    
    
        is String -> println("value is String , length : ${
      
      value.length}")
        is Int -> println("value is Int , toLong : ${
      
      value.toLong()}")
        !is Long -> println("value !is Long")
        else -> println("unknown")
    }
}

同時に、is 演算子にはスマート変換機能も付いています。多くの場合、コンパイラーは明示的な変換とともに不変の値をチェックし、必要に応じて安全な変換を自動的に挿入するため、kotlin で明示的な変換演算子を使用する必要はありません。

例えば上記の例で、値がString型であると判定された場合、その値をそのままString型の変数として使用し、その内部プロパティを呼び出すことができます。この処理をスマート変換と呼びます。

コンパイラは、コンテキストに従って変数を適切な型にインテリジェントに変換するように指定します。

if (value !is String) return
//如果 value 非 String 类型时直接被 return 了,所以此处可以直接访问其 length 属性
println(value.length)

// || 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value !is String || value.length > 0) {
    
    

}

// && 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value is String && value.length > 0) {
    
    

}

2. 安全でない変換演算子

変換が不可能な場合、変換演算子はas例外をスローします。したがって、安全でない変換演算子を呼び出します。

fun main() {
    
    
    parserType("leavesC") //value is String , length is 7
    parserType(10) //会抛出异常 ClassCastException
}

fun parserType(value: Any) {
    
    
    val tempValue = value as String
    println("value is String , length is ${
      
      tempValue.length}")
}

この型は null許容ではないため、null を String 変数に変換できないことに注意してください。

したがって、次の変換では例外がスローされます

val x = null
val y: String = x as String //会抛出异常 TypeCastException

変換可能な型は、一致安全性のために null 許容として宣言されます

val x = null
val y: String? = x as String?

3. 安全な変換演算子

安全な変換演算子 as? を使用すると、変換時に失敗時に null を返す例外がスローされるのを避けることができます。

val x = null
val y: String? = x as? String

上記の例の右側 as? は非 null 型 String ですが、変換結果は null 許容です

十、クラス

1. 基本的な考え方

クラスの概念は、データとそれを処理するコードを単一のエンティティにカプセル化することです。Java では、データにアクセスまたは変更するためのアクセサー メソッド (ゲッターおよびセッター)を提供することにより、データはプライベート フィールドに保存されます。

次のサンプル コードは Java で非常に一般的です。Point クラスには、コンストラクターを介して同じ名前のフィールドにパラメーターを割り当て、ゲッターを介してプロパティ値を取得するという繰り返しのコードが多数含まれています。

public final class Point {
    
    

   private final int x;
   
   private final int y;
   
   public Point(int x, int y) {
    
    
      this.x = x;
      this.y = y;
   }

   public final int getX() {
    
    
      return this.x;
   }

   public final int getY() {
    
    
      return this.y;
   }
   
}

kotlin を使用して Point クラスを宣言する場合、必要なコードは 1 行だけであり、この 2 つは完全に同等です。

class Point(val x: Int, val y: Int)

Kotlin では、キーワードclassを使用してクラスを宣言することもできます。クラス宣言は、クラス名、クラス ヘッダー (型パラメーター、プライマリ コンストラクターなどを指定)、および中括弧で囲まれたクラス本体で構成されます。クラス ヘッダーとクラス本体はオプションです。クラスに本体がない場合は、中括弧を省略できます。さらに、kotlin のクラスはデフォルトで公開 (パブリック) および最終 (継承不可) になります。

Kotlinは、プライマリ コンストラクター (クラス本体の外部で宣言) とセカンダリ コンストラクター (クラス本体の内部で宣言)を区別します。クラスには 1 つのプライマリ コンストラクターと複数のセカンダリ コンストラクターを持つことができます。また、追加のコンストラクターをinit初期化コード ブロックに追加することもできます。論理

2. メインコンストラクター

プライマリ コンストラクターは、クラス ヘッダーの一部であり、クラス名 (およびオプションの型パラメーター) に続きます。プライマリ コンストラクターのパラメーターは、変数 (var) または読み取り専用 (val) です。

class Point constructor(val x: Int, val y: Int) {
    
    

}

プライマリ コンストラクターに注釈や可視性修飾子がない場合は、コンストラクター キーワードを省略できます。

class Point(val x: Int, val y: Int) {
    
    

}

//如果不包含类体,则可以省略花括号
class Point(val x: Int, val y: Int)

コンストラクターに注釈または可視性修飾子があり、それらの修飾子がコンストラクターの前にある場合は、コンストラクター キーワードが必要です。

class Point public @Inject constructor(val x: Int, val y: Int) {
    
    

}

メイン コンストラクターにはコードを含めることはできません。初期化コードは、init キーワードをプレフィックスとして付けた初期化ブロックに配置できます。初期化ブロックには、クラスの作成時に実行されるコードが含まれます。メイン コンストラクターのパラメーターは、使用場所に含めることができます。初期化ブロック。必要に応じて、クラス内で複数の初期化ブロックを宣言することもできます。コンストラクターのパラメーターを val/var で変更する場合、クラス内で同じ名前のグローバル属性を宣言するのと同じであることに注意してください。変更のために val/var を追加しない場合、コンストラクターは init 関数ブロックとグローバル属性が初期化されるときにのみ参照できます。

さらに、クラスのインスタンスを作成するには、Java で new キーワードを使用する必要はなく、通常の関数と同じようにコンストラクターを呼び出すだけです。

class Point(val x: Int, val y: Int) {
    
    

    init {
    
    
        println("initializer blocks , x value is: $x , y value is: $y")
    }

}

fun main() {
    
    
    Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}

プライマリ コンストラクターのパラメーターは、クラス本体で宣言されたプロパティ初期化子でも使用できます。

class Point(val x: Int, val y: Int) {
    
    

    private val localX = x + 1

    private val localY = y + 1

    init {
    
    
        println("initializer blocks , x value is: $x , y value is: $y")
        println("initializer blocks , localX value is: $localX , localY value is: $localY")
    }

}

fun main() {
    
    
    Point(1, 2)
    //initializer blocks , x value is: 1 , y value is: 2
    //initializer blocks , localX value is: 2 , localY value is: 3
}

3. 二次コンストラクター

クラスは、constructor というプレフィックスを付けて 2 番目のコンストラクターを宣言することもできます。クラスにプライマリ コンストラクターがある場合、各セカンダリ コンストラクターはプライマリ コンストラクターに直接委任するか、間接委任の場合は別のセカンダリ コンストラクターに委任する必要があります。これは this キーワードで指定できます。

class Point(val x: Int, val y: Int) {
    
    

    private val localX = x + 1

    private val localY = y + 1

    init {
    
    
        println("initializer blocks , x value is: $x , y value is: $y")
        println("initializer blocks , localX value is: $localX , localY value is: $localY")
    }

    constructor(base: Int) : this(base + 1, base + 1) {
    
    
        println("constructor(base: Int)")
    }

    constructor(base: Long) : this(base.toInt()) {
    
    
        println("constructor(base: Long)")
    }

}

fun main() {
    
    
    Point(100)
    //initializer blocks , x value is: 101 , y value is: 101
    //initializer blocks , localX value is: 102 , localY value is: 102
    //constructor(base: Int)
    Point(100L)
    //initializer blocks , x value is: 101 , y value is: 101
    //initializer blocks , localX value is: 102 , localY value is: 102
    //constructor(base: Int)
    //constructor(base: Long)
}

初期化ブロック内のコードは実際にはプライマリ コンストラクターの一部となり、プライマリ コンストラクターへの委任はセカンダリ コンストラクターの最初のステートメントとなるため、初期化ブロック内のすべてのコードはセカンダリ コンストラクターの本体の前に実行されます。クラスにプライマリ コンストラクターがない場合でも、この委任は暗黙的に行われ、初期化ブロックは引き続き実行されます。非抽象クラスが (プライマリまたはセカンダリの) コンストラクタを宣言していない場合、パラメータを持たないパブリック プライマリ コンストラクタがデフォルトで生成されます。

4. プロパティ

Java では、フィールドとそのアクセサーの組み合わせをプロパティと呼びます。kotlin では、プロパティはファーストクラスの言語機能であり、フィールドとアクセサー メソッドを完全に置き換えます。クラスでのプロパティの宣言は、val キーワードと var キーワードを使用して変数を宣言することと同じです。val 変数にはゲッターが 1 つだけあり、var 変数にはゲッターとセッターの両方があります

fun main() {
    
    
    val user = User()
    println(user.name)
    user.age = 200
}

class User() {
    
    

    val name: String = "leavesC"

    var age: Int = 25

}

5. カスタム アクセサー

アクセサーのデフォルトの実装ロジックは単純です。値を格納するフィールド、プロパティの値を返すゲッター、プロパティの値を更新するセッターを作成します。必要に応じてアクセサーをカスタマイズすることもできます

たとえば、次の例ではカスタム アクセサーを使用して 3 つのプロパティを宣言しています。

class Point(val x: Int, val y: Int) {
    
    

    val isEquals1: Boolean
        get() {
    
    
            return x == y
        }

    val isEquals2
        get() = x == y

    var isEquals3 = false
        get() = x > y
        set(value) {
    
    
            field = !value
        }

}

アクセサーの可視性を変更するか、アクセサーにアノテーションを追加するだけの場合は、その実装を定義せずにアクセサーを定義できます。

fun main() {
    
    
    val point = Point(10, 10)
    println(point.isEquals1)
    //以下代码会报错
    //point.isEquals1 = true
}

class Point(val x: Int, val y: Int) {
    
    

    var isEquals1: Boolean = false
        get() {
    
    
            return x == y
        }
        private set
    
}

6. 遅延初期化

一般に、空でない型のプロパティはコンストラクターで初期化する必要がありますが、Dagger2 などの依存関係注入フレームワークを使用するプロジェクトでは非常に不便です。この状況に対処するために、lateinit 修飾子を使用してプロパティをマークできます。プロパティが後で初期化されることをコンパイラに伝える

lateinit で修飾されたプロパティまたは変数は、null 以外の型である必要があり、プリミティブ型であってはなりません

class Point(val x: Int, val y: Int)

class Example {
    
    

    lateinit var point: Point

    var point2: Point

    constructor() {
    
    
        point2 = Point(10, 20)
    }
    
}

初期化されていない lateinit 変数にアクセスすると、特定の理由 (変数が初期化されていない) を含む例外メッセージがスローされます。

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized

11、クラス分け

1. 抽象クラス

抽象として宣言されたクラスには、実装本体のないメンバー メソッドを含めることができ、そのメンバー メソッドにも抽象マークが付けられます。このようなクラスを抽象クラスと呼び、実装本体のないメソッドを抽象メソッドと呼びます。

また、抽象クラスまたは抽象メソッドに open の注釈を付ける必要はありません。これはデフォルトで宣言されているためです。

abstract class BaseClass {
    
    
    abstract fun fun1()
}

class Derived : BaseClass() {
    
    
    override fun fun1() {
    
    
        
    }
}

2. データクラス

データ クラスは、状態を保存するために使用される Java の POJO の定型コードの重複を回避する非常に強力なクラスですが、動作は非常に単純です。通常、プロパティにアクセスするための単純なゲッターとセッターのみを提供します。

新しいデータクラスの定義は次のように簡単です。

data class Point(val x: Int, val y: Int)

デフォルトでは、データ クラスはメイン コンストラクターで宣言されたすべてのプロパティに対して次のメソッドを生成します。

  • ゲッター、セッター (var である必要があります)
  • コンポーネントN()。プライマリコンストラクタの属性宣言の順序に対応
  • コピー()
  • toString()
  • ハッシュコード()
  • 等しい()

生成されたコードで一貫性のある有意義な動作を保証するには、データ クラスが次の要件を満たしている必要があります。

  • プライマリ コンストラクターにはパラメーターが含まれている必要があります
  • プライマリ コンストラクターのすべてのパラメーターは、val または var としてマークする必要があります。
  • データ クラスを抽象、オープン、シール、または内部にすることはできません

IDEA を使用して Point クラスの Java 実装を逆コンパイルして表示し、その内部実装を理解できます。

public final class Point {
    
    
   private final int x;
   private final int y;

   public final int getX() {
    
    
      return this.x;
   }

   public final int getY() {
    
    
      return this.y;
   }

   public Point(int x, int y) {
    
    
      this.x = x;
      this.y = y;
   }

   public final int component1() {
    
    
      return this.x;
   }

   public final int component2() {
    
    
      return this.y;
   }

   @NotNull
   public final Point copy(int x, int y) {
    
    
      return new Point(x, y);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static Point copy$default(Point var0, int var1, int var2, int var3, Object var4) {
    
    
      if ((var3 & 1) != 0) {
    
    
         var1 = var0.x;
      }

      if ((var3 & 2) != 0) {
    
    
         var2 = var0.y;
      }

      return var0.copy(var1, var2);
   }

   public String toString() {
    
    
      return "Point(x=" + this.x + ", y=" + this.y + ")";
   }

   public int hashCode() {
    
    
      return this.x * 31 + this.y;
   }

   public boolean equals(Object var1) {
    
    
      if (this != var1) {
    
    
         if (var1 instanceof Point) {
    
    
            Point var2 = (Point)var1;
            if (this.x == var2.x && this.y == var2.y) {
    
    
               return true;
            }
         }

         return false;
      } else {
    
    
         return true;
      }
   }
}

出力変数値のフォーマット、オブジェクトの変数へのマッピング、変数間の等価性の比較、変数のコピーなど、多くの一般的な操作はデータ クラスによって簡素化され、簡単に実行できます。

fun main() {
    
    
    val point1 = Point(10, 20)
    val point2 = Point(10, 20)
    println("point1 toString() : $point1") //point1 toString() : Point(x=10, y=20)
    println("point2 toString() : $point2") //point2 toString() : Point(x=10, y=20)

    val (x, y) = point1
    println("point1 x is $x,point1 y is $y") //point1 x is 10,point1 y is 20

    //在 kotlin 中,“ == ” 相当于 Java 的 equals 方法
    //而 “ === ” 相当于 Java 的 “ == ” 方法
    println("point1 == point2 : ${
      
      point1 == point2}") //point1 == point2 : true
    println("point1 === point2 : ${
      
      point1 === point2}") //point1 === point2 : false

    val point3 = point1.copy(y = 30)
    println("point3 toString() : $point3") //point3 toString() : Point(x=10, y=30)
}

toString()、equals()、hashCode()、copy()データ クラスのメソッドはメイン コンストラクターで宣言されたプロパティのみを考慮するため、2 つのデータ クラス オブジェクトを比較すると予期しない結果が生じる可能性があることに注意してください

data class Point(val x: Int) {
    
    

    var y: Int = 0

}

fun main() {
    
    
    val point1 = Point(10)
    point1.y = 10

    val point2 = Point(10)
    point2.y = 20

    println("point1 == point2 : ${
      
      point1 == point2}") //point1 == point2 : true
    println("point1 === point2 : ${
      
      point1 === point2}") //point1 === point2 : false
}

3. 封止

Sealed クラス (sealed クラス) は、クラスの可能なサブクラスを制限するために使用されます。Sealedで変更されたクラスの直接のサブクラスは、 Sealed クラスが配置されているファイル (sealed クラスの間接継承) でのみ定義できます。他のファイルで定義できます (中央)。これにより、開発者は親クラスとサブクラスの間の変化する関係を把握し、コード変更によって引き起こされる潜在的なバグを回避できます。シールされたクラスのコンストラクターはプライベートのみにすることができます。

たとえば、View クラスの場合、そのサブクラスはそれと同じファイル内でのみ定義でき、Sealed 修飾子によって変更されたクラスもそのクラスがオープン クラスであることを暗黙的に示すため、オープン クラスを明示的に追加する必要はありません。修飾子

sealed class View {
    
    

    fun click() {
    
    

    }

}

class Button : View() {
    
    

}

class TextView : View() {
    
    

}

Sealed クラスのサブクラスはコンパイラで制御できるため、Sealed クラスのすべてのサブクラスが when 式で処理される場合、else のデフォルト分岐を提供する必要はありません。将来ビジネスの変更により View サブクラスが追加された場合でも、コンパイラは check メソッドに分岐チェックがないことを検出してエラーを報告するため、check メソッドはタイプセーフです

fun check(view: View): Boolean {
    
    
    when (view) {
    
    
        is Button -> {
    
    
            println("is Button")
            return true
        }
        is TextView -> {
    
    
            println("is TextView")
            return true
        }
    }
}

4. 列挙型クラス

Kotlin は列挙型の実装も提供しますが、Java と比較すると、class キーワードを使用して列挙型を宣言する必要があります。

enum class Day {
    
    
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

列挙型ではいくつかのパラメータを宣言できます

enum class Day(val index: Int) {
    
    
    SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)
}

さらに、列挙型はインターフェイスを実装することもできます。

interface OnChangedListener {
    
    

    fun onChanged()

}

enum class Day(val index: Int) : OnChangedListener {
    
    
    SUNDAY(0) {
    
    
        override fun onChanged() {
    
    

        }
    },
    MONDAY(1) {
    
    
        override fun onChanged() {
    
    
            
        }
    }
}

列挙にはいくつかのパブリック関数も含まれています

fun main() {
    
    
    val day = Day.FRIDAY
    //获取值
    val value = day.index  //5
    //通过 String 获取相应的枚举值
    val value1 = Day.valueOf("SUNDAY") //SUNDAY
    //获取包含所有枚举值的数组
    val value2 = Day.values()
    //获取枚举名
    val value3 = Day.SUNDAY.name //SUNDAY
    //获取枚举声明的位置
    val value4 = Day.TUESDAY.ordinal //2
}

5. 入れ子になったクラス

kotlin では、クラス内に定義されたクラスはデフォルトでネストされたクラスになりますが、この時点ではネストされたクラスには外部クラスへの暗黙的な参照は含まれません。

class Outer {
    
    

    private val bar = 1

    class Nested {
    
    
        fun foo1() = 2
        //错误
        //fun foo2() = bar
    }
}

fun main() {
    
    
    val demo = Outer.Nested().foo1()
}

上記のコードが IDEA によって逆コンパイルされると、その内部 Java 実装が表示されます。

Nested は実際には静的クラスであることがわかります。そのため、 foo2() は外部クラスの非静的メンバーにアクセスできません。また、最初に アウター 変数を宣言してから Nested クラスをポイントする必要はありませんが、直接ポイントする必要はありません。外部クラスを介してネストされたクラスへ

public final class Outer {
    
    
   private final int bar = 1;

   public static final class Nested {
    
    
      public final int foo1() {
    
    
         return 2;
      }
   }
}

public final class MainkotlinKt {
    
    
   public static final void main(@NotNull String[] args) {
    
    
      Intrinsics.checkParameterIsNotNull(args, "args");
      int demo = (new Outer.Nested()).foo1();
   }
}

6. 内部クラス

外部クラスのメンバーにアクセスする必要がある場合は、ネストされたクラスを内部修飾子でマークする必要があります。これは内部クラスと呼ばれます。内部クラスは外部クラスへの参照を暗黙的に保持します

class Outer {
    
    

    private val bar = 1

    inner class Nested {
    
    
        fun foo1() = 2
        fun foo2() = bar
    }
}

fun main() {
    
    
    val demo = Outer().Nested().foo2()
}

内部の Java 実装を見てみましょう

inner を使用してネストされたクラスを宣言した後は、それを非静的内部クラスとして宣言することと同じであるため、 foo2() はその外部クラスの非静的メンバーにアクセスできます。ネストされた変数を宣言する前に、次も使用する必要があります。内部のネストされた種類を指す外部変数

public final class Outer {
    
    
   private final int bar = 1;

   public final class Nested {
    
    
      public final int foo1() {
    
    
         return 2;
      }

      public final int foo2() {
    
    
         return Outer.this.bar;
      }
   }
}

public final class MainkotlinKt {
    
    
   public static final void main(@NotNull String[] args) {
    
    
      Intrinsics.checkParameterIsNotNull(args, "args");
      int demo = (new Outer().new Nested()).foo2();
   }
}
クラス A がクラス B で宣言されている ジャワで コトリンで
ネストされたクラス (外部クラスへの参照を保存しない) 静的クラスA クラスA
内部クラス (外部クラスへの参照を格納) クラスA 内部クラスA

7. 匿名の内部クラス

オブジェクト式を使用して匿名の内部クラス インスタンスを作成できます。

interface OnClickListener {
    
    

    fun onClick()

}

class View {
    
    

    fun setClickListener(clickListener: OnClickListener) {
    
    

    }

}

fun main() {
    
    
    val view = View()
    view.setClickListener(object : OnClickListener {
    
    
        override fun onClick() {
    
    

        }

    })
}

8. インラインクラス

場合によっては、プログラムの堅牢性を向上させるためにネイティブ型をラップする必要があります。たとえば、sendEmailメソッドの入力パラメータについては、入力パラメータの意味の種類を厳密に制限することはできませんが、遅延をミリ秒単位で理解する開発者もいるでしょうし、分単位で理解する開発者もいるかもしれません。

fun sendEmail(delay: Long) {
    
    
    println(delay)
}

プログラムの堅牢性を向上させるために、ラッパー クラスをパラメーターの型として宣言できます。

fun sendEmail(delay: Time) {
    
    
    println(delay.second)
}

class Time(val second: Long)

class Minute(private val count: Int) {
    
    

    fun toTime(): Time {
    
    
        return Time(count * 60L)
    }

}

fun main() {
    
    
    sendEmail(Minute(10).toTime())
}

このようにして、開発者が渡すことができるパラメータの種類はコードのソースで制限され、開発者はクラス名を通じて必要な時間を直接表現できます。ただし、この方法では、追加のヒープ メモリ割り当ての問題により、実行時のパフォーマンスのオーバーヘッドが発生します。新しいラッパー クラスが必要とするパフォーマンスの消費量は、ネイティブ タイプのそれよりもはるかに高くなりますが、現時点では、プログラムの堅牢性と信頼性が必要です。可読性が高いため、ラッパー クラスも必要

この 2 つの矛盾を解決するためにインライン クラス (InlineClass) が誕生しました。上記のコードを次のように変更すると、次のようになります。

fun sendEmail(delay: Time) {
    
    
    println(delay.second)
}

inline class Time(val second: Long)

inline class Minute(private val count: Int) {
    
    

    fun toTime(): Time {
    
    
        return Time(count * 60L)
    }

}

fun main() {
    
    
    sendEmail(Minute(10).toTime())
}

inline で変更されたクラスは、インライン クラスと呼ばれます。インライン クラスには、メイン コンストラクターで初期化される一意の属性が含まれている必要があります。この一意の属性は、実行時にインライン クラスのインスタンスを表すために使用され、クラスのラップを回避します。実行時のオーバーヘッド

sendEmailたとえば、バイトコードを見ると、メソッドが入力型としてlong型を持つ関数として解釈され、オブジェクトが含まれていないことがわかります。

public static final void sendEmail_G1aXmDY/* $FF was: sendEmail-G1aXmDY*/(long delay) {
    
    
  boolean var4 = false;
  System.out.println(delay);
}

12. インターフェース

1. 抽象メソッドとデフォルトメソッド

kotlin のインターフェイスは Java 8 のインターフェイスに似ています。抽象メソッドの定義と非抽象メソッドの実装を含めることができます。非抽象メソッドをデフォルトの実装でマークするために、default キーワードを使用する必要はありませんが、ラベル付けのためのインターフェイスオーバーライドの抽象メソッドを実装するときに使用する必要があります

fun main() {
    
    
    val view = View()
    view.click()
    view.longClick()
}

class View : Clickable {
    
    
    
    override fun click() {
    
    
        println("clicked")
    }

}

interface Clickable {
    
    
    fun click()
    fun longClick() = println("longClicked")
}

クラスが複数のインターフェイスを実装しており、そのインターフェイスにデフォルト実装と同じシグネチャを持つメソッドが含まれている場合、コンパイラーは開発者にそのメソッドを明示的に実装することを要求し、このメソッドで異なるインターフェイスの対応するメソッドを呼び出すことを選択できます。

class View : Clickable, Clickable2 {
    
    

    override fun click() {
    
    
        println("clicked")
    }

    override fun longClick() {
    
    
        super<Clickable>.longClick()
        super<Clickable2>.longClick()
    }
}

interface Clickable {
    
    
    fun click()
    fun longClick() = println("longClicked")
}

interface Clickable2 {
    
    
    fun click()
    fun longClick() = println("longClicked2")
}

2. 抽象属性

インターフェイスには、抽象プロパティ宣言を含めることができます。インターフェイスは、抽象プロパティをバッキング フィールドに格納するか、ゲッターを通じて取得するかを定義しません。インターフェイス自体には状態が含まれないため、このインターフェイスを実装するクラスのみが、必要なときにこれを格納します。 .値

次の例を見てください。Button クラスと TextView クラスの両方が Clickable インターフェイスを実装しており、どちらも statusValue 値を取得する方法を提供しています。

Button クラスは、アクセスされるたびに statusValue 値を再取得するためのカスタム ゲッターを提供します。そのため、プロパティ値が複数回取得されると、そのたびに getRandom() メソッドが呼び出されるために、値が不一致になる可能性があります。

TextView クラスの statusValue 属性には、クラスの初期化時に取得されたデータを格納するバッキング フィールドがあるため、その値は初期化後に再び値を取得することはありません。つまり、TextView クラスの getRandom() は 1 回だけ呼び出されます。

fun main() {
    
    
    val button = Button()
    println(button.statusValue)
    val textView = TextView()
    println(textView.statusValue)
}

class Button : Clickable {
    
    

    override val statusValue: Int
        get() = getRandom()

    private fun getRandom() = Random().nextInt(10)

}

class TextView : Clickable {
    
    

    override val statusValue: Int = getRandom()

    private fun getRandom() = Random().nextInt(10)

}

interface Clickable {
    
    

    val statusValue: Int

}

抽象プロパティを宣言できることに加えて、インターフェイスには、バッキング フィールドを参照しない限り、ゲッターとセッターを含むプロパティを含めることもできます (バッキング フィールドはインターフェイスに状態を保存する必要がありますが、これは許可されていません)。

interface Clickable {
    
    

    val statusValue: Int

    val check: Boolean
        get() = statusValue > 10
    
}

13、SAMインターフェース

次の例では、2 番目の書き方は Kotlin 1.4 より前ではサポートされていないため、setRunnableメソッドを呼び出すには SelfRunnable を完全に実装する必要があります。

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
interface SelfRunnable {
    
    

    fun run()

}

fun setRunnable(selfRunnable: SelfRunnable) {
    
    
    selfRunnable.run()
}

fun main() {
    
    
    setRunnable(object : SelfRunnable {
    
    
        override fun run() {
    
    
            println("hello,leavesC")
        }
    })
    //错误,Kotlin 1.4 之前不支持
//    setRunnable {
    
    
//        println("hello,leavesC")
//    }
}

Kotlin 1.4 以降、Kotlin はSAM 変換をサポートし始めました抽象メソッドが 1 つだけあるインターフェイスは、関数インターフェイスまたはSAM (Single Abstract Method) インターフェイスと呼ばれます。関数インターフェイスは複数の非抽象メンバーを持つことができますが、抽象メンバーは 1 つだけです。SAM 変換とは、Single Abstract Method Conversionsデフォルト以外の抽象メソッドが 1 つだけあるインターフェイスの場合、Lambda で表される関数の型がインターフェイスのメソッド シグネチャと一致する場合に限り、Lambda で直接表すことができることを意味します。

したがって、Kotlin 1.4 以降では、SelfRunnable の実装クラスを Lambda の形式で直接宣言することがサポートされており、メソッド呼び出しをより簡潔にすることができますが、これには同時に fun キーワードを使用してインターフェイスを変更する必要もあります

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
fun interface SelfRunnable {
    
    

    fun run()

}

fun setRunnable(selfRunnable: SelfRunnable) {
    
    
    selfRunnable.run()
}

fun main() {
    
    
    setRunnable {
    
    
        println("hello,leavesC")
    }
}

14. 継承

kotlin のすべてのクラスには共通のスーパークラスAnyがあります。これは、スーパークラス宣言のないクラスのデフォルトのスーパークラスです。Any はjava.lang.Objectではないことに注意してください。equals()、hashCode()、toString()以外の他のプロパティや関数はありません

明示的なスーパークラスを宣言するには、クラスヘッダーのコロンの後に親クラス名を置きます。

open class Base()

class SubClass() : Base()

このうち、クラスのオープンアノテーションは、Java における最終的な意味とは逆で、他のクラスがこのクラスを継承できるようにするために使用されます。デフォルトでは、kotlin のすべてのクラスは Final です

派生クラスにプライマリ コンストラクターがある場合、その基本型は、基本クラスのプライマリ コンストラクターを直接または間接的に呼び出す必要があります。

open class Base(val str: String)

class SubClass(val strValue: String) : Base(strValue)

class SubClass2 : Base {
    
    

    constructor(strValue: String) : super(strValue)

    constructor(intValue: Int) : super(intValue.toString())

    constructor(doubValue: Double) : this(doubValue.toString())

}

1.取材方法

Java とは異なり、kotlin ではオーバーライド可能なメンバーとオーバーライドされたメンバーの明示的なアノテーションが必要です。

open class Base() {
    
    
    open fun fun1() {
    
    

    }

    fun fun2() {
    
    
        
    }
}

class SubClass() : Base() {
    
    
    override fun fun1() {
    
    
        super.fun1()
    }
}

open とマークされた関数のみがサブクラスによってオーバーロードでき、サブクラスはオーバーライドを使用して、その関数が親クラスの同じシグネチャを持つ関数をオーバーライドすることを示します。override とマークされたメンバーはそれ自体オープンです。つまり、サブクラスによってオーバーライドできます。再上書きを禁止したい場合は、final キーワードを使用してマークを付けることができます。
親クラスが open を使用して関数をマークしない場合、サブクラスは同じシグネチャを持つ関数を定義できません。最終クラス (open でマークされていないクラス) の場合、プロパティとメソッドをマークするために open を使用することは無意味です。

2. 属性の範囲

プロパティのオーバーライドはメソッドのオーバーライドと似ています。スーパークラスでオープンとして宣言されたプロパティをオーバーライドする場合は、派生クラスで override で始まるプロパティを再宣言する必要があり、互換性のある型を持っている必要があります。

宣言された各プロパティは、初期化子を持つプロパティまたはゲッター メソッドを持つプロパティによってオーバーライドできます。

open class Base {
    
    
    open val x = 10

    open val y: Int
        get() {
    
    
            return 100
        }
}

class SubClass : Base() {
    
    
    override val x = 100

    override var y = 200
}

fun main() {
    
    
    val base = Base()
    println(base.x) //10
    println(base.y) //100

    val base1: Base = SubClass()
    println(base1.x) //100
    println(base1.y) //200

    val subClass = SubClass()
    println(subClass.x) //100
    println(subClass.y) //200
}

また、val 属性を var 属性でオーバーライドすることは可能ですが、その逆はできません。val プロパティは本質的にゲッター メソッドを宣言しており、それを var としてオーバーライドすると、サブクラスで追加のセッター メソッドが宣言されるだけであるためです。

プロパティ宣言の一部としてプライマリ コンストラクターで override キーワードを使用できます。

open class Base {
    
    
    open val str: String = "Base"
}

class SubClass(override val str: String) : Base()

fun main() {
    
    
    val base = Base()
    println(base.str) //Base

    val subClass = SubClass("leavesC")
    println(subClass.str) //leavesC
}

3. スーパークラス実装を呼び出す

派生クラスは super キーワードを使用して、スーパークラスの関数およびプロパティ アクセサーの実装を呼び出すことができます。

open class BaseClass {
    
    
    open fun fun1() {
    
    
        println("BaseClass fun1")
    }
}

class SubClass : BaseClass() {
    
    

    override fun fun1() {
    
    
        super.fun1()
    }

}

内部クラスの場合、外部クラスの関数を直接呼び出すことができます。

open class BaseClass2 {
    
    
    private fun fun1() {
    
    
        println("BaseClass fun1")
    }

    inner class InnerClass {
    
    
        fun fun2() {
    
    
            fun1()
        }
    }

}

ただし、内部クラス内の外部クラスのスーパークラスにアクセスしたい場合は、外部クラス名で修飾された super キーワードを使用して、これを実現する必要があります。

open class BaseClass {
    
    
    open fun fun1() {
    
    
        println("BaseClass fun1")
    }
}

class SubClass : BaseClass() {
    
    

    override fun fun1() {
    
    
        println("SubClass fun1")
    }

    inner class InnerClass {
    
    

        fun fun2() {
    
    
            super@SubClass.fun1()
        }

    }

}

fun main() {
    
    
    val subClass = SubClass()
    val innerClass = subClass.InnerClass()
    //BaseClass fun1
    innerClass.fun2()
}

クラスが、その直接のスーパークラスおよび実装されたインターフェイスから同じメンバーの複数の実装を継承する場合、このメンバーをオーバーライドし、曖昧さを解消するために独自の実装を提供する必要があります。

どのスーパータイプを継承するかを示すには、super<BaseClass> のように、山括弧で囲んだスーパータイプ名でスーパー修飾を使用して指定します。

open class BaseClass {
    
    
    open fun fun1() {
    
    
        println("BaseClass fun1")
    }
}

interface BaseInterface {
    
    
    //接口成员默认就是 open 的
    fun fun1() {
    
    
        println("BaseInterface fun1")
    }
}

class SubClass() : BaseClass(), BaseInterface {
    
    
    override fun fun1() {
    
    
        //调用 SubClass 的 fun1() 函数
        super<BaseClass>.fun1()
        //调用 BaseInterface 的 fun1() 函数
        super<BaseInterface>.fun1()
    }
}

15. コレクション

1. 読み取り専用コレクションと可変コレクション

kotlin のコレクション設計の Java とは異なるもう 1 つの特徴は、kotlin ではデータにアクセスするためのインターフェイスとコレクションのデータを変更するためのインターフェイスが分離されており、コレクション要素の走査、コレクションのサイズの取得、コレクションかどうかの判断などの操作が提供されますkotlin.collections.Collection要素が含まれています。このインターフェイスには、要素を追加および削除するメソッドは提供されませんkotlin.collections.MutableCollectionインターフェイスはkotlin.collections.Collectionインターフェイスを継承し、要素を追加、削除、クリアするためのメソッドを拡張します。

valkotlin の と の区別と同様にvar、読み取り専用コレクション インターフェイスと可変コレクション インターフェイスを分離すると、コードの制御性が向上します。関数がCollection仮パラメーターとして受け取る場合、関数がコレクションを変更しないことがわかります。 、ただし読み取られるデータのみ

以下は、さまざまなタイプのコレクションの作成に使用される関数です。

要素を設定する 読み取り専用 変数
リスト リストの mutableListOf、arrayListOf
設定 セットの mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
地図 の地図 mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf
val list = listOf(10, 20, 30, 40)
//不包含 add 方法
//list.add(100)
println(list.size)
println(list.contains(20))

val mutableList = mutableListOf("leavesC", "leavesc", "叶")
mutableList.add("Ye")
println(mutableList.size)
println(mutableList.contains("leavesC"))

2. コレクションと Java

Java は読み取り専用コレクションと可変コレクションを区別しないため、コレクションが kotlin で読み取り専用と宣言されている場合でも、Java コードでコレクションを変更できます。また、Java コード内のコレクションも kotlin にとって未知の可変性であるため、kotlin コードで処理できます。読み取り専用または変更可能であり、含まれる要素は null または null 以外にすることもできます

たとえば、Java コードでは、names は List<String> 型の変数です。

public class JavaMain {
    
    

    public static List<String> names = new ArrayList<>();

    static {
    
    
        names.add("leavesC");
        names.add("Ye");
    }

}

kotlin で変数名を参照するには 4 つの方法があります

val list1: List<String?> = JavaMain.names

val list2: List<String> = JavaMain.names

val list3: MutableList<String> = JavaMain.names

val list4: MutableList<String?> = JavaMain.names

3. 読み取り専用コレクションの可変性

読み取り専用コレクションは必ずしも不変であるとは限りません。たとえば、読み取り専用型のインターフェイスを持つオブジェクトがあり、そのオブジェクトに対して 2 つの異なる参照 (1 つは読み取り専用、もう 1 つは変更可能) があるとします。変更可能な参照がオブジェクトを変更する場合、これは読み取り専用と同等です。 「読み取り専用コレクションが変更されました」という参照のみであるため、読み取り専用コレクションは常にスレッドセーフであるとは限りません。マルチスレッド環境でデータを処理する必要がある場合は、データへのアクセスが正しく同期されていることを確認するか、同時アクセスをサポートするデータ構造を使用する必要があります。

たとえば、list1 と list1 は同じコレクション オブジェクトを参照しており、コレクションに対する list3 の変更は list1 にも影響します。

val list1: List<String> = JavaMain.names
val list3: MutableList<String> = JavaMain.names
list1.forEach {
    
     it -> println(it) } //leavesC Ye
list3.forEach {
    
     it -> println(it) } //leavesC Ye
for (index in list3.indices) {
    
    
    list3[index] = list3[index].toUpperCase()
}
list1.forEach {
    
     it -> println(it) } //LEAVESC YE

4. コレクションと null 可能性

コレクションの null 可能性は、次の 3 つのタイプに分類できます。

  1. null を含む可能性のあるコレクション要素
  2. コレクション自体は null になる可能性があります
  3. コレクション自体が null の場合もあれば、null のコレクション要素が含まれる場合もあります

たとえば、intList1 には null のコレクション要素を含めることができますが、コレクション自体が null を指すことはできません。intList2 には null のコレクション要素を含めることはできませんが、コレクション自体が null を指すことはできます。intList3 には null のコレクション要素を含めることができ、コレクション自体が null を指すことはできます。

//List<Int?> 是能持有 Int? 类型值的列表
val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
//List<Int>? 是可以为 null 的列表
var intList2: List<Int>? = listOf(10, 20, 30, 40)
intList2 = null
//List<Int?>? 是可以为 null 的列表,且能持有 Int? 类型值
var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
intList3 = null

16. 拡張関数と拡張属性

1.拡張機能

拡張関数は、クラスに新しい動作を追加するために使用されます。これは、有用な機能が不足しているクラスを拡張する方法です。拡張関数の目的は、Java で実装された静的ユーティリティ メソッドの目的と似ています。kotlin で拡張関数を使用する利点の 1 つは、メソッドを呼び出すときにオブジェクト全体をパラメーターとして渡す必要がないことです。拡張関数はクラス自体に属しているかのように動作し、 this キーワードと直接呼び出す すべてのパブリック メソッド

拡張関数ではカプセル化を解除することはできません。クラス内で定義されたメソッドとは異なり、拡張関数はプライベート メンバーや保護されたメンバーにアクセスできません。

//为 String 类声明一个扩展函数 lastChar() ,用于返回字符串的最后一个字符
//get方法是 String 类的内部方法,length 是 String 类的内部成员变量,在此处可以直接调用
fun String.lastChar() = get(length - 1)

//为 Int 类声明一个扩展函数 doubleValue() ,用于返回其两倍值
//this 关键字代表了 Int 值本身
fun Int.doubleValue() = this * 2

その後、クラス自体の内部で宣言されたメソッドを呼び出すのと同じように、拡張関数を直接呼び出すことができます。

fun main() {
    
    
    val name = "leavesC"
    println("$name lastChar is: " + name.lastChar())

    val age = 24
    println("$age doubleValue is: " + age.doubleValue())
}

静的拡張関数を宣言する必要がある場合は、Java 静的関数を呼び出すのと同じように、Namer インスタンスなしで拡張関数を呼び出せるように、コンパニオン オブジェクトで静的拡張関数を定義する必要があります。

class Namer {
    
    

    companion object {
    
    

        val defaultName = "mike"

    }

}

fun Namer.Companion.getName(): String {
    
    
    return defaultName
}

fun main() {
    
    
    Namer.getName()
}

拡張関数がクラス内で宣言されている場合、拡張関数はクラスおよびそのサブクラス内でのみ呼び出すことができます。これは、この時点では非静的関数を宣言していることと同等であり、外部から参照することはできません。したがって、一般的に拡張関数はグローバル関数として宣言されます。

2. 拡張属性

拡張関数はプロパティでも使用できます

//扩展函数也可以用于属性
//为 String 类新增一个属性值 customLen
var String.customLen: Int
    get() = length
    set(value) {
    
    
        println("set")
    }

fun main() {
    
    
    val name = "leavesC"
    println(name.customLen)
    name.customLen = 10
    println(name.customLen)
    //7
    //set
    //7
}

3. 書き換え不可能な拡張関数

次の例では、サブクラス Button が親クラス View の click() 関数を書き換えていますが、このとき View 変数を宣言して Button 型のオブジェクトに代入すると、呼び出される click() 関数はButton クラスによって書き換えられたメソッド。

fun main() {
    
    
    val view: View = Button()
    view.click() //Button clicked
}

open class View {
    
    
    open fun click() = println("View clicked")
}

class Button : View() {
    
    
    override fun click() = println("Button clicked")
}

拡張関数の場合は上記の例とは異なります。基本クラスとサブクラスの両方で同じ名前の拡張関数が定義されている場合、どの拡張関数を呼び出すかは、変数の実行時型ではなく、変数の静的型によって決まります。

fun main() {
    
    
    val view: View = Button()
    view.longClick() //View longClicked
}

open class View {
    
    
    open fun click() = println("View clicked")
}

class Button : View() {
    
    
    override fun click() = println("Button clicked")
}

fun View.longClick() = println("View longClicked")

fun Button.longClick() = println("Button longClicked")

また、クラスのメンバー関数が拡張関数と同じシグネチャを持つ場合、メンバー関数が最初に使用されます。

拡張関数は実際には元のクラスを変更しませんが、その最下層は実際には静的インポートによって実装されます。拡張関数は任意のファイルで宣言できるため、一連の関連関数を新しいファイルに入れるのが一般的です。

なお、拡張機能はプロジェクトスコープ全体に自動的に有効になるわけではないため、拡張機能を使用する必要がある場合はインポートする必要があります。

4.ヌル可能レシーバー

レシーバーが null の場合でも、null 許容レシーバー型に対して拡張を定義できるため、開発者は拡張関数を呼び出す前に null 判定を行う必要がなく、次の方法でthis == nullレシーバーが空かどうかを確認できます。

fun main() {
    
    
    var name: String? = null
    name.check() //this == null
    name = "leavesC"
    name.check() //this != null
}

fun String?.check() {
    
    
    if (this == null) {
    
    
        println("this == null")
        return
    }
    println("this != null")
}

17、ラムダ式

ラムダ式は、基本的に他の関数に渡すことができる小さなコードです。ラムダ式を通じて、共通のコード構造をライブラリ関数に抽出したり、ラムダ式を変数に格納して通常の関数として扱うことができます。関数。

//由于存在类型推导,所以以下三种声明方式都是完全相同的
val plus1: (Int, Int) -> Int = {
    
     x: Int, y: Int -> x + y }
val plus2: (Int, Int) -> Int = {
    
     x, y -> x + y }
val plus3 = {
    
     x: Int, y: Int -> x + y }
println(plus3(1, 2))
  1. ラムダ式は常に中括弧で囲まれ、引数リストと関数本体は矢印で区切られています。
  2. Lambda が関数の型を宣言する場合、関数本体の型宣言は省略できます。
  3. Lambda がパラメータの型を宣言し、戻り値が型推定をサポートしている場合、関数の型宣言は省略できます。

副作用を避けるために、ラムダ式で外部変数を参照することは避けようとする傾向がありますが、場合によっては、ラムダ式で外部変数を参照できるようにすると、計算構造が簡素化される場合もあります。外部環境変数にアクセスするラムダ式はクロージャと呼ばれ、クロージャはパラメータとして渡すことも、直接使用することもできます。Java とは異なり、kotlin のクロージャーは外部変数にアクセスできるだけでなく、外部変数を変更することもできます。

たとえば、関数が呼び出されるたびに合計を計算し、現在の合計サイズを返すメソッドが必要だとします。現在の合計をメソッドの外部に保存する変数はなく、ラムダ式の内部に保存されます。

fun main() {
    
    
    val sum = sumFunc()
    println(sum(10)) //10
    println(sum(20)) //30
    println(sum(30)) //60
}

fun sumFunc(): (Int) -> Int {
    
    
    var base = 0
    return fun(va: Int): Int {
    
    
        base += va
        return base
    }
}

さらに、kotlin は autorun 構文もサポートしています

{
    
     va1: Int, va2: Int -> println(va1 + va2) }(10, 20)

ラムダ式の最も一般的な使用法は、コレクションを操作することです。次の例を参照してください。

人物リストから最年長の人を取得するには

data class Person(val name: String, val age: Int)

fun main() {
    
    
    val people = listOf(Person("leavesC", 24), Person("Ye", 22))
    println(people.maxBy {
    
     it.age }) //Person(name=leavesC, age=24)
}

このうち、ライブラリ関数 maxBy は任意のコレクションに対して呼び出すことができ、引数を 1 つ取ります。それは、比較に使用する関数を指定する関数です。中括弧内のコードは、{ it.age }このロジックを実装する Lambda 式です。

上記のmaxBy関数の実パラメータは簡略化した書き方です。maxBy関数の簡略化処理を見てみましょう

最も基本的な構文ステートメントは次のようになり、ラムダ式が括弧で囲まれています。

println(people.maxBy({
    
     p: Person -> p.age }))

Kotlin には、ラムダ式が関数呼び出しの最後の引数である場合、括弧の外側に配置できるという文法規則があります。

 println(people.maxBy() {
    
     p: Person -> p.age })

ラムダ式が関数の唯一の実パラメータである場合、呼び出しコード内の空のかっこのペアを削除できます。

 println(people.maxBy {
    
     p: Person -> p.age })

ラムダ式のパラメータの型が推定できる場合は、パラメータの型の宣言を省略できます。

println(people.maxBy {
    
     p -> p.age })

現在のコンテキストがパラメーターを 1 つだけ含むラムダ式を予期しており、パラメーターの型が推測できる場合は、パラメーターのデフォルト名が生成されます。

 println(people.maxBy {
    
     it.age })

kotlin と Java の大きな違いは、kotlin の関数内の Lambda 式は関数パラメータと最終変数へのアクセスに限定されず、Lambda 内の非最終変数にもアクセスして変更できることです。

Lambda 内から外部変数にアクセスすることを、これらの変数が Lambda によってキャプチャされると言います。最終変数をキャプチャする場合、変数値は、その値を使用する Lambda コードとともに保存されます。最終変数以外の場合、値は特別なラッパーにラップされ、このラッパーへの参照は Lambda コードとともに保存されます

var number = 0
val list = listOf(10, 20, 30, 40)
list.forEach {
    
    
    if (it > 20) {
    
    
        number++
    }
}
println(number) //2

メンバー参照は、単一のメソッドを呼び出すか、単一のプロパティにアクセスする関数値を作成するために使用されます。クラス名は、参照されるメンバー (メソッドまたはプロパティ) の名前から二重コロンで区切られます。

メンバー参照の使用方法の 1 つは、パラメーターとして渡されるコード ブロックが関数として定義されている場合、関数を呼び出すための Lambda 式を作成する必要がなく、関数をメンバー参照 (またはメンバー参照) によって直接渡すことができます。転送属性)。また、メンバー参照は拡張関数に対しても同様に機能します。

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

    val myAge = age

    fun getPersonAge() = age
}

fun Person.filterAge() = age

fun main() {
    
    
    val people = listOf(Person("leavesC", 24), Person("Ye", 22))
    println(people.maxBy {
    
     it.age })    //Person(name=leavesC, age=24)
    println(people.maxBy(Person::age))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::myAge))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::getPersonAge))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::filterAge))  //Person(name=leavesC, age=24)
}

参照が関数であってもプロパティであっても、メンバー参照の名前の後にかっこを置かないでください。

さらに、トップレベル関数を参照できます。

fun test() {
    
    
    println("test")
}

fun main() {
    
    
    val t = ::test
}

コンストラクターを使用して、ストレージを参照したり、クラス インスタンスを作成するアクションの遅延実行を行うこともできます。

data class Person(val name: String, val age: Int)

fun main() {
    
    
    val createPerson = ::Person
    val person = createPerson("leavesC", 24)
    println(person)
}

18. 標準ライブラリの拡張関数

kotlin 標準ライブラリは、標準ファイルで定義されているいくつかの便利な拡張関数を提供します。

1、走る

run 関数は関数パラメータを受け取り、関数の戻り値を run 関数の戻り値として使用します。

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

fun main() {
    
    
    var nickName = "leavesC"
    nickName = nickName.run {
    
    
        if (isNotEmpty()) {
    
    
            this
        } else {
    
    
            ""
        }
    }
    println(nickName)
}

2、付き

with関数は拡張関数ではありませんが、似たような機能があるので合わせて紹介します。with 関数の最初のパラメータはレシーバ オブジェクト レシーバ、2 番目のパラメータはレシーバ オブジェクト タイプで定義された拡張関数であるため、レシーバのパブリック メソッドとプロパティは関数内で直接呼び出すことができます。

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with 関数は、オブジェクトの名前を繰り返し書き出すことなく、同じオブジェクトに対して複数の操作を実行するために使用されます。

たとえば、指定されたコンテンツを含む文字列を構築するには、次を連続して呼び出す必要があります。

fun main() {
    
    
    val result = StringBuilder()
    result.append("leavesC")
    result.append("\n")
    for (letter in 'A'..'Z') {
    
    
        result.append(letter)
    }
    println(result.toString())
 }

with 関数を使用してビルドすると、コードはより簡潔になります。

val result = with(StringBuilder()) {
    
    
    append("leavesC")
    append("\n")
    for (letter in 'A'..'Z') {
    
    
        append(letter)
    }
    toString()
}
println(result)

with 関数は、2 つのパラメーター (この場合は StringBuilder と Lambda 式) を取る関数で、Lambda 式をかっこの外側に置くという規則を利用しています。

with 関数の戻り値は Lambda 式の実行結果であり、Lambda 内の最後の式の戻り値であるため、以下のようにコードを変更すると、 println() メソッドには戻り値がないため、出力されます 内容は kotlin.Unit になります

val result = with(StringBuilder()) {
    
    
    append("leavesC")
    append("\n")
    for (letter in 'A'..'Z') {
    
    
        append(letter)
    }
    println("Hello")
}
println(result)  //kotin.Unit

3、申請する

apply 関数は T 型の拡張関数として宣言され、そのレシーバーは実パラメータとしての Lambda のレシーバーであり、最後の関数はオブジェクト自体である this を返します。

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

したがって、apply 関数と with 関数の唯一の違いは、apply 関数は引数として渡されたオブジェクトを常に返すことです。

val result = StringBuilder().apply {
    
    
    append("leavesC")
    append("\n")
    for (letter in 'A'..'Z') {
    
    
        append(letter)
    }
    toString()
}
println(result)
println(result.javaClass) //class java.lang.StringBuilder

4、また

また、関数は関数型のパラメータを受け取り、次にレシーバ自体をパラメータとして受け取り、最後にレシーバ オブジェクト自体を返します。

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

fun main() {
    
    
    val nickName = "leavesC"
    val also = nickName.also {
    
    
        it.length
    }
    println(also) //leavesC
}

5、しましょう

let 関数は関数型のパラメータを受け取り、その関数型が受信者自体をパラメータとして受け取り、最後に関数の評価結果を返します。

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

fun main() {
    
    
    val nickName = "leavesC"
    val also = nickName.let {
    
    
        it.length
    }
    println(also) //7
}

6、テイクイフ

takeIf は戻り値の型が bool である関数を受け入れ、パラメーターの戻り値が true の場合はレシーバー オブジェクト自体を返し、それ以外の場合は null を返します。

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    
    
    contract {
    
    
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

fun main() {
    
    
    println(check("leavesC")) //7
    println(check(null)) //0
}

fun check(name: String?): Int {
    
    
    return name.takeIf {
    
     !it.isNullOrBlank() }?.length ?: 0
}

7、取らない限り

takeUnless の判定条件は takeIf の逆なのでここでは詳しく説明しません

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    
    
    contract {
    
    
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

19. 関数演算子

1.トータルオペレーター

1、任意

少なくとも 1 つの要素が指定された基準を満たす場合に true を返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.any {
    
     it > 13 })  //false
println(list.any {
    
     it > 7 })   //true

2、すべて

すべての要素が指定された基準を満たしている場合に true を返します

val list = listOf(1, 3, 5, 7, 9)
println(list.all {
    
     it > 13 })  //false
println(list.all {
    
     it > 0 })   //true

3、数える

指定された基準を満たす要素の総数を返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.count {
    
     it > 7 })  //1
println(list.count {
    
     it > 2 })  //4

4、折ります

初期値に基づいて最初の項目から最後の項目まですべての要素を関数で累積します

fun main() {
    
    
    val list = listOf(1, 3, 5, 7, 9)
    println(list.fold(2) {
    
     total, next->
        println("$next , $total")
        next + total
    })
}
1 , 2
3 , 3
5 , 6
7 , 11
9 , 18
27

5、右に折ります

フォールドと同じですが、順序は最後の項目から最初の項目になります。

val list = listOf(1, 3, 5, 7, 9)
println(list.foldRight(2) {
    
     next, total->
    println("$next , $total")
    next + total
})
9 , 2
7 , 11
5 , 18
3 , 23
1 , 26
27

6、それぞれ

val list = listOf(1, 3, 5, 7, 9)
list.forEach {
    
     print(it + 1) } //246810

7、インデックスごとに

forEachと同様に、同時に要素のインデックスを取得できます

val list = listOf(1, 3, 5, 7, 9)
list.forEachIndexed {
    
     index, value -> println("$index value is $value") }

0 value is 1
1 value is 3
2 value is 5
3 value is 7
4 value is 9

8、最大

最大の項目を返すか、何もない場合は null を返します

val list = listOf(1, 3, 5, 7, 9)
println(list.max()) //9

9、最大値

指定された関数に従って最大の項目を返します。何もない場合は null を返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.maxBy {
    
     -it }) //1

10分

最小の項目を返します。ない場合は null を返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.min()) //1

11、マイシティ

指定された関数に従って最小の項目を返します。何もない場合は null を返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.minBy {
    
     -it }) //9

12、なし

指定された関数に一致する要素がない場合は true を返します

val list = listOf(1, 3, 5, 7, 9)
println(list.none {
    
     it > 10 }) //true

13、減らす

フォールドと同じですが、初期値はありません。最初の項目から最後の項目まで関数で累積します。

val list = listOf(1, 3, 5, 7, 9)
println(list.reduce {
    
     total, next ->
    println("$next , $total")
    total + next
})
3 , 1
5 , 4
7 , 9
9 , 16
25

14、右に減らす

reduce と同じですが、順序は最後の項目から最初の項目になります。

val list = listOf(1, 3, 5, 7, 9)
println(list.reduceRight {
    
     next, total ->
    println("$next , $total")
    total + next
})

7 , 9
5 , 16
3 , 21
1 , 24
25

15、合計

各項目が関数によって変換された後のすべてのデータの合計を返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.sumBy {
    
     it + 1 }) //30

2. フィルタ演算子

1、ドロップ

最初の n 要素を除いたすべての要素を含むリストを返します。

val list = listOf(1, 3, 5, 7, 9)
println(list.drop(2)) //[5, 7, 9]

2、ドロップしながら

指定された関数に準拠しない最初の要素から始まるリストを返します。

val list = listOf(1, 3, 5, 7, 9, 2)
println(list.dropWhile {
    
     it < 4 }) //[5, 7, 9, 2]

3、ドロップLastwhile

最後の項目から始めて、指定された関数に準拠しない要素から始まるリストを返します

val list = listOf(10, 1, 3, 5, 7, 9)
println(list.dropLastWhile {
    
     it > 4 }) //[10, 1, 3]

4、フィルター

指定された関数条件に一致するすべての要素をフィルターします

val list = listOf(1, 3, 5, 7, 9, 2)
println(list.filter {
    
     it < 4 }) //[1, 3, 2]

5、フィルターなし

指定された関数基準を満たさないすべての要素をフィルターします。

val list = listOf(1, 3, 5, 7, 9, 2)
println(list.filterNot {
    
     it < 4 }) //[5, 7, 9]

6、フィルターNotNull

null ではないすべての要素をフィルタリングする

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.filterNotNull()) //[1, 3, 5, 7, 9, 2]

7、スライス

リスト内の指定されたインデックスの要素をフィルターします。

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.slice(listOf(0, 3))) //[1, 7]

8、取る

最初から n 個の要素を返します

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.take(2)) //[1, 3]

9、最後に取る

最後の要素から始まる n 個の要素を返します

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.takeLast(2)) //[2, null]

10、テイクホワイル

指定された関数の基準を満たす要素を最初の要素から返します。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.takeWhile {
    
     it > 2 }) //[]
println(list.takeWhile {
    
     it > 0 }) //[1, 3, 5]

3. マッピング演算子

1、フラットマップ

すべての要素を反復処理し、要素ごとにセットを作成し、最後にすべてのセットを 1 つのセットにまとめます。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.flatMap {
    
     listOf(it, it + 1) }) //[1, 2, 3, 4, 5, 6, -1, 0, 7, 8, 9, 10, 2, 3]

2、グループ化

指定された関数によってグループ化されたマップを返します

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.groupBy {
    
     listOf(it) }) //{[1]=[1], [3]=[3], [5]=[5], [-1]=[-1], [7]=[7], [9]=[9], [2]=[2]}
println(list.groupBy {
    
     listOf(it, it + 1) }) //{[1, 2]=[1], [3, 4]=[3], [5, 6]=[5], [-1, 0]=[-1], [7, 8]=[7], [9, 10]=[9], [2, 3]=[2]}

3、地図

指定された関数に従って変換された各要素を含むリストを返します。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.map {
    
     listOf(it) }) //[[1], [3], [5], [-1], [7], [9], [2]]
println(list.map {
    
     listOf(it, it + 1) }) //[[1, 2], [3, 4], [5, 6], [-1, 0], [7, 8], [9, 10], [2, 3]]

4、地図のインデックス付き

要素インデックスを含む指定された関数に従って変換された各要素で構成されるリストを返します。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.mapIndexed {
    
     index, value -> index }) //[0, 1, 2, 3, 4, 5, 6]
println(list.mapIndexed {
    
     index, value -> index * value }) //[0, 3, 10, -3, 28, 45, 12]

5、mapNotNull

指定された関数に従って変換された null 以外の各要素で構成されるリストを返します。

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.mapNotNull {
    
     it }) //[1, 3, 5, -1, 7, 9, 2]

4. 要素演算子

1、含まれています

指定された要素がコレクション内で見つかる場合は true を返します。

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.contains(3)) //true
println(list.contains(13)) //false

2、要素で

指定されたインデックスに対応する要素を返します。インデックス配列が範囲外の場合、IndexOutOfBoundsException がスローされます。

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAt(3)) //-1
println(list.elementAt(6)) //null

3、元素分析

指定されたインデックスに対応する要素を返します。インデックス配列が範囲外の場合は、指定された関数に従ってデフォルト値を返します。

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAtOrElse(3, {
    
     it * 2 }))  //-1
println(list.elementAtOrElse(16, {
    
     it * 2 })) //32

4、要素AtOrNull

指定されたインデックスに対応する要素を返します。インデックス配列が範囲外の場合は null を返します。

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAtOrNull(3))  //-1
println(list.elementAtOrNull(16)) //null

5、最初に

指定された関数基準を満たす最初の要素を返します。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.first {
    
     it % 3 == 0 })  //3

6、最初またはNull

指定された関数の条件を満たす最初の要素を返します。ない場合は null を返します。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.firstOrNull {
    
     it % 3 == 0 })  //3
println(list.firstOrNull {
    
     it % 8 == 0 })  //null

7、インデックスの

指定された要素の最初のインデックスを返します。要素が存在しない場合は -1 を返します。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.indexOf(5))  //2
println(list.indexOf(12)) //-1

8、最初のインデックス

指定された関数の条件を満たす最初の要素のインデックスを返します。条件が見つからない場合は -1 を返します。

val list = listOf(1, 3, 5, 1, 7, 9, 2)
println(list.indexOfFirst {
    
     it % 2 == 0 })   //6
println(list.indexOfFirst {
    
     it % 12 == 0 })  //-1

9、最後のインデックス

指定された関数の基準を満たす最後の要素のインデックスを返します。見つからない場合は -1 を返します。

val list = listOf(1, 3, 5, 6, 7, 9, 2)
println(list.indexOfLast {
    
     it % 2 == 0 })   //6
println(list.indexOfLast {
    
     it % 12 == 0 })  //-1

10、最後

指定された関数の条件を満たす最後の要素を返します。

val list = listOf(1, 3, 5, 6, 7, 9, 2)
println(list.last {
    
     it % 2 == 0 })   //2
println(list.last {
    
     it % 3 == 0 })   //9

11、lastIndexOf

指定された要素の最後のインデックスを返します。要素が存在しない場合は -1 を返します。

val list = listOf(1, 3, 2, 6, 7, 9, 2)
println(list.lastIndexOf(2))    //6
println(list.lastIndexOf(12))   //-1

12、lastOrNull

指定された関数の条件を満たす最後の要素を返します。何もない場合は null を返します。

val list = listOf(1, 3, 2, 6, 7, 9, 2)
println(list.lastOrNull {
    
     it / 3 == 3 })    //9
println(list.lastOrNull {
    
     it == 10 })       //null

13、シングル

指定された関数に一致する単一の要素を返します。要素が存在しないか複数の場合は例外をスローします。

val list = listOf(1, 9, 2, 6, 7, 9, 2)
println(list.single {
    
     it % 7 == 0 })  //7
println(list.single {
    
     it == 2 })      //IllegalArgumentException

14、シングルまたはヌル

指定された関数に一致する単一の要素を返します。要素が存在しないか複数の場合は null を返します。

val list = listOf(1, 9, 2, 6, 7, 9, 2)
println(list.singleOrNull {
    
     it % 7 == 0 })  //7
println(list.singleOrNull {
    
     it == 2 })      //null

5. 生産オペレーター

1、パーティション

指定されたセットを 2 つに分割します。最初のセットは、元のセット内の要素が指定された関数の条件に一致し true を返す要素で構成され、2 番目のセットは、元のセット内の要素が指定された関数に一致する各要素で構成されます。条件が false を返す

val list = listOf(1, 9, 2, 6, 7, 9, 2)
val (list1, list2) = list.partition {
    
     it % 2 == 0 }
println(list1)  //[2, 6, 2]
println(list2)  //[1, 9, 7, 9]

2、プラス

元のコレクションと指定されたコレクションのすべての要素を含むコレクションを返します。関数の名前により、+ 演算子を使用できます。

val list1 = listOf(1, 9, 2, 6, 7, 9, 2)
val list2 = listOf(1, 2, 4, 6, 8, 10)
println(list1.plus(list2)) //[1, 9, 2, 6, 7, 9, 2, 1, 2, 4, 6, 8, 10]
println(list1 + list2)  //[1, 9, 2, 6, 7, 9, 2, 1, 2, 4, 6, 8, 10]

3、ジップ

ペアで構成されるリストを返します。各ペアは、2 つのコレクション内の同じインデックスの要素で構成されます。返されるリストのサイズは、最小セットによって決まります。

val list1 = listOf(1, 9, 2, 6, 7, 9, 2)
val list2 = listOf(1, 2, 4, 6, 8, 10)
val list3 = list1.zip(list2)
println(list3.javaClass)
println(list3.get(0).javaClass)
println("${
      
      list3.get(0).first} , ${
      
      list3.get(0).second}")
list3.forEach {
    
     println(it) }
class java.util.ArrayList
class kotlin.Pair
1 , 1
(1, 1)
(9, 2)
(2, 4)
(6, 6)
(7, 8)
(9, 10)

4、解凍

ペアを含むリストからリストを含むペアを生成する

val list1 = listOf(Pair("leavesC", 1), Pair("leavesC_2", 2), Pair("leavesC_3", 3))
val list2 = list1.unzip()
println(list2.javaClass)
println(list2.first)
println(list2.second)
class kotlin.Pair
[leavesC, leavesC_2, leavesC_3]
[1, 2, 3]

6. 逐次演算子

1、リバース

指定されたリストの逆順でリストを返します。

val list1 = listOf(Pair("leavesC", 1), Pair("leavesC_2", 2), Pair("leavesC_3", 3))
val list2 = list1.reversed()
println(list2)      //[(leavesC_3, 3), (leavesC_2, 2), (leavesC, 1)]

2、並べ替え

自然にソートされたリストを返します

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sorted()
println(list2) //[1, 2, 4, 5, 9, 10]

val list3 = listOf("a", "c", "ab", "b", "cdd", "cda")
val list4 = list3.sorted()
println(list4) //[a, ab, b, c, cda, cdd]

3、並べ替え

指定された関数でソートされたリストを返します。

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedBy {
    
     it - 3 }
println(list2) //[1, 2, 4, 5, 9, 10]

4、降順で並べ替え

降順にソートされたリストを返します

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedDescending()
println(list2) //[10, 9, 5, 4, 2, 1]

5、降順で並べ替え

指定された関数によって降順にソートされたリストを返します。

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedByDescending {
    
     it % 2 }
println(list2) //[1, 9, 5, 2, 4, 10]

20. アブノーマル

kotlin の例外処理の基本的な形式は Java に似ています

fun compute(index: Int): Boolean {
    
    
    if (index !in 0..10) {
    
    
        throw IllegalArgumentException("参数错误")
    }
    return true
}

Java とは異なり、kotlin の throw 構造は別の式の一部として使用できる式です。

たとえば、次の例では、条件が満たされない場合、例外がスローされ、ステータス変数は初期化されません。

val status = if (index in 0..10) index else throw IllegalArgumentException("参数错误")

さらに、チェックされた例外は、try/catch ステートメントで例外をキャッチするか、処理のために呼び出し元にスローすることにより、Java で明示的に処理する必要があります。Kotlin は、チェックされた例外とチェックされていない例外を区別せず、関数によってスローされる例外(処理できるかどうか)を指定する必要はありません。

kotlin では、try キーワードによって式が導入され、式の値を変数に割り当てることができます。try コード ブロックが正常に実行された場合は、コード ブロック内の最後の式が結果となり、例外がキャッチされた場合は、対応する catch コード ブロック内の最後の式が結果となります。

次の例を見てください。try 式でラップされた式が例外をスローした場合、戻り値は null になり、それ以外の場合は true になります。

fun main() {
    
    
    compute(5)   //fun end : true
    compute(100) //fun end : null
}

fun compute(index: Int) {
    
    
    val status = try {
    
    
        if (index in 0..10) true else throw IllegalArgumentException("参数错误")
    } catch (e: Exception) {
    
    
        null
    }
    println("fun end : " + status)
}

ただし、catch ステートメントの return で compute 関数が終了した場合は、何も出力されません。

fun main() {
    
    
    compute(5)   //fun end : true
    compute(100) //没有任何输出
}

fun compute(index: Int) {
    
    
    val status = try {
    
    
        if (index in 0..10) true else throw IllegalArgumentException("参数错误")
    } catch (e: Exception) {
    
    
        return
    }
    println("fun end : " + status)
}

21. 演算子のオーバーロード

Kotlin では、型に対して事前定義された演算子の実装を提供できます。これらの演算子には、固定の記号表現 (+ や * など) と固定の優先順位があります。演算子のオーバーロードにより、演算子の動作を指定されたメソッドにマップできます。このような演算子を実装するには、クラスの固定名を持つメンバー関数または拡張関数を提供する必要があり、対応するオーバーロードされた演算子関数を演算子修飾子でマークする必要があります。

1. 単項演算子

オペレーター 関数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
はー a.dec()

2. 二項演算子

オペレーター 関数
a + b a.プラス(b)
a - b a.マイナス(b)
a * b a×(b)
a / b a.div(b)
a%b レム(b)
a…b a.rangeTo(b)
bのa b.(a)を含む
!in b !b.contains(a)
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

3. 配列演算子

オペレーター 関数
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, …, i_n] a.get(i_1, …, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, …, i_n] = b a.set(i_1, …, i_n, b)

4. 等号演算子

オペレーター 関数
a == b a?.equals(b) ?: b === null
a != b !(a?.equals(b) ?: b === null)

等価演算子は少し異なり、名前を指定するだけでなく、正確な関数構造の比較を取得するため、正しい等価性チェックを取得するためにより複雑な変換を実行します。

このメソッドは次のように正確に実装する必要があります。

operator fun equals(other: Any?): Boolean

演算子 === と !== は ID チェックに使用され (Java ではそれぞれ == と !=)、オーバーロードすることはできません。

5. 比較演算子

オペレーター 関数
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

すべての比較は、compareTo の呼び出しに変換され、Int 値を返す必要があります。

6. 関数呼び出し

方法 移行
あ() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, …, i_n) a.invoke(i_1, …, i_n)

7. 例

いくつかの例を見てください

data class Point(val x: Int, val y: Int) {
    
    

    //+Point
    operator fun unaryPlus() = Point(+x, +y)

    //Point++ / ++Point
    operator fun inc() = Point(x + 1, y + 1)

    //Point + Point
    operator fun plus(point: Point) = Point(x + point.x, y + point.y)

    //Point + Int
    operator fun plus(value: Int) = Point(x + value, y + value)

    //Point[index]
    operator fun get(index: Int): Int {
    
    
        return when (index) {
    
    
            0 -> x
            1 -> y
            else -> throw IndexOutOfBoundsException("无效索引")
        }
    }

    //Point(index)
    operator fun invoke(index: Int) = when (index) {
    
    
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("无效索引")
    }

}
fun main() {
    
    
    //+Point(x=10, y=-20)  =  Point(x=10, y=-20)
    println("+${
      
      Point(10, -20)}  =  ${
      
      +Point(10, -20)}")

    //Point(x=10, y=-20)++  =  Point(x=10, y=-20)
    var point = Point(10, -20)
    println("${
      
      Point(10, -20)}++  =  ${
      
      point++}")

    //++Point(x=10, y=-20)  =  Point(x=11, y=-19)
    point = Point(10, -20)
    println("++${
      
      Point(10, -20)}  =  ${
      
      ++point}")

    //Point(x=10, y=-20) + Point(x=10, y=-20)  =  Point(x=20, y=-40)
    println("${
      
      Point(10, -20)} + ${
      
      Point(10, -20)}  =  ${
      
      Point(10, -20) + Point(10, -20)}")

    //Point(x=10, y=-20) + 5  =  Point(x=15, y=-15)
    println("${
      
      Point(10, -20)} + ${
      
      5}  =  ${
      
      Point(10, -20) + 5}")

    point = Point(10, -20)
    //point[0] value is: 10
    println("point[0] value is: ${
      
      point[0]}")
    //point[1] value is: -20
    println("point[1] value is: ${
      
      point[1]}")

    //point(0) values is: 10
    println("point(0) values is: ${
      
      point(0)}")
}

22. 中置呼び出しと構造化ステートメント

1.中置呼び出し

Map 変数は次の形式で作成できます。

fun main() {
    
    
    val maps = mapOf(1 to "leavesC", 2 to "ye", 3 to "https://juejin.cn/user/923245496518439")
    maps.forEach {
    
     key, value -> println("key is : $key , value is : $value") }
}

to を使用してマップのキーと値の対応を宣言します。この形式の関数呼び出しは中置呼び出しと呼ばれます

kotlin標準ライブラリにおけるto関数の宣言は以下の通りで、拡張関数として存在しジェネリック関数であり、戻り値のPairは最終的に分割宣言を経てキーと値をMapに渡します。

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

中缀调用只能与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。中缀符号需要通过 infix 修饰符来进行标记

fun main() {
    
    
    val pair = 10 test "leavesC"
    val pair2 = 1.2 test 20
    println(pair2.javaClass) //class kotlin.Pair
}

infix fun Any.test(other: Any) = Pair(this, other)

对于 mapOf 函数来说,它可以接收不定数量的 Pair 类型对象,因此我们也可以通过自定义的中缀调用符 test 来创建一个 map 变量

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> =
    if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()

 val map = mapOf(10 test "leavesC", 20 test "hello")

2、解构声明

有时会有把一个对象拆解成多个变量的需求,在 kotlin 中这种语法称为解构声明

例如,以下例子将 Person 变量结构为了两个新变量:name 和 age,并且可以独立使用它们

data class Person(val name: String, val age: Int)

fun main() {
    
    
    val (name, age) = Person("leavesC", 24)
    println("Name: $name , age: $age")
    //Name: leavesC , age: 24
}

一个解构声明会被编译成以下代码:

val name = person.component1()
val age = person.component2()

其中的 component1()component2() 函数是在 kotlin 中广泛使用的约定原则的另一个例子。任何表达式都可以出现在解构声明的右侧,只要可以对它调用所需数量的 component 函数即可

需要注意的是,componentN() 函数需要用 operator 关键字标记,以允许在解构声明中使用它们

对于数据类来说,其自动生成了 componentN() 函数,而对非数据类,为了使用解构声明,需要我们自己来手动声明函数

class Point(val x: Int, val y: Int) {
    
    
    operator fun component1() = x
    operator fun component2() = y
}

fun main() {
    
    
    val point = Point(100, 200)
    val (x, y) = point
    println("x: $x , y: $y")
    //x: 100 , y: 200
}

如果我们需要从一个函数返回两个或者更多的值,这时候使用解构声明就会比较方便了

这里使用的是标准类 Pair 来包装要传递的数据,当然,也可以自定义数据类

fun computer(): Pair<String, Int> {
    
    
    //各种计算
    return Pair("leavesC", 24)
}

fun main() {
    
    
    val (name, age) = computer()
    println("Name: $name , age: $age")
}

此外,解构声明也可以用在 for 循环中

val list = listOf(Person("leavesC", 24), Person("leavesC", 25))
for ((name, age) in list) {
    
    
    println("Name: $name , age: $age")
}

对于遍历 map 同样适用

val map = mapOf("leavesC" to 24, "ye" to 25)
for ((name, age) in map) {
    
    
    println("Name: $name , age: $age")
}

同样也适用于 lambda 表达式

val map = mapOf("leavesC" to 24, "ye" to 25)
map.mapKeys {
    
     (key, value) -> println("key : $key , value : $value") }

如果在解构声明中不需要某个变量,那么可以用下划线取代其名称,此时不会调用相应的 componentN() 操作符函数

val map = mapOf("leavesC" to 24, "ye" to 25)
for ((_, age) in map) {
    
    
    println("age: $age")
}

二十三、Object 关键字

1、对象声明

在 kotlin 的世界中,可以通过对象声明这一功能来实现 Java 中的单例模式,将类声明与该类的单一实例声明结合到一起。与类一样,一个对象声明可以包含属性、方法、初始化语句块等的声明,且可以继承类和实现接口,唯一不被允许的是构造方法

与普通类的实例不同,对象声明在定义的时候就被立即创建了,不需要在代码的其它地方调用构造方法,因此为对象声明定义构造方法是没有意义的

interface Fly {
    
    

    fun fly()

}

open class Eat {
    
    

    fun eat() {
    
    
        println("eat")
    }

}

object Animal : Eat(), Fly {
    
    

    override fun fly() {
    
    
        println("fly")
    }

}

fun main() {
    
    
    Animal.fly()
    Animal.eat()
}

kotlin 中的对象声明被编译成了通过静态字段来持有它的单一实例的类,这个字段名字始终都是 INSTANCE

例如,对于 kotlin 中的如下两个对象声明

class Test {
    
    

    object SingleClass {
    
    
        val names = arrayListOf<String>()
    }

    object SingleClass2 {
    
    
        val names = arrayListOf<String>()
    }

}

在 Java 代码中来访问这两个对象

public static void main(String[] args) {
    
    
    Test.SingleClass.INSTANCE.getNames();
    Test.SingleClass2.INSTANCE.getNames();
}

2、伴生对象

如果需要一个可以在没有类实例的情况下调用但是需要访问类内部的函数(类似于 Java 中的静态变量/静态函数),可以将其写成那个类中的对象声明的成员

通过关键字 companion ,就可以获得通过容器类名称来访问这个对象的方法和属性的能力,不再需要显式地指明对象的名称

class Test {
    
    

    companion object {
    
    

        const val NAME = ""

        fun testFun() {
    
    

        }
    }

}

fun main() {
    
    
    Test.NAME
    Test.testFun()
}

1、工厂模式

可以利用伴生对象来实现工厂模式

private class User private constructor(val name: String) {
    
    

    companion object {
    
    
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    
    
    //构造函数私有,无法创建
    //val user1 = User("leavesC")
    val user2 = User.newById(10)
    val user3 = User.newByDouble(1.3)
}

2、指定名称

伴生对象既可以为其指定名字,也可以直接使用其默认名 Companion,在引用伴生对象时,可以自由选择是否要在类名后加上伴生对象名

如果使用的是其默认名 Companion(没有自定义名称),则以下两种引用方式都是等价的

val user2 = User.Companion.newById(10)
val user3 = User.newByDouble(1.3)

如果为伴生对象声明了自定义名称,引用方式等同

private class User private constructor(val name: String) {
    
    

    companion object UserLoader {
    
    
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    
    
    //构造函数私有,无法创建
    //val user1 = User("leavesC")
    val user2 = User.UserLoader.newById(10)
    val user3 = User.newByDouble(1.3)
}

3、实现接口

伴生对象也可以实现接口,且可以直接将包含它的类的名字当做实现了该接口的对象实例来使用

private class User private constructor(val name: String) {
    
    

    companion object UserLoader : Runnable {
    
    

        override fun run() {
    
    

        }
    }

}

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    
    
    //User 会直接被当做 Runnable 的实例
    val thread = newThread(User)
    val thread2 = newThread(User.UserLoader)
}

3、对象表达式

object 能用来声明匿名对象,可用于替代 Java 中的匿名内部类,且对象表达式中的代码可以访问并修改其外部的非 final 型的变量

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    
    
    var count = 0
    val thread = newThread(object : Runnable {
    
    
        override fun run() {
    
    
            count++
        }
    })
}

二十四、委托

1、委托模式

委托模式是一种基本的设计模式,该模式下有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。kotlin 原生支持委托模式,可以零样板代码来实现,通过关键字 by 实现委托

interface Printer {
    
    

    fun print()
    
}

class DefaultPrinter : Printer {
    
    

    override fun print() {
    
    
         println("DefaultPrinter print")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer

fun main() {
    
    
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter print
}

CustomPrinter 的 by 子句表示将会在 CustomPrinter 中存储 printer 变量,并且编译器将为 CustomPrinter 隐式生成 Printer 接口的所有抽象方法,并将这些方法的调用操作转发给 printer

此外,CustomPrinter 也可以决定自己实现部分方法或全部自己实现,但重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现

interface Printer {
    
    

    val message: String

    fun print()

    fun reprint()

}

class DefaultPrinter : Printer {
    
    

    override val message: String = "DefaultPrinter message"

    override fun print() {
    
    
        println(message)
    }

    override fun reprint() {
    
    
        println("DefaultPrinter reprint")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer {
    
    

    override val message: String = "CustomPrinter message"

    override fun reprint() {
    
    
        println("CustomPrinter reprint")
    }

}

fun main() {
    
    
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter message
    printer.reprint() //CustomPrinter reprint
}

2、属性委托

kotlin 支持通过委托属性将对一个属性的访问操作委托给另外一个对象来完成,对应的语法格式是:

val/var <属性名>: <类型> by <表达式>

属性的委托不必实现任何的接口,但需要提供一个 getValue() 方法与 setValue()(对于 var 属性),对一个属性的 get 和 set 操作会被委托给属性的委托的这两个方法

class Delegate {
    
    
    //第一个参数表示被委托的对象、第二个参数表示被委托对象自身的描述
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    
    
    }
	//第一个参数表示被委托的对象、第二个参数表示被委托对象自身的描述,第三个参数是将要赋予的值
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    
    
    }
}

看以下的小例子,通过输出值就可以看出各个方法的调用时机

package test

import kotlin.reflect.KProperty

class Delegate {
    
    

    private var message: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    
    
        println("${
      
      thisRef?.javaClass?.name}, thank you for delegating '${
      
      property.name}' to me!")
        return message ?: "null value"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    
    
        println("$value has been assigned to '${
      
      property.name}' in ${
      
      thisRef?.javaClass?.name}.")
        message = value
    }
}

class Example {
    
    
    var strValue: String by Delegate()
}

fun main() {
    
    
    val example = Example()
    println(example.strValue)
    example.strValue = "leaveC"
    println(example.strValue)
//    test.Example, thank you for delegating 'strValue' to me!
//    null value
//    leaveC has been assigned to 'strValue' in test.Example.
//    test.Example, thank you for delegating 'strValue' to me!
//    leaveC
}

3、延迟属性

lazy() 是接受一个 lambda 并返回一个 Lazy < T > 实例的函数,返回的实例可以作为实现延迟属性的委托,第一次调用 get() 会执行已传递给 lazy() 函数的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果

class Example {
    
    

    val lazyValue1: String by lazy {
    
    
        println("lazyValue1 computed!")
        "Hello"
    }

    val lazyValue2: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    
    
        println("lazyValue2 computed!")
        computeLazyValue()
    }

    private fun computeLazyValue() = "leavesC"

}

fun main() {
    
    
    val example = Example()
    println(example.lazyValue1) //lazyValue1 computed!     Hello
    println(example.lazyValue1) //Hello
    println(example.lazyValue2) //lazyValue2 computed! leavesC
}

默认情况下,对于 lazy 属性的求值是带同步锁的(synchronized),即带有 LazyThreadSafetyMode.SYNCHRONIZED 参数,此时该值只允许同一时刻只能有一个线程对其进行初始化,并且所有线程会看到相同的初始化值。如果初始化委托的同步锁不是必需的,即如果允许多个线程同时执行,那么可以将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么可以使用 LazyThreadSafetyMode.NONE 模式, 此时不会有任何线程安全的保证以及相关的资源开销

4、可观察属性

Delegates.observable() 接受两个参数:初始值以及修改属性值时的回调函数。当为属性赋值后就会调用该回调函数,该回调函数包含三个参数:被赋值的属性、旧值与新值

fun main() {
    
    
    val example = Example()
    example.age = 24 //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = 27 //kProperty.name: age , oldValue: 24 , newValue: 27
}

class Example {
    
    
    var age: Int by Delegates.observable(-100) {
    
     kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${
      
      kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    }
}

如果想要拦截一个赋值操作并判断是否进行否决,可以使用 vetoable() 函数,通过返回一个布尔值来决定是否进行拦截,该判断逻辑是在属性被赋新值生效之前进行

fun main() {
    
    
    val example = Example()
    example.age = 24  //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = -10 //kProperty.name: age , oldValue: 24 , newValue: -10
    example.age = 30  //kProperty.name: age , oldValue: 24 , newValue: 30 (oldValue 依然是 24,说明第二次的赋值操作被否决了)
}

class Example {
    
    
    var age: Int by Delegates.vetoable(-100) {
    
     kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${
      
      kProperty.name} , oldValue: $oldValue , newValue: $newValue")
        age <= 0 //返回true 则表示拦截该赋值操作
    }
}

5、把属性储存在映射中

可以在一个 map 映射里存储属性的值,然后把属性的存取操作委托给 map 进行管理

fun main() {
    
    
    val student = Student(
        mapOf(
            "name" to "leavesCZY",
            "age" to 24
        )
    )
    println(student.name)
    println(student.age)
}

class Student(val map: Map<String, Any?>) {
    
    
    val name: String by map
    val age: Int by map
}

在上述示例中,属性 name 和 age 都是不可变的(val),因此 map 的类型也是 Map 而非 MutableMap(MutableMap 在赋值后可以修改),因此如果为了支持 var 属性,可以将只读的 Map 换成 MutableMap

6、局部委托属性

可以将局部变量声明为委托属性

class Printer {
    
    

    fun print() {
    
    
        println("temp.Printer print")
    }

}

fun getPrinter(): Printer {
    
    
    println("temp.Printer getPrinter")
    return Printer()
}

//局部委托
fun example(getPrinter: () -> Printer) {
    
    
    val lPrinter by lazy(getPrinter)
    val valid = true
    if (valid) {
    
    
        lPrinter.print()
    }
}

fun main() {
    
    
    example {
    
     getPrinter() }
    //temp.Printer getPrinter
    //temp.Printer print
}

委托变量只会在第一次访问时才会进行初始化,因此如果 valid 为 false 的话,getPrinter() 方法就不会被调用

二十五、注解

注解是将元数据附加到代码元素上的一种方式,附件的元数据就可以在编译后的类文件或者运行时被相关的源代码工具访问

注解的语法格式如下所示:

annotation class AnnotationName()

注解的附加属性可以通过用元注解标注注解类来指定:

  • @Target 指定该注解标注的允许范围(类、函数、属性等)
  • @Retention 指定该注解是否要存储在编译后的 class 文件中,如果要保存,则在运行时可以通过反射来获取到该注解值
  • @Repeatable 标明允许在单个元素上多次使用相同的该注解
  • @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class AnnotationName()

注解可以声明包含有参数的构造函数

annotation class OnClick(val viewId: Long)

允许的参数类型有:

  • 原生数据类型,对应 Java 原生的 int 、long、char 等
  • 字符串
  • class 对象
  • 枚举
  • 其他注解
  • 以上类型的数组

注解参数不能包含有可空类型,因为 JVM 不支持将 null 作为注解属性的值来存储

看一个在运行时获取注解值的例子

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class OnClick(val viewId: Long)

class AnnotationsTest {
    
    

    @OnClick(200300)
    fun onClickButton() {
    
    
        println("Clicked")
    }

}

fun main() {
    
    
    val annotationsTest = AnnotationsTest()
    for (method in annotationsTest.javaClass.methods) {
    
    
        for (annotation in method.annotations) {
    
    
            if (annotation is OnClick) {
    
    
                println("method name: " + method.name)  //method name: onClickButton
                println("OnClick viewId: " + annotation.viewId)  //OnClick viewId: 200300
            }
        }
    }
}

おすすめ

転載: blog.csdn.net/gqg_guan/article/details/131992722