Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Kotlinスタートブック読書勉強会 第12章 補足

Kotlinスタートブック読書勉強会 第12章 補足


referenceKotlinイン・アクション や 他のサイトからの 引用 が中心

または 赤べこ本 第13章 以降 に掲載 されていて, 12章 のタイミングでついでに習った方が良さそうな内容


4. 安全呼び出し

list 12.10 拡張関数 let の 定義

  • Oct 2017 の コミット で 中の実装が少し変更された
    • contract { ... } が追加されている
  • 以下の定義は 上記コミット前の実装内容
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

任意の型 T に対する拡張関数

  • Nullable Type も対象

-> @koher(Koshizawa Yuta)さんの Qiita 記事 「 JavaプログラマがKotlinでつまづきがちなところ から 引用

Any と Any?

...(中略)
T が Nullable Type を表すことができるからです。
型パラメータは制約を付けなければ Nullable Type を含めて
すべての型のプレースホルダーとして働きます
(デフォルトで upper bound が Any? )
  • let の定義は T.let なので, Nullable Type 含めて 任意の型から呼び出せる
fun main(args: Array<String>) {
    val a: String = "test"
    val b: String? = null

    a.let { println(it) }  // test
    b.let { println(it) }  // null
}

-> 第8章 補足 参照


list 12.11 let + 安全呼び出し

-> @koher(Koshizawa Yuta) さんの Qiita 記事 「 null安全でない言語は、もはやレガシー言語だ から 引用

map メソッドに渡されたラムダ式は、 Optional の値が nil でない場合だけ実行され、 map の結果として返されます。 ラムダ式の引数に渡されるのは non-null 化された値です。

もし Optional の値が nil であれば結果も nil になります。

Kotlin では nullable に map メソッドはないですが、 ?.let メソッドを組み合わせることで number?.let { it * it } と書くことができます。

  • koher さんの 記事を踏まえて, swift の map, flatMap の実装 と Kotlin の ?.let を比較

swift (Optional.map) との比較

// swift
func square(i: Int) -> Int {
  return i * i
}

let a: Int? = 5
let aSquare = a.map{ square(i: $0) }
let aSquare2 = a.map( square )
print(aSquare)         // Optional(25)
print(aSquare2)        // Optional(25)
// kotlin
fun square(i: Int): Int = i * i

fun main(args: Array<String>) {
    val a: Int? = 5
    val aSquare = a?.let { square(it) }  // list 12.11 `let` + 安全呼び出し
    val aSquare2 = a?.let(::square)
    println(aSquare)   // 25  (`aSquare` は Int? 型)
    println(aSquare2)  // 25  (`aSquare2` は Int? 型)
}
  • Nullable?.let{ ...} は、swift の Optional.map と同じ振る舞い

swift (Optional.flatMap) との比較

// swift (戻り値が Int?)
func square2(i: String) -> Int? {
  return Int(i).map{ $0 * $0 }
}

let a: String? = "5"
let aSquare = a.map{ square2(i: $0) }
let aSquare2 = a.map( square2 )
print(aSquare)         // Optional(Optional(25))
print(aSquare2)        // Optional(Optional(25))

let aSquare3 = a.flatMap{ square2(i: $0) }
let aSquare4 = a.flatMap( square2 )
print(aSquare3)        // Optional(25)
print(aSquare4)        // Optional(25)
// kotlin (戻り値が Int?)
fun square2(i: String): Int? =
    i.toIntOrNull()?.let { it * it }

fun main(args: Array<String>) {
    val a: String? = "5"
    val aSquare3 = a?.let { square2(it) }
    val aSquare4 = a?.let(::square2)
    println(aSquare3)   // 25  (`aSquare3` は Int? 型)
    println(aSquare4)   // 25  (`aSquare4` は Int? 型)
}
  • Int?? は無いので, swift の Optional.flatMap とも同じ振る舞いに

swift (if let) との比較

-> 「 Kotlinのif let - Qiita 」 から 引用

// swift
let name: String? = "John Doe"

if let it = name {
  print("Name: \(it)")
}
// kotlin
val name: String? = "John Doe"

name?.let {
   print("Name: ${it}")
}

スコープ関数

-> taro さんの qiita 記事「 Kotlin スコープ関数 用途まとめtaro さんのスライド「 データクラスの話/スコープ関数の話 #rkt (p27 〜)」 や「 Kotlin入門 (サポーターズKotlin勉強会 #3) (p37 〜) 」 参照

  • 赤べこ本で 紹介されているのは 以下の3つ
    • let
      • 第12章-4 安全呼び出し list 12.10 (p179)
    • apply
      • 第15章-3 記事ビューのリスト表示 list 15.10 (p252)
    • run
      • 第15章-4 記事詳細画面 list 15.14 (p256)
  • 他に 1.1 から also が追加
  • with もあるけれどあまり使われないらしい
  • run は (拡張関数 ではない)普通の関数 も定義されている

list 12.10 拡張関数 let の 定義

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

list 15.14 拡張関数 run の 定義

public inline fun <T, R> T.run(block: T.() -> R): R = block()

拡張関数 also の 定義

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

list 15.10 拡張関数 apply の 定義

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

関数 run の 定義

public inline fun <R> run(block: () -> R): R = block()
  • Oct 2017 の コミット で 中の実装が少し変更された
    • contract { ... } が追加されている
  • ↑の定義は コミット前の実装内容

block = 各スコープ関数 の 引数に渡す関数

スコープ関数 の種類 スコープ関数 の 戻り値 block の 引数 block の中の this
let block の実行結果 レシーバ 変わらない
run block の実行結果 無し レシーバ
also レシーバ レシーバ 変わらない
apply レシーバ 無し レシーバ
fun main(args: Array<String>) {
    val s1 = "hoge".let { it.toUpperCase() }
    println(s1) // HOGE (`let` の 引数に渡した lambda の結果)

    val s2 = "hoge".run { /* `this.` 省略 */ toUpperCase() }
    println(s2) // HOGE (`run` の 引数に渡した lambda の結果)

    val s3 = "hoge".also { it.toUpperCase() }
    println(s3) // hoge (`also` の レシーバー)

    val s4 = "hoge".apply { /* `this.` 省略 */ toUpperCase() }
    println(s4) // hoge (`apply` の レシーバー)
}
class A {
    fun foo() {
        println(this)        // A@27c170f0

        "hoge".let {
            println(this)    // A@27c170f0    // `this` は変わらない
            println(it)      // hoge          // 引数 = レシーバー
        }
        "hoge".run {
            println(this)    // hoge          // `this` が変わる(= レシーバー)
            println(this@A)  // A@27c170f0    // `this@クラス名` で ラムダの外側のクラスの参照 を得る
        }

        "hoge".also {
            println(this)    // A@27c170f0    // `this` は変わらない
            println(it)      // hoge          // 引数 = レシーバー
        }
        "hoge".apply {
            println(this)    // hoge          // `this` が変わる(= レシーバー)
            println(this@A)  // A@27c170f0    // `this@クラス名` で ラムダの外側のクラスの参照 を得る
        }
    }

    fun bar() {
        println(this)        // A@27c170f0

        // 以下 `this.` 省略. レシーバー は 全て `A`
        let {
            println(this)    // A@27c170f0
            println(it)      // A@27c170f0
        }
        run {
            println(this)    // A@27c170f0
        }

        also {
            println(this)    // A@27c170f0
            println(it)      // A@27c170f0
        }
        apply {
            println(this)    // A@27c170f0
        }
    }
}

fun main(args: Array<String>) {
    A().foo(); println()
    A().bar()
}
// top-level の関数
fun main(args: Array<String>) {
    println(this)      // コンパイルエラー('this' is not defined in this context).
                       // top-level の関数 は static なので `this` を参照できない.

    "hoge".let {
        println(this)  // コンパイルエラー(同上)
        println(it)    // hoge (引数 = レシーバー)
    }
    "hoge".run {
        println(this)  // hoge (`this` = レシーバー)
    }

    let { // コンパイルエラー(Unresolved reference: let)
    }
    run { // `run` だけ 普通の関数(拡張関数ではない) も存在する
    }
}

apply の使用例

-> 「Idiomatic Kotlin. Best Practices. 」 (日本語訳) から 引用

//Don't
val dataSource = BasicDataSource()
dataSource.driverClassName = "com.mysql.jdbc.Driver"
// ...(中略)
dataSource.minIdle = 4
//Do
val dataSource = BasicDataSource().apply {
    driverClassName = "com.mysql.jdbc.Driver"
    // ...(中略)
    minIdle = 4
}
  • also でも同じ事が可能.
    • it. 指定が冗長
val dataSource2 = BasicDataSource().also {
    it.driverClassName = "com.mysql.jdbc.Driver"
    // ...(中略)
    it.minIdle = 4
}
  • letrun でも同じ事が可能. (この使い方は 一般的ではない)
    • 引数で渡す ラムダ の 最後の式 を レシーバ に する
val dataSource3 = BasicDataSource().run {
    driverClassName = "com.mysql.jdbc.Driver"
    // ...(中略)
    minIdle = 4
    this        // レシーバ自身を返す
}

val dataSource4 = BasicDataSource().let {
    it.driverClassName = "com.mysql.jdbc.Driver"
    // ...(中略)
    it.minIdle = 4
    it          // レシーバ自身を返す
}

buildString (apply の使用例)

  • buildString 標準ライブラリ関数 の定義
    • 引数: レシーバ付きラムダ
      • レシーバ は StringBuilder
public inline fun buildString(builderAction: StringBuilder.() -> Unit): String =
        StringBuilder().apply(builderAction).toString()

-> Kotlin in Action - Chapter 5 の サンプルソース から 引用

fun alphabet() = buildString {
    for (letter in 'A'..'Z') {
        append(letter)
    }
}

fun main(args: Array<String>) {
    println(alphabet())       // ABCDEFGHIJKLMNOPQRSTUVWXYZ
}

注意: 暗黙の this に対してローカル変数が勝つ (シャドーイング の問題)

-> @koher(Koshizawa Yuta)さんの Qiita 記事 「 JavaプログラマがKotlinで便利だと感じること <コメント欄> 参照

data class Baz(var title: String = "default property")

class A {
    fun foo() {
        var title = "local variable"

        val baz = Baz().apply {
            println(this)  // Baz(title=default property)

            // 暗黙の this に対してローカル変数が勝つ
            title = "updated"
        }
        println(title)  // updated [※ローカル変数が更新された]
        println(baz)    // Baz(title=default property) [※`apply`対象の`Baz`は更新されていない]
    }
}

fun main(args: Array<String>) {
    A().foo()
}

-> 「 apply() 要らなくない? (夏のKotlin LT祭 #jkug) 」 から 引用

代替案(also)
代替案(名前付き引数)
...
apply, run, with は
let, also で代用
でいいのでは?

this でラベルされた式 (apply の使用例)

-> Kotlin in Action - Chapter 8 の サンプルソース から 引用

  • apply をネストした例
fun main(args: Array<String>) {
    //                            ↓ ネストの外側 で `sb@` ラベル 指定 ( StringBuilder() を指す )
    println(StringBuilder().apply sb@{
       listOf(1, 2, 3).apply {
           this@sb.append(this.toString())
       } // ↑             ↑ 内側の `apply` 内の `this` は `listOf(1, 2, 3)` を指す
    }) //
    //     `this@ラベル名` で 内側の `apply` 内から 外側 の `StringBuilder()` にアクセス

    // 追記
    println(StringBuilder().also { sb ->  // label 使用するよりも also に変更した方が良い気が..
        listOf(1, 2, 3).apply {
            sb.append(this.toString())
        }
    })
} 

結果

[1, 2, 3]
[1, 2, 3]

apply の中で 参照先 が変わる場合(不変オブジェクト)の場合

  • 不変オブジェクト(String) の 例
fun main(args: Array<String>) {
    val a = A()
    println(a.next())  // a
    println(a.next())  // aa
}

class A {
    var current = "a"  // 再代入可能(`var`) + 不変オブジェクト(`String`)

    fun next() = current.apply { // 変更する前に 現在の 文字列 を 結果として返す
        current = repeat(2)      // 再代入
        println("apply: end...")
    }
}

結果

apply: end...
a
apply: end...
aa
  • 可変オブジェクト(StringBuilder) と 比較
fun main(args: Array<String>) {
    val b = B()
    println(b.next())  // bb
    println(b.next())  // bbb
}

class B {
    val current = StringBuilder("b") // 可変オブジェクト(`StringBuilder`)

    fun next() = current.apply {
        append("b")                  // 状態を変更
        println("apply: end...")
    }
}

結果

apply: end...
bb
apply: end...
bbb

LocalDateiterator (apply の使用例)

-> Kotlin in Action - Chapter 7 の サンプルソース から 引用

import java.time.LocalDate

// `iterator` の仕様は 赤べこ本 第4章 list 4.9 (p63) 参照
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
        object : Iterator<LocalDate> {
            var current = start        // 再代入可能(`var`) + 不変オブジェクト(`LocalDate`)

            override fun hasNext() =
                    current <= endInclusive

//                                         ↓
            override fun next() = current.apply {  // 変更する前に 現在の 日付 を 結果として返す
                current = plusDays(1)  // 再代入
            }
        }

fun main(args: Array<String>) {
    // LocalDate (2017-01-01)
    val newYear = LocalDate.ofYearDay(2017, 1)
    // minusDays(1) -> LocalDate (2016-12-31) -> rangeTo 呼び出し -> ClosedRange<LocalDate> [ start(2016-12-31)..endInclusive(2017-01-01) ]
    val daysOff = newYear.minusDays(1)..newYear
    for (dayOff in daysOff) { println(dayOff) }
}

結果

2016-12-31
2017-01-01

run{...} で 無名スコープ

-> @koher(Koshizawa Yuta)さんの Qiita 記事 「 JavaプログラマがKotlinでつまづきがちなところ から 引用

inline と Non-local return

...(中略)
たとえば、 Java では `{ }` で簡単に無名スコープを作れますが
Kotlin では `{ }` はラムダ式に割り当てられており無名スコープが作れません。
...(中略)
Kotlin ではそんなときに `run` を使います。

// Kotlin
for (x in list) {
  run { // 無名スコープの代わり
    val a = 2
    ...
    if (x < a) {
      break // ラムダ式の中だけど外のループを `break` できる
    }
  }

  run {
    val a = 3 // スコープが異なるので `a` を作れる
    ...
  }
}
  • 値を返す ブロック としても
class A {
    fun foo() {
        val a = 99
        println(this)  // A@27c170f0

        val x = run {  // (ここの `run` は `let` でも 代用可能)
            val a = 2
            val b = 2
            a + b
        }
        val y = run {
            println(this) // A@27c170f0 (`A`をレシーバとして`run`を実行 -> 外側のスコープと`this`(=A)は変わらない)
            val a = 3
            val b = 3
            a + b
        }
        println(x + y)  // 10
    }
}

fun main(args: Array<String>) {
    A().foo()

    val x = run {  // ここの `run` は 拡張関数 ではなく 普通の関数 (`let` では 代用不可)
        val a = 1
        val b = 1
        a + b
    }
    val y = run {
        val a = 4
        val b = 4
        a + b
    }
    println(x + y)  // 10
}

?: の後に run { ... }

if (a != null) {
    // ...(中略)
} else {
    // ...(中略)
}

の代わりとなる。

{ ... } ?: run { ... }

-> 「 best way to null check in kotlin? - Stack Overflow 」 から 引用

a?.let {
   println("not null")
   println("Wop-bop-a-loom-a-boom-bam-boom")
} ?: run {
    println("null")
    println("When things go null, don't go with them")
}

Java経験者が学ぶKotlin入門者向けハンズオン @ IDOM #idom_kt のハンズオン で

public class Lesson5Activity extends AppCompatActivity {
    // ...(中略)
    private void lesson5_1() {
        // ...(中略)
        if (newPokemon != null) {
            textView.setText(newPokemon.getName());
        } else {
            textView.setText(hoOh.getName());
        }
    }

    private void lesson5_2() {
        final Pokemon pokemon = gotchaLegend();
        if (pokemon != null) {
            pokemon.setEffortHp(252);
            // ...(中略)
            textView2.setText(pokemon.getName());
        } else {
            textView2.setText("Mistake!");
        }
    }
}
  • Convert Java File to Kotlin File して
    • この時点では if(● != null) {...} else {...} のまま
class Lesson5Activity : AppCompatActivity() {
    // ...(中略)
    private fun lesson5_1() {
        // ...(中略)
        if (newPokemon != null) {
            textView!!.setText(newPokemon!!.name)
        } else {
            textView!!.text = hoOh.name
        }
    }

    private fun lesson5_2() {
        val pokemon = gotchaLegend()
        if (pokemon != null) {
            pokemon.effortHp = 252
            // ...(中略)
            textView2!!.text = pokemon.name
        } else {
            textView2!!.text = "Mistake!"
        }
    }
}
  • ↓ さらに 手作業で変更 して
    • ?.let { ... } ?: run { ... }
    • ?.run { ... } ?: run { ... }
class Lesson5Activity : AppCompatActivity() {
    // ...(中略)
    private fun lesson5_1() {
        // ...(中略)
        newPokemon?.let {
            textView.text = it.name
        } ?: run {
            textView.text = hoOh.name
        }
    }

    private fun lesson5_2() {
        gotchaLegend()?.run { // runを使うとblock内にthisでアクセス可能. 動作はletと同じ letはitでのアクセス
            effortHp = 252
            // ...(中略)
            textView2.text = name
        } ?: run {
            textView2.text = "Mistake"
        }
    }
}

swift (guard let) との比較: ?: return

-> Swift constructs for Kotlin から 引用

// swift
guard let unwrapped = wrapped else {  
  return
}
// kotlin
val unwrapped = wrapped ?: return

swift (guard let) との比較: ?: run { ... }

-> 鹿野壮@WPJでiOSアプリ記事 on Twitter: "Kotlinのnull安全と、Swiftのoptionalを比較してみました" から 引用

  • Kotlin vs Swift (null check)
// swift
func hoge(str:String?) {
  guard let str2 = str else {
    print("str is null")
    return
  }
  print(str2)
}

-> ラルフ on Twitter: "別変数バージョン" から 引用

fun hoge(str: String?) {
    val str2 = str ?: run {
        println("str is null")
        return                 // Non-local returns
    }
    println(str2)
}

他にも early return を実現する例 (takeIf)

-> A few ways to implement a Swift like guard in Kotlin から 引用

val thing: Int? = null
val something = thing?.takeIf { it < 10 } ?: return  // null または 10以上 の場合は return

run 使用で function の block で return 書かなくて済む

-> Kota Mizushima on Twitter: "ちなみに、私はreturn書きたくない人なので、Kotlinではrun使いまくります。たとえば、 https://t.co/UemolSXtHy とか。" から 引用

override fun toString(): String = run {
    "Left(${value})" // lambda 内なので return 省略
}

Lambda が 入れ子になった場合 の 命名規則

-> サイバーエージェント・FRESH の規約 「 FRESH!_Kotlin_StyleGuide (日本Androidの会 2017年7月定例会 #jag1707) 」「 Kotlin style guide of FRESH! 」 から 引用

Lambda 式の中の it は スコープの広い方にする
// bad
{ hoge ->
    hoge?.let { it.foo() }
}
// good
{
    it?.let { hoge -> hoge.foo() }
}

それに対する 反応:

たろう on Twitter: "#jag1707 面白い、私は逆だなー"

wm3 on Twitter: "関数入れ子にする時はスコープの広い方を it にするのか…。逆にしたいなあ。 #jag1707"


公式 Reference では

In lambdas which are short and not nested, it's recommended to use the it convention instead of declaring the parameter explicitly.

短くネストされていないラムダでは、 it 規約を使用することをお勧めします。 パラメータを明示的に宣言するのではなく

In nested lambdas with parameters, parameters should be always declared explicitly.

パラメータ付きネストされたラムダでは、パラメータは常に明示的に宣言する必要があります。


おまけ

前回(#8)「11章 ジェネリック」(#read_kotlin) の補足

"変位指定で反変の使い所が分からないので何が嬉しいか誰か教えて欲しい"

という話があったようなので

Kotlinイン・アクション から 反変(contravariant) の例を引用

反変(contravariant) の 例 (Comparator, sortedWith)

Iterable<T>.sortedWith の定義

//                                                           ↓ 使用場所変位指定 ( use-site variance )
public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
    // ※中略
    return toMutableList().apply { sortWith(comparator) }
}

-> Kotlinイン・アクション 9.3.4 から 引用

val anyComparator = Comparator<Any> { e1, e2 ->
    e1.hashCode() - e2.hashCode()
}

fun main(args: Array<String>) {
  
    // Iterable<String> の sortedWith に Comparator<Any> を渡す -> sortedWith の定義 <T> の型 = String
    // 
    val strings = listOf("b", "a")            // List<String> -> Collection<String> -> Iterable<String>
    val s = strings.sortedWith(anyComparator) // s は List<String>

    // 以下、追記 
 
    // Iterable<Int> の sortedWith に Comparator<Any> を渡す -> sortedWith の定義 <T> の型 = Int
    // 
    val ints = setOf(3, 2, 1)                 // Set<Int> -> Collection<Int> -> Iterable<Int>
    val i = ints.sortedWith(anyComparator)    // i は List<Int>

    // 反変ではない例
    // 
    // Iterable<Char> の sortedWith2 に Comparator<Any> を渡す -> sortedWith の定義 <T> の型 = Any
    // 
    val chars = listOf('b', 'a')              // List<Char> -> Collection<Char> -> Iterable<Char>
    val c = chars.sortedWith2(anyComparator)  // c は List<Any> (※ List<Char> にならない)
} //                      ↑
//
// Iterable<T>.sortedWith の実装をコピーして                 ↓ の `in` を削除
public fun <T> Iterable<T>.sortedWith2(comparator: Comparator<T>): List<T> {
    if (this is Collection) {
        if (size <= 1) return this.toList()
        @Suppress("UNCHECKED_CAST")
        return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList()
    }
    return toMutableList().apply { sortWith(comparator) }
}

-> Understanding Generics and Variance in Kotlin – ProAndroidDev の 解説 と から引用

interface Compare<in T> {
  fun compare(first: T, second: T): Int
}

This means that all methods in Compare can have T as an argument but cannot return T type.

This makes Compare contravariant.

- Covariance(共変) Contravariance(反変) Invariance(非変)
Purpose Producer Consumer Producer + Consumer
Example ImmutableList Compare MutableList
Java extends super -
Kotlin out in -

-> Java総称型のワイルドカードを上手に使いこなすための勘所 - 達人プログラマーを目指して から引用

C#のキーワードが暗示しているように 共変は出力専用の型反変は入力専用の型 でなくてはならないと言い換えることができます。

上限型つきのワイルドカード型

? extends Number」のようにワイルドカードの型に上限を設定することができます (中略) 上限つきのワイルドカード型は局所的には共変な型変数のように振舞う ことがわかります。 メソッドから値を戻すことはできるが、メソッドに値を渡すことができない(ただし、nullは例外)

下限型つきのワイルドカード型

? super Integer」のようにワイルドカードの型に下限を設定することができます。 (中略) 下限つきのワイルドカード型は局所的には反変な型変数のように振舞う ことがわかります。 (中略) Object型で値を返すことが可能ですが、

GetとPutの法則(PECSの法則)

たとえば、Comparatorは以下のように定義されています。

public interface Comparable<T> {
    public int compareTo(T o);
}

Collections.sort()メソッドは以下のシグネチャになっています。

public static <T> void sort(List<T> list, Comparator<? super T> c) {...}

反変(contravariant) の 他の例 (.NET の ドキュメント)

-> 反変のジェネリック型パラメーターを持つジェネリック インターフェイス 参照

interface Shape {
    val area: Double
}

data class Circle(val radius: Double) : Shape {
    override val area: Double
        get() = Math.PI * radius * radius
}

data class Rectangle(val width: Double, val height: Double) : Shape {
    override val area: Double
        get() = width * height
}

val shapeComparator = Comparator<Shape> { e1, e2 ->
    e1.area.compareTo(e2.area)
}

fun main(args: Array<String>) {
    val circles = listOf(Circle(7.2), Circle(100.0), Circle(0.01))
    val rectangles = listOf(Rectangle(2.0, 9.0), Rectangle(4.3, 2.1), Rectangle(3.1, 1.3))

    // Iterable<Circle>    の sortedWith に Comparator<Shape> を渡す -> sortedWith の定義 <T> の型 = Circle
    // Iterable<Rectangle> の sortedWith に Comparator<Shape> を渡す -> sortedWith の定義 <T> の型 = Rectangle
    //
    val c = circles.sortedWith(shapeComparator)    // c は List<Circle>
    val r = rectangles.sortedWith(shapeComparator) // r は List<Rectangle>

    println(c.map { it.area }) // [3.1415926535897936E-4, 162.8601631620949, 31415.926535897932]
    println(r.map { it.area }) // [4.03, 9.03, 18.0]


    // 以下、反変ではない例
    //
    // Iterable<Circle>    の sortedWith2 に Comparator<Shape> を渡す -> sortedWith の定義 <T> の型 = Shape
    // Iterable<Rectangle> の sortedWith2 に Comparator<Shape> を渡す -> sortedWith の定義 <T> の型 = Shape
    //
    val c2 = circles.sortedWith2(shapeComparator)    // c2 は List<Shape>
    val r2 = rectangles.sortedWith2(shapeComparator) // r2 は List<Shape>
} //                      ↑
//
// Iterable<T>.sortedWith の実装をコピーして                 ↓ の `in` を削除
public fun <T> Iterable<T>.sortedWith2(comparator: Comparator<T>): List<T> {
    if (this is Collection) {
        if (size <= 1) return this.toList()
        @Suppress("UNCHECKED_CAST")
        return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList()
    }
    return toMutableList().apply { sortWith(comparator) }
}

再掲: Iterable<T>.sortedWith の定義

//                                                           ↓ 使用場所変位指定 ( use-site variance )
public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
    // ※中略
    return toMutableList().apply { sortWith(comparator) }
}

データコピー関数 の 例

-> 再掲: Java総称型のワイルドカードを上手に使いこなすための勘所 - 達人プログラマーを目指して から 引用

GetとPutの法則(PECS の法則)

(中略) したがって、コレクションの要素値をコピーするメソッドは以下のように定義するのが正解です。

public static <E> void copy(List<? super E> dst, List<? extends E> src) {
	for (int i = 0; i < src.size(); i++) {
		dst.set(i, src.get(i));
	}
}

データコピー関数 の 例 (2つの型パラメータ)

-> Kotlin in Action - Chapter 9 の サンプルソース(5_1_CopyDataAny.kt) から 引用

//   ↓ <T> <R> 2つの型パラメータ
fun <T : R, R> copyData(source: MutableList<T>,
                        destination: MutableList<R>) {
    for (item in source) {
        destination.add(item)
    }
}

fun main(args: Array<String>) {
    val ints = mutableListOf(1, 2, 3)   // MutableList<Int> (<T> の型 = Int)
    val anyItems = mutableListOf<Any>() // MutableList<Any> (<R> の型 = Any)
    copyData(ints, anyItems)
    println(anyItems) // [1, 2, 3]
}

データコピー関数 の 例 (out 投影型 パラメータ)

-> Kotlin in Action - Chapter 9 の サンプルソース(5_2_CopyDataOut.kt) から 引用

//  ↓ <T> 1つの型パラメータ          ↓ 共変 <out T>
fun <T> copyData(source: MutableList<out T>,
                 destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
    // 追記
    // ※ out 指定 -> 引数に T を受け入れるメソッドが使用できなくなる
    // source.add(source.get(0)) // コンパイルエラー
                                 // (Out-projected type 'MutableList<out T>' prohibits
                                 //   the use of 'public abstract fun add(element: E): 以下略
}

fun main(args: Array<String>) {
    val ints = mutableListOf(1, 2, 3)   // MutableList<Int>
    val anyItems = mutableListOf<Any>() // MutableList<Any> (<T> の型 = Any)
    copyData(ints, anyItems)
    println(anyItems) // [1, 2, 3]
}

データコピー関数 の 例 (in 投影型 パラメータ)

-> Kotlin in Action - Chapter 9 の サンプルソース(5_3_CopyDataIn.kt) から 引用

//  ↓ <T> 1つの型パラメータ               反変 <in T>
fun <T> copyData(source: MutableList<T>, //
                 destination: MutableList<in T>) {
    for (item in source) {
        destination.add(item)
    }
    // 追記
    // ※ in 指定 -> 読み取り できるが 戻り値 は Any? 型 [赤べこ本 第11章 list 11.13 (p164) の解説参照]
    val a: Any? = destination.get(0)
}

fun main(args: Array<String>) {
    val ints = mutableListOf(1, 2, 3)   // MutableList<Int> (<T> の型 = Int)
    val anyItems = mutableListOf<Any>() // MutableList<Any>
    copyData(ints, anyItems)
    println(anyItems) // [1, 2, 3]
}

データコピー関数 の 例 (read only collection)

-> Kotlin in Action - Chapter 6 の サンプルソース から 引用

//                           ↓ 共変: Collection<out E> 宣言場所変位指定( Declaration-site variance )
fun <T> copyElements(source: Collection<T>,
                     target: MutableCollection<T>) {
    for (item in source) {
        target.add(item)
    }
}

fun main(args: Array<String>) {
    val source: Collection<Int> = arrayListOf(3, 5, 7)  // Collection<Int>        (<T> の型 = Int)
    val target: MutableCollection<Int> = arrayListOf(1) // MutableCollection<Int> (<T> の型 = Int)
                                                        //   ※ Int -> Number や Any に変更して,
                                                        //      <T> の型 = Number or Any になっても, コンパイル通る
    copyElements(source, target)
    println(target) // [1, 3, 5, 7]

    // 追記
    val source2: Collection<Int> = arrayListOf(3, 5, 7)       // Collection<Int>
    val target2: MutableCollection<Number> = arrayListOf(1.9) // MutableCollection<Number> (<T> の型 = Number)
    copyElements(source2, target2)
    println(target2) // [1.9, 3, 5, 7]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.