- Kotlinスタートブック読書勉強会 #9(最終回) で LT した内容
- 第12章 の 写経
reference や Kotlinイン・アクション や 他のサイトからの 引用 が中心
または 赤べこ本 第13章 以降 に掲載 されていて, 12章 のタイミングでついでに習った方が良さそうな内容
- Oct 2017 の コミット で 中の実装が少し変更された
contract { ... }
が追加されている
- 以下の定義は 上記コミット前の実装内容
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
- 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章 補足 参照
-> @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
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 (戻り値が 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
とも同じ振る舞いに
-> 「 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
は (拡張関数 ではない)普通の関数 も定義されている
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
public inline fun <T, R> T.run(block: T.() -> R): R = block()
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
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` だけ 普通の関数(拡張関数ではない) も存在する
}
}
-> 「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
}
let
やrun
でも同じ事が可能. (この使い方は 一般的ではない)- 引数で渡す ラムダ の 最後の式 を レシーバ に する
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
標準ライブラリ関数 の定義- 引数: レシーバ付きラムダ
- レシーバ は
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
}
-> @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 で代用
でいいのでは?
-> 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]
- 不変オブジェクト(
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
-> 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
-> @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
}
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 のハンズオン で
- Lesson5Activity.java (
if(● != null) {...} else {...}
の構成) を
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"
}
}
}
- ↑ Lesson5Activity.kt を 解答例 としていました。
-> Swift constructs for Kotlin から 引用
// swift
guard let unwrapped = wrapped else {
return
}
// kotlin
val unwrapped = wrapped ?: return
-> 鹿野壮@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)
}
-> 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
override fun toString(): String = run {
"Left(${value})" // lambda 内なので return 省略
}
-> サイバーエージェント・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.
パラメータ付きネストされたラムダでは、パラメータは常に明示的に宣言する必要があります。
おまけ
"変位指定で反変の使い所が分からないので何が嬉しいか誰か教えて欲しい"
という話があったようなので
Kotlinイン・アクション から 反変(contravariant) の例を引用
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 haveT
as an argument but cannot returnT
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) {...}
-> 反変のジェネリック型パラメーターを持つジェネリック インターフェイス 参照
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));
}
}
-> 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]
}
-> 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]
}
-> 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]
}
-> 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]
}