この記事は 第2のドワンゴ Advent Calendar 2016 の19日目の記事です。
皆さんこんにちは。ドワンゴ新卒2年目のdogwood008と申します。以前途中まで翻訳した(Kotlinの公式リファレンスを日本語化してみた[前編])のですが、"Other" やその他メインでない部分を除いた残りを続きとして翻訳しました。
基本的には上記ページの内容と、本ページの内容は同じです。個別のページで見たい人用と、一覧で見たい人用です。ただし、上記のリファレンスでは、マウスカーソルをあてると原文が表示されるようになっております。翻訳に誤りがあればお教えいただけると幸いです(PRいただけるともっとうれしいです)。
ちなみに、前編で翻訳したときの文章は、全て書き直してあります。
データクラス
何もしない、データを保持するためだけのクラスを作成することはよくあります。そのようなクラスでは、いくつかの標準機能は、データから機械的に推論できます。Kotlinでは、これは データクラス と呼ばれ、 data
としてマークされています。
data class User(val name: String, val age: Int)
プライマリコンストラクタで宣言されたすべてのプロパティから、コンパイラは自動的に次のメンバを推論します:
-
equals()
/hashCode()
のペア、 -
"User(name=John, age=42)"
形式のtoString()
、 - 宣言した順番でプロパティに対応する
componentN()
関数、 -
copy()
関数(下記参照)。
これらの機能のいずれかが明示的にクラス本体に定義されているか、基本型から継承されている場合は、生成されません。
生成されたコードの一貫性と意味のある動作を保証するために、データクラスは、次の要件を満たさなければなりません:
- プライマリコンストラクタは、少なくとも1つのパラメータを持っている必要があります。
- すべてのプライマリコンストラクタのパラメータは、
val
またはvar
としてマークする必要があります。 - データクラスは、 abstract, open, sealed または inner にすることはできません。
- データクラスは他のクラスを拡張しない場合があります(ただし、インターフェイスを実装することはできます)。
JVM上で、生成されたクラスがパラメータなしのコンストラクタを持つ必要がある場合は、すべてのプロパティのデフォルト値を指定する必要があります(コンストラクタを参照してください)。
data class User(val name: String = "", val age: Int = 0)
コピー
プロパティの いくつか を変更し、残りをそのままにしてオブジェクトをコピーする、ということが必要になるのはよくあることです。これが copy()
関数が作成される理由です。次のような User
クラスの場合、その実装は次のようになります。
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
これは次のように書くことができます:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
データクラスと分解宣言 (Destructuring Declarations)
データクラスのために生成した コンポーネント関数 は、分解宣言内で使用できます。
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // "Jane, 35 years of age" を出力する
標準データクラス
標準ライブラリは、 Pair
と Triple
を提供します。プロパティのために意味のある名前を提供することにより、コードを読みやすくするため、というのが理由です。ほとんどのケースでは、名前付きデータクラスは、設計上のより良い選択ですが。
ジェネリクス
Javaと同じように、Kotlinのクラスは型パラメータを持っている場合があります。
class Box<T>(t: T) {
var value = t
}
一般的に、このようなクラスのインスタンスを作成するために、我々は、型引数を提供する必要があります。
val box: Box<Int> = Box<Int>(1)
しかし、パラメータを推測することができる場合には、(例えば、コンストラクタの引数から、または何らかの他の手段によって)、「1」は、型引数を省略することができます:
val box = Box(1) // 1 は Int型をもつため、ここでは Box<Int> について話しているとコンパイラはわかる
分散
Javaの型システムの最もトリッキーな部分の一つは、ワイルドカードの種類(JavaのジェネリックのFAQを参照してください)です。そして、Kotlinは、いずれも持っていません。その代わり、2つの別のものがあります:宣言箇所の分散と型プロジェクションです。
まずは、Javaがこれらの神秘的なワイルドカードを必要とする理由について考えてみましょう。この問題はEffective Javaの項目28「APIの柔軟性を高めるためのバインドされたワイルドカードの使用」で説明されています。まず、Javaでジェネリック型は不変です。これは、 List<String>
は List<Object>
のサブタイプ ではない ことを意味します。なぜそうなのか?もしリストが 不変 でなかった場合は、次のコードはコンパイルされ、実行時に例外を発生させていたので、それは、Javaの配列より良いものではなかったでしょう。
// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! 今後の問題の原因はここにあります。 Javaはこれを禁止しています!
objs.add(1); // Integer を Strings のリストへ入れる
String s = strs.get(0); // !!! ClassCastException: Integer を String へキャストできない
つまり、Javaの実行時の安全性を保証するためにそのようなことを禁止しているのです。しかし、これはいくつかの意味があります。例えば、 Collection
インタフェースからの addAll()
メソッドを考えます。このメソッドのシグネチャは何でしょうか?直感的に、我々はそれをこのように置くと思います。
// Java
interface Collection<E> ... {
void addAll(Collection<E> items);
}
しかし、その後、次のような簡単なこと(完全に安全である)を行うことができなくなります。
// Java
void copyAll(Collection<Object> to, Collection<String> from) {
to.addAll(from); // !!! addAllのネイティブの宣言ではコンパイルできません:
// Collection<String> は Collection <Object> のサブタイプではありません
}
(Javaでは、この教訓に堅い方法を学びました。 Effective Javaの項目25「 配列よりリストを好む 」を参照してください。)
これが、 addAll()
の実際のシグネチャが以下の通りになる理由です:
// Java
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}
ワイルドカード型引数 ? extends T
は、このメソッドが受け入れるコレクションは T
のサブタイプ のオブジェクトであって、 T
自身ではないことを示します。
私たちが安全に T
の項目(このコレクションの要素は T
のサブクラスのインスタンスです)を 読み取る ことができても、未知の T
のサブタイプに対して、どのオブジェクトが応じるかわからないため、 書き込みができない 理由はこれです。
この制限と引き換えに、私たちは望んだ動作を得ます: Collection<String>
は Collection<? extends Object>
のサブタイプ である ということ。
「賢い言葉」で言い換えると、 拡張する バインド( 上昇 のバインド)のワイルドカードは 型共変 になります。
このトリックがなぜ働くのかを理解するための鍵は、かなりシンプルです:コレクションからアイテムを 取り出す ことだけできるのならば、String
のコレクションを使用して、 Object
で読み出せば良いのです。
反対に、コレクションにアイテムを 入れる ことだけできるのならば、 Object
のコレクションを使用し、 String
を入れても良いのです。 Javaでは List<Object>
の スーパータイプ である、 List<? super String>
を使用します。
後者は 反変性 と呼ばれ、 String を List<? super String>
の引数としたメソッドを呼ぶことのみができます(例えば、 add(String)
や set(int, String)
を呼ぶことができます)。ただし、List<T>
で T
を返す何かを呼んだとき、得るのは String
ではなく Object
ですが。
ジョシュア・ブロック (Joshua Bloch) はこれらのオブジェクトを 「 プロデューサ(生産者) からのみ 読み込み 、コンシューマ(消費者) にのみ 書き込む 」と呼びました。彼の勧めによると、 「最大の柔軟性を得るために、プロデューサやコンシューマを表す入力パラメータにワイルドカードタイプを使用する」 。 次の記憶術 (mnemonic) も提案しています。
PECSはProducer-Extends, Consumer-Super を意味します。
注 :プロデューサオブジェクトを使用する場合(たとえば、 List<? extends Foo>
)、このオブジェクト上の add()
や set()
を呼び出すことができません。しかし、このオブジェクトは イミュータブル(不変) であるというわけでもありません。例えば、 clear()
は全くパラメータを取らないため、リストからすべての項目を削除するために clear()
を呼び出しても構いません。ワイルドカード(または分散の他の型)によって唯一保証されるのは 型の安全性 です。不変性は全く別の話です。
宣言箇所分散
ジェネリックインターフェイスの Source<T>
があると仮定します。また、パラメータとして T
をとるメソッドを持たず、 T
を返すメソッドのみを持つとします。
// Java
interface Source<T> {
T nextT();
}
それは Source<Object>
型の変数(呼び出せるコンシューマメソッドがない)内で Source<String>
のインスタンスへの参照を保持するのに完全に安全です。 -- しかし、Javaはこれを知っているし、まだそれを禁止していません:
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!! Java では許可されていない
// ...
}
これを修正するために、Source<? extends Object>
型のオブジェクトを宣言する必要があります。全ての同メソッドを前のような変数で呼ぶことができるので、順序に意味はなく、より複雑な型で追加することに価値はありません。しかし、コンパイラはそれを知りません。
Kotlinでは、コンパイラにこの種の問題を説明する方法があります。これは、 宣言箇所分散 と呼ばれています:ソースの 型パラメータ T
を Source<T>
のメンバからのみ 返し (プロデュースする)、消費されることがないということを確認するために、アノテーションを付けることができます。これを行うために、我々は out 修飾子を提供します。
abstract class Source<out T> {
abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // これは OK 、なぜなら T はoutパラメータのため
// ...
}
一般的なルールは次のとおりです。クラス C
の型パラメータ T
が、 out として宣言されているとき、 C
のメンバの中で out の位置でのみそれが起きることがあります。 しかし、 C<Base>
を返すときは C<Derived>
のスーパータイプに安全になり得ます。
「賢い言葉」でいうと、クラス C
は、パラメータ T
に 共変 である、または T
が 共変 の型パラメータであるとなります。 C
は T
の プロデューサ であり、 T
の コンシューマ ではない、と考えることができます。
out 修飾子は、 分散アノテーション と呼ばれ、それは型パラメータの宣言箇所で提供されているので、我々は 宣言箇所分散 について話しています。
これは、ワイルドカードが使用時に型を共変にする、Javaの 使用箇所分散 とは対照的です。
out に加えて、Kotlinは in という補完的な分散アノテーションを提供します。これは、型パラメータの 反変 を行います。消費されるのみであり、決してプロデュース(生産)されません。
反変クラスの良い例は Comparable
です:
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 は Number のサブタイプである Double 型をもつ
// それゆえ、 x を Comparable<Double> 型の変数へ代入できる
val y: Comparable<Double> = x // OK!
}
(C#で何度も成功しているように)in と out は自己説明的であるゆえに、以前述べた記憶術(ニーモニック)は本当は不要で、より高次の目的のために言い換えることができます:
実存的言い換え:コンシューマ(消費者)は in、プロデューサ(生産者)は out ! :-)
タイププロジェクション(型投影)
利用箇所の分散:タイププロジェクション
out 型パラメータTを宣言し、使用箇所のサブタイプとの問題がないことは非常に便利です。
そう、それは問題のクラスが実際に T
のインスタンスのみを返すよう制限 できる ときですが、できないのはどんなときでしょう。この良い例は、Arrayです。
class Array<T>(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
このクラスは T
の共変または反変のいずれかにもなることはできません。そして、これは特定の不撓(ふとう)性(曲げられない特性)を課しています。次の関数を考えてみます:
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
この関数は、ある配列から別の配列へ、アイテムをコピーすることになっています。それでは、実際にそれを適用してみましょう:
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3)
copy(ints, any) // エラー: (Array<Any>, Array<Any>) が期待されている
ここで同じようなよくある問題に遭遇します: Array<T>
は T
において 不変 であり、ゆえに Array<Int>
も Array<Any>
も他のサブタイプではありません。
どうして?コピーが何か悪いこと(すなわち from
への文字列の 書き込み や出力の試行)をやっている 可能性がある ためです。また Int
の配列を実際に渡されると、ClassCastException
が時々後で投げられるでしょう。
ここで、唯一の保証したいことは、copy()
がいかなる悪さもしないということです。 copy()
が from
に書き込むことを禁止したく、それを行うことはできます:
fun copy(from: Array<out Any>, to: Array<Any>) {
// ...
}
ここで起こったことは、 タイププロジェクション(型投影) と呼ばれています。 from
は単純に配列なのではなく、制限された( 投影された )ものであるということです。型パラメータ T
を返すこれらのメソッドはこのケースでのみ呼び出すことができます。
つまり、 get()
を呼ぶことのみができるということです。これが、 使用箇所分散 のための我々のアプローチであり、Javaの Array<? extends Object>
に対応しますが、少しだけ簡単な方法です。
このように in でタイププロジェクション(型投影)を使用できます:
fun fill(dest: Array<in String>, value: String) {
// ...
}
Array<in String>
は Javaの Array<? super String>
に対応します。すなわち、 CharSequence
の配列や Object
の配列を fill()
関数へ渡すことができます。
スタープロジェクション (star-projections)
型引数について何も知らないが、それでも安全な方法で使用したいと、時には言いたくなることもあるでしょう。ここでの安全な方法とは、ジェネリック型のそのようなプロジェクションを定義することです。ジェネリック型を具体的にインスタンス化すると、全てそのプロジェクションのサブタイプになります。
Kotlinはこのために、いわゆる スタープロジェクション (star-projection) 構文を提供します:
-
Foo <out T>
の場合、T
は上限TUpper
を持つ共変の型のパラメータであり、Foo <*>
はFoo<out TUpper>
と等価です。これは、T
が不明な場合に、安全にはFoo <*>
からTUpper
の値を読み取ることができることを意味します。 -
T
が反変の型パラメータであるFoo<in T>
については、Foo<*>
はFoo <in Nothing>
と等価です。それはT
は不明である場合は安全な方法でFoo <*>
に書き込むことができる方法がないことを意味します。 -
Foo <T>
の場合、T
は上限TUpper
を持つ不変の型パラメータであり、値を読み込むためのFoo<out TUpper>
および値を書き込むためのFoo<in Nothing>
とFoo <*>
は同等です。
ジェネリック型がいくつかの型パラメータをもつ場合、それらは独立してプロジェクション(投影)することができます。
例えば、型が interface Function<in T, out U>
として宣言されている場合ならが、次のようなスタープロジェクションを想像することができます:
-
Function<*, String>
はFunction<in Nothing, String>
を意味します -
Function<Int, *>
はFunction<Int, out Any?>
を意味します -
Function<*, *>
はFunction<in Nothing, out Any?>
を意味します
注意 :スタープロジェクションは非常にJavaの raw タイプににていますが、安全です。
ジェネリック関数
型パラメータを持つことができるのはクラスだけではありません。関数も同じです。
型パラメータは、関数名の前に置かれます。
fun <T> singletonList(item: T): List<T> {
// ...
}
fun <T> T.basicToString() : String { // 拡張関数
// ...
}
ジェネリック関数を呼び出すには、関数名の 後に 呼び出し箇所で型引数を指定します。
val l = singletonList<Int>(1)
ジェネリックの制約
与えられる型パラメータに置換することができるすべての許容される型の集合は、 ジェネリックの制約 によって制限されてもかまいません。
上限 (Upper bounds)
制約の最も一般的なタイプは、Javaの extends キーワードに対応する 上限 です。
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
コロンの後に指定されたタイプが 上限 です。 Comparable<T>
のサブタイプは T
の代わりに使用することができます。例えば:
sort(listOf(1, 2, 3)) // OK. Int は Comparable<Int> のサブタイプです
sort(listOf(HashMap<Int, String>())) // エラー: HashMap<Int, String> は Comparable<HashMap<Int, String>> のサブタイプではない
デフォルトの上限(いずれも指定されていない場合)は Any?
です。唯一の上限を、角括弧内で指定することができます。
同じ型パラメータに複数の上限を必要とする場合、それぞれ独立した where 句が必要になります:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable,
T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}
ネストされたクラス
クラスは他のクラスの中に入れ子にすることができます:
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
内部クラス
クラスは、外部クラスのメンバーにアクセスできるように inner{:.keyword} としてマークされてもかまいません。内部クラスは、外部クラスのオブジェクトへの参照をもちます:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
内部クラス内での this{:.keyword} の曖昧さ回避について学ぶために、修飾された this{:.keyword} 式を参照してください。
無名内部クラス
無名内部クラスのインスタンスはオブジェクト式を使用して作成されます:
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
オブジェクトが機能的なJavaインタフェースのインスタンス(つまり、あるJavaインタフェースがひとつの抽象メソッドとひもづく)である場合は、インタフェースの型が前に付いたラムダ式を使用してオブジェクトを作成できます。
val listener = ActionListener { println("clicked") }
列挙型クラス (Enum Classes)
列挙型クラスの最も基本的な使用法は、型安全な列挙型を実装しています:
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
各列挙型の定数はオブジェクトです。列挙型定数はカンマで区切られます。
初期化
各列挙型は列挙型クラスのインスタンスなので、初期化することができます:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
無名クラス
列挙型定数は、独自の無名クラスを宣言することができます:
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
それらに対応するメソッド、オーバーライドした基本メソッドも同様に併せて宣言できます。列挙型クラスでメンバが定義されている場合は、Javaの場合と同様に、メンバ定義から 列挙型 定数定義をセミコロンで区切る必要があります。
列挙型定数を使用した作業
ちょうどJavaと同じように、Kotlinの列挙型クラスは、定義された列挙型定数を羅列し、その名前で列挙型定数を得ることを可能にする合成メソッドを持っています。(列挙型クラスの名前を EnumClass
と仮定して)これらのメソッドのシグネチャは次のとおりです。
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
指定された名前が、クラスで定義されている列挙型定数のいずれとも一致しない場合、valueOf()
メソッドは IllegalArgumentException
をスローします。
すべての列挙型定数は、列挙型クラス宣言でその名前と位置を取得するためのプロパティがあります。
val name: String
val ordinal: Int
列挙型定数は列挙型クラスで定義された順序である自然な順序で、Comparable インタフェースも実装します。
オブジェクト式と宣言
時にはあるクラスをわずかに修正しただけのオブジェクトをそれのための新しいサブクラスを明示的に宣言せずに、作成する必要があります。Javaでは 無名内部クラス でこの事例を処理します。
Kotlinではたった オブジェクト式 と オブジェクトの宣言 だけでこの概念を一般化します。
オブジェクト式
いくつかの型(1つでも複数でも)から継承する無名クラスのオブジェクトを作成するには、このようにします:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
スーパータイプがコンストラクタを持っている場合は、適切なコンストラクタのパラメータが渡されなければなりません。多くのスーパータイプは、コロンの後にコンマ区切りのリストとして指定することができます:
open class A(x: Int) {
public open val y: Int = x
}
interface B {...}
val ab: A = object : A(1), B {
override val y = 15
}
万が一、自明でないスーパータイプの「オブジェクトのみ」が必要な場合は、単純に次のように言うことができます:
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
ただ、Javaの無名内部クラスのように、内包するスコープからオブジェクト式のコードが変数にアクセスすることができます。(Javaのとは違って、これは final の変数に限定されるものではありません。)
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ...
}
オブジェクトの宣言
シングルトンは非常に便利なパターンであり、Kotlin(Scalaの後です)は、シングルトンを容易に宣言で切るようにしました:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
- これは、オブジェクトの宣言と呼ばれ、それは常に object キーワードの後に名前を持ちます。ちょうど変数宣言と同じように、オブジェクトの宣言は式ではなく、代入文の右側に使用することはできません。
オブジェクトを参照するために、その名前を直接使用します。
DataProviderManager.registerDataProvider(...)
このようなオブジェクトは、スーパータイプを持つことができます:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
}
注:オブジェクト宣言はローカルにすることはできません(つまり、関数内で直接ネストしてください)。ただし、他のオブジェクト宣言または非内部クラスにネストすることもできます。
コンパニオンオブジェクト (Companion Objects)
クラス内のオブジェクトの宣言は、 companion{: .keyword } キーワードでマークすることができます。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
コンパニオンオブジェクトのメンバーは修飾子として単にクラス名を使用して呼び出すことができます:
val instance = MyClass.create()
コンパニオンオブジェクトの名前を省略することができます。この場合、 Companion
という名前が使用されます。
class MyClass {
companion object {
}
}
val x = MyClass.Companion
コンパニオンオブジェクトのメンバは、他の言語のスタティックメンバのように見えますが、実行時にはそれらはまだ実際のオブジェクトのインスタンスメンバであり、たとえばインターフェイスを実装できます:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
しかしながら、JVM上では、 @JvmStatic
アノテーションを使用すると、コンパニオンオブジェクトのメンバを実際の静的メソッドやフィールドとして生成することができます。詳細については、Javaの相互運用性のセクションを参照してください。
オブジェクト式と宣言の間の意味の違い
オブジェクト式とオブジェクトの宣言の間には、ある重要な意味上の違いがあります:
- オブジェクト式は すぐに 実行され(初期化され)、そこで使用されます
- オブジェクト宣言は、初回アクセス時に 遅延して 初期化されます
- コンパニオンオブジェクトは、対応するクラスが読み込まれた(解決)されたときに初期化され、これは Java の静的初期化子のセマンティクスに一致します
委譲 (Delegation)
クラスの委譲
Delegationパターンは、実装継承の良い代替手段であることが証明されており、Kotlinはネイティブでそれをサポートし、かつ定型コードを必要としません。
Derivced
クラスは、 Base
インターフェイスから継承することができ、指定されたオブジェクトへの public メソッドのすべてを委譲することができます。
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 出力:10
}
Derived
のスーパータイプのリスト中の by{:.keyword} 節は、 b
が Derived
のオブジェクトに内部的に格納されることを示し、コンパイラは b
に取り次ぐ Base
のすべてのメソッドを生成します。
委譲プロパティ (Delegated Properties)
必要なときに手動で実装することはできますが、一度実装してライブラリに入っていると非常にうれしいといった、ある種のよくある一般的なのプロパティはあります。例としては、
- 遅延プロパティ (lazy properties) :値は最初のアクセス時に初めて計算されます
- オブザーバブルプロパティ (observable properties) :リスナがこのプロパティの変更に関する通知を受け取ります
- フィールドとは分かれていない、map内での格納プロパティ (storing properties)
これら(およびその他)のケースをカバーするために、Kotlinは、 委譲プロパティ (delegated properties) をサポートしています。
class Example {
var p: String by Delegate()
}
構文は次のとおりです val/var <property name>: <Type> by <expression>
get()
(と set()
)はその getValue()
および setValue()
メソッドに委譲されるプロパティに対応するため、 by
の後に続く式は、 委譲 (delegate) です。プロパティの委譲には、任意のインターフェイスを実装する必要はありませんが、 getValue()
関数(そして setValue()
--- var{:.keyword}用に)を提供する必要があります。例えば:
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
Delegate インスタンスのデリゲートである p
を読み込むとき、 Delegate
のgetValue()
関数が呼び出されています。そのため、その最初のパラメータは、 p
を読み取る先のオブジェクトであり、2番目のパラメータは、 p
自体の説明を保持しています(例えば、あなたがその名前を得ることができます)。例えば:
val e = Example()
println(e.p)
これは次の通り出力します
Example@33a17727, thank you for delegating ‘p’ to me!
p
に代入するのと同様に、setValue()
関数が呼び出されます。最初の2つのパラメータは同じであり、3つ目は、割り当てられた値を保持します。
e.p = "NEW"
これは次の通り出力します
NEW has been assigned to ‘p’ in Example@33a17727.
プロパティデリゲートの要件
ここでは、オブジェクトを委譲するための要件をまとめます。
読み取り専用プロパティ(すなわち val{:.keyword})のために、デリゲートは、次のパラメータを取る getValue
という名前の関数を提供する必要があります。
- レシーバ --- プロパティ所有者 のものと同じかスーパータイプでなければなりません(拡張プロパティー --- 拡張されるタイプの場合)。
- メタデータ --- 型
KProperty <*>
またはそのスーパータイプでなければなりません。
この関数は、プロパティ(またはそのサブタイプ)と同じ型を返さなければなりません。
変更可能な プロパティ ( var{:.keyword} ) の場合、デリゲートは、さらに次のパラメータを取り setValue
という名前の関数を 追加で 提供する必要があります。
- レシーバ ---
getValue()
と同じ - メタデータ ---
getValue()
と同じ - 新しい値 --- プロパティまたはそのスーパータイプと同じタイプでなければなりません。
getValue()
および/または setValue()
関数は、いずれかの委譲クラスや拡張機能のメンバ関数として提供することができます。これらの機能を提供していないオブジェクトにプロパティを委譲する必要がある場合、後者が便利です。関数の両方を operator
キーワードでマークする必要があります。
Kotlin標準ライブラリでは、いくつかの有用なデリゲートのファクトリメソッドを提供します。
遅延 (lazy)
lazy()
はラムダをとり、遅延プロパティを実装するためのデリゲートとして機能する Lazy<T>
のインスタンスを返す関数です。get()
の最初の呼び出しは lazy()
に渡されたラムダを実行し、結果を保持します。 それ以降、get()
を呼び出すと、単に記憶された結果が返されます。
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
デフォルトでは、遅延特性の評価が 同期されます 。値は1つのスレッドで計算され、すべてのスレッドで同じ値が表示されます。もし初期化デリゲートの同期が必要ではない場合は、 複数のスレッドが同時に初期化を実行できるように LazyThreadSafetyMode.PUBLICATION
を lazy()
関数のパラメータとして渡します。初期化が常に単一のスレッドで起こると確信しているなら、任意のスレッドの安全性の保証および関連するオーバーヘッドが発生しない LazyThreadSafetyMode.NONE
モードを使用することができます。
オブザーバブル (Observable)
Delegates.observable()
は、2つの引数を取ります。初期値と修正のためのハンドラです。ハンドラは(割り当てが行われた 後 に)プロパティに割り当てるたびに呼び出されます。それには3つのパラメータがあり、割り当てられているプロパティ、古い値、そして新しい値です:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
この例の出力:
<no name> -> first
first -> second
もし代入を傍受し、それに対し 「拒否権」を発動できるようにしたい場合は、observable() の代わりに vetoable()
を使います。 vetoable
に渡されたハンドラは、新しいプロパティ値の割り当てが行われる 前 に呼び出されます。
Map 中の格納プロパティ(ストアリングプロパティ) (Storing Properties in a Map)
一般的な使用例のひとつとして、map 内のプロパティの値を記憶することが挙げられます。これはJSONをパースしたり、他の「動的」なことをやるようなアプリケーションで頻繁に起こっています。この事例では、委譲プロパティのデリゲートとして map のインスタンス自体を使用することができます。
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
この例では、コンストラクタは、 map を取ります。
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
委譲プロパティは、このマップから(文字列 --- この場合プロパティの名前 --- のキーを使って)値を取ります:
println(user.name) // 出力:"John Doe"
println(user.age) // 出力:25
読み取り専用 Map
の代わりに MutableMap
を使用すると、これは var{:.keyword} のプロパティに対しても動作します:
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
関数
関数の宣言
Kotlinの関数は fun{: .keyword } キーワードを使用して宣言されています。
fun double(x: Int): Int {
}
関数の使い方
関数の呼び出しは、伝統的なアプローチを採用しています。
val result = double(2)
メンバ関数の呼び出しは、ドット表記を使用します。
Sample().foo() // Sampleクラスのインスタンスを作ってfooを呼ぶ
中置記法
次のようなとき、中置表記法 (infix notations) を使用して関数を呼び出すことができます:
- メンバ関数や拡張関数であるとき
- 単一のパラメータを持っているとき
-
infix
キーワードでマークされているとき
// Intにエクステンションを定義
infix fun Int.shl(x: Int): Int {
...
}
// 拡張関数を infix ノーテーションを使用して呼ぶ
1 shl 2
// これは次と同じ
1.shl(2)
パラメーター
関数のパラメータはパスカル記法、すなわち 名前: タイプ を使用して定義されています。パラメータはカンマを使用して分離されます。各パラメータは、明示的に入力する必要があります。
fun powerOf(number: Int, exponent: Int) {
...
}
デフォルトの引数
関数パラメータは、対応する引数が省略されているときに使用されるデフォルト値をもつことができます。これにより、他言語に比べてオーバーロード数を減らすことができます。
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {
...
}
デフォルト値は値と共に記述した後に = を使用して定義されます。
オーバーライドメソッドは、常にベースメソッドと同じデフォルトのパラメータ値を使用します。デフォルトのパラメータ値を持つメソッドをオーバーライドする場合は、デフォルトのパラメータ値は、シグネチャから省略されている必要があります。
open class A {
open fun foo(i: Int = 10) { ... }
}
class B : A() {
override fun foo(i: Int) { ... } // デフォルト値は使用できない
}
名前付き引数
関数を呼び出すとき、関数のパラメータに名前を付けることができます。これは関数が沢山のパラメータやデフォルトのパラメータを持つ場合に非常に便利です。
次の関数を考えます:
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
私たちは、デフォルト引数を使用して、これを呼び出すことができます:
reformat(str)
しかし、デフォルト値が無い場合はそれの呼び出しは次のようになります:
reformat(str, true, true, false, '_')
名前付き引数で、コードをはるかに読みやすくすることができます:
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
すべての引数を必要としない場合:
reformat(str, wordSeparator = '_')
Javaバイトコードは常に関数パラメータの名を保存しないため、Java関数を呼び出すときに名前付き引数構文を使用できないことに注意してください。
Unit を返す関数
関数が任意の有用な値を返さない場合、その戻り値の型は Unit
です。 Unit
は、唯一の値 ( Unit
) だけを持つ型です。
この値は、明示的に return されなくてもかまいません:
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` または `return` は必須ではない
}
Unit
の戻り型の宣言も任意です。上記のコードは次と等価です:
fun printHello(name: String?) {
...
}
単一式関数
関数は、単一の式を返すと中括弧を省略することができ、本体は = 記号の後に指定されています:
fun double(x: Int): Int = x * 2
コンパイラによって戻り値の型を推論することができる時には、明示的な戻り値の型の宣言は任意です:
fun double(x: Int) = x * 2
明示的な戻り値の型
Unit
を返すことを意図していない限り、ブロック本体を持つ関数は、それが任意である場合には、常に明示的に戻り値の型を指定しなければなりません。Kotlinはブロック本体と関数の戻り値の型を推論することはありません。なぜならこのような機能は本体内に複雑な制御フローをもつことがあり、戻り値の型が読み手(時にはコンパイラ)に自明ではないからです。
可変長引数(可変引数, Varargs)
関数(通常は最後のひとつ)のパラメータは、 vararg
修飾子でマークされることがあります:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts は配列
result.add(t)
return result
}
関数に渡される引数を可変数にすることができます:
val list = asList(1, 2, 3)
関数の中では、 T
型の vararg
をつけたパラメータは T
の配列として見えます。すなわち、前述例での ts
変数は Array<out T>
型を持ちます。
唯一のパラメータが vararg
としてマークされることがあります。 vararg
パラメータが変数リストの最後のひとつでない場合には、名前付き引数の構文を使用して、またはパラメータが関数型をもっている場合、括弧の外でラムダを渡すことによって、リストにおける次のパラメータの値を渡すことができます。
vararg
をもつ関数を呼び出すとき、例えば asList(1, 2, 3)
のように、一つずつ引数を渡すことができます。または、すでに配列を持っており、関数にその内容を渡したい場合は、( * を配列名の接頭辞にする) spread 演算子を使用します:
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
関数のスコープ
Kotlinでは、関数はファイルのトップレベルで宣言することができます。これは、関数を保持するためのクラスを作成する必要がないことを意味します。JavaやC#, Scalaなどの言語と同じように。トップレベルの関数に加えて、Kotlinの関数はメンバ関数や拡張機能として、ローカルに宣言することもできます。
ローカル関数
Kotlinはローカル関数、すなわち、ある関数内の別の関数をサポートしています。
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
ローカル関数は、外側の関数(すなわちクロージャ)のローカル変数にアクセスすることができます。これにより、上記の場合には、 visited をローカル変数にすることができます。
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
メンバ関数
メンバ関数は、クラスやオブジェクトの内部で定義される関数です。
class Sample() {
fun foo() { print("Foo") }
}
メンバ関数は、ドット表記と呼ばれています。
Sample().foo() // Sampleクラスのインスタンスを作り、 foo を呼ぶ
クラスおよびオーバーライドするメンバの詳細については クラスと継承 を参照してください。
ジェネリック関数
関数は、関数名の前に山括弧(訳注:<>のことです)を使用して、ジェネリックパラメータを持つことができます。
fun <T> singletonList(item: T): List<T> {
// ...
}
ジェネリック関数の詳細については、ジェネリクス を参照してください。
インライン関数
インライン関数は、 ここ で説明されています。
拡張関数
拡張機能は、 拡張関数 で説明されています。
高階関数とラムダ
高階関数とラムダは、 高階関数とラムダ で説明されています。
末尾再帰関数
Kotlinは末尾再帰として知られている関数型プログラミングのスタイルをサポートしています。これは通常ループを使用して書かれるいくつかのアルゴリズムを代わりに再帰で、しかし、普通の再帰と違ってスタックオーバーフローのリスクがないように書くことです。ある関数が tailrec
修飾子でマークされ、必要な形式を満たしている場合、コンパイラは高速かつ効率的なループベースのバージョンを残して、再帰を最適化します。
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
このコードは、数学定数であるコサインの不動点(固定点, fixpoint)を算出します。それは Math.cos を 1.0 から始めて結果に0.7390851332151607の結果を得、それ以上変化しなくなるまで単に繰り返し呼び出します。その結果生成されたコードは、このより伝統的なスタイルに相当します。
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return y
x = y
}
}
tailrec
修飾子の対象となるためには、関数は実行する最後の操作として自身を呼び出す必要があります。再帰呼び出しの後に多くのコードがあるときは、末尾再帰を使用することはできません。 try / catch / finally ブロック内で使用することもできません。現在、末尾再帰はJVMのバックエンドでのみサポートされています。
高階関数とラムダ
高階関数
高階関数はパラメータとして関数を取るか、関数を返す関数です。このような機能の良い例が lock()
のような関数です。これは lock オブジェクトと関数を受け取り、 lock をかけ、関数を実行して lock を解放します。
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
それでは、上記のコードを見てみましょう。 body
は関数型: () -> T
を持ちます。つまり、パラメータを取らず、型 T
の値を返す関数と考えられます。 body
は lock
によって保護されながら、 try{: .keyword } ブロック内で呼び出され、その結果は lock()
関数によって返されます。
lock()
を呼び出したい場合は、引数として別の関数を渡すことができます(関数の参照を参照してください):
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
別手段として、多くの場合、より便利な方法はラムダ式を渡すことです:
val result = lock(lock, { sharedResource.operation() })
ラムダ式は以下でより詳細に説明しますが、このセクションを継続したいので、今は簡単な概要を見てみましょう:
- ラムダ式は、常に中括弧で囲まれています
- そのパラメータは、(もしあれば)
->
の前で宣言されます(パラメータの型を省略してもかまいません) - 本体が
->
に続きます(存在する場合)
Kotlinでは、関数の最後のパラメータが関数である場合、そのパラメータは括弧の外に指定することができるという慣習があります:
lock (lock) {
sharedResource.operation()
}
別の例で高階関数が map()
になっています:
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
この関数を次のように呼び出すことができます。
val doubled = ints.map { it -> it * 2 }
ラムダがその呼び出しに唯一の引数である場合、呼び出しの括弧を完全に省略することができることに注意してください。
it
: 単一パラメータの暗黙の名前
もう一つの有用な慣習は、関数リテラルがパラメータを1つだけ持つ場合、その宣言を( ->
と一緒に)省略してもよいということです。その場合、その名前は、 it になります。
ints.map { it * 2 }
これらの慣習により、 LINQスタイル のコードを書くことができます:
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
インライン関数
場合によっては、インライン関数の使用は高階関数のパフォーマンスの向上に有効です。
ラムダ式と無名関数
ラムダ式や無名関数は「関数リテラル」です。すなわち、その関数は宣言されたのではなく、表現としてすぐに渡されたということです。次の例を考えてみます:
max(strings, { a, b -> a.length < b.length })
関数 max
は高階関数です。すなわち2番目の引数として関数値をとります。この2番目の引数はそれ自体が関数である式、すなわち関数リテラルです。関数としては、次と等価です:
fun compare(a: String, b: String): Boolean = a.length < b.length
関数型
関数がパラメータとして別の関数を受け入れられるようにするために、そのパラメータの関数型を指定する必要があります。たとえば、次のように前述の関数 max
が定義されているとします:
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
パラメータ less
は (T, T) -> Boolean
型、すなわち2つの T
型のパラメータをとり、前者が後者より小さければ Boolean
: true を返す関数です。
本体の4行目では、 less
は関数として使用されています。つまり、型 T
の2つの引数を渡すことによってその関数は呼び出されました。
関数型は前述の通り、または各パラメータの意味をドキュメント化する場合は、名前付きパラメータを使用することがあります。
val compare: (x: T, y: T) -> Int = ...
ラムダ式の構文
ラムダ式、つまり関数型リテラルの完全な構文形式は、次のとおりです。
val sum = { x: Int, y: Int -> x + y }
ラムダ式は常に中括弧で囲まれ、完全な構文形式のパラメータ宣言はカッコ内にあり、型注釈を持つことができ、本体は ->
記号の後に置かれます。必須ではない注釈をすべて省略した場合、残っているものは次のようになります:
val sum: (Int, Int) -> Int = { x, y -> x + y }
ラムダ式がパラメータを1つだけ持っていることはよくあることです。もしKotlinが署名自体を理解することができれば、唯一のパラメータ宣言を省略することができ、暗黙のうちにそれを it
という名で宣言します。
ints.filter { it > 0 } // このリテラルは '(it: Int) -> Boolean' 型
関数が最後のパラメータとして別の関数を取る場合は、ラムダ式の引数は括弧で囲まれた引数リストの外に渡すことができることに注意してください。callSuffixのための文法を参照してください。
無名関数
上記のラムダ式の構文から一つ欠落しているのは、関数の戻り値の型を指定する機能です。ほとんどの場合は、戻り型を自動的に推論することができるので不要です。しかし、それを明示的に指定する必要がある場合、別の構文を使用することができます。_無名関数_です。
fun(x: Int, y: Int): Int = x + y
無名関数は、その名が省略されていることを除いて、通常の関数の宣言と非常のよく似ています。その本体は、式(上記のように)、またはブロックのいずれかになります:
fun(x: Int, y: Int): Int {
return x + y
}
パラメータおよび戻り型は、それらが文脈から推測することができ、パラメータの種類を省略することができる場合を除き、通常の関数と同じ方法で指定されます。
ints.filter(fun(item) = item > 0)
無名関数の戻り値の型推論は普通の関数のように動作します:戻り値の型は式本体と無名関数のために自動的に推論され、ブロック本体で無名関数のために明示的に指定され(または Unit
とされ)ます。
無名関数のパラメータは、常にかっこ内に渡されることに注意してください。括弧の外の関数を残すことができるように、速記構文はラムダ式に対してのみ機能します。
ラムダ式と無名関数の間のもう一つの違いは、非局所的なリターンの動作です。ラベルなしの return{: .keyword } 文は、常に fun{: .keyword } キーワードで宣言された関数から返されます。これは、ラムダ式の内側からの return{: .keyword } は囲んでいる関数から返される一方で、無名関数の内部 return{: .keyword } は無名関数自体から返されることを意味します。
クロージャ
ラムダ式や無名関数(ならびにローカル関数やオブジェクト式)は、その クロージャ 、すなわち、外側のスコープで宣言された変数にアクセスすることができます。Javaとは異なり、クロージャに取り込まれた変数を変更することができます。
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
レシーバ付き関数リテラル
Kotlinは、指定された レシーバ・オブジェクト と関数リテラルを呼び出す機能を提供します。リテラル関数の本体内では、任意の追加の修飾子なしでそのレシーバオブジェクトのメソッドを呼び出すことができます。これは、関数の本体内にあるレシーバオブジェクトのメンバにアクセスすることを可能にする拡張機能に似ています。それらの使用法の最も重要な例の一つは、型安全のGroovyスタイルのビルダーです。
このような関数リテラルの型は、レシーバを持つ関数型です:
sum : Int.(other: Int) -> Int
それはレシーバオブジェクトのメソッドであるかのように関数リテラルを呼び出すことができます。
1.sum(2)
無名関数の構文は、直接関数リテラルのレシーバの種類を指定することができます。レシーバを持つ関数型の変数を宣言し、後でそれを使用する必要がある場合に役立ちます。
val sum = fun Int.(other: Int): Int = this + other
レシーバ型は文脈から推測することができる場合、ラムダ式は、レシーバ関数リテラルとして使用することができます。
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // レシーバオブジェクトを生成
html.init() // そのレシーバオブジェクトをラムダに渡す
return html
}
html { // レシーバ付きラムダがここから始まる
body() // レシーバオブジェクトのメソッドを呼んでいる
}
インライン関数
高階関数を使用すると、特定のランタイムペナルティを課せられます。各関数はオブジェクトであり、それはクロージャ、すなわち、関数の本体でアクセスされるそれらの変数をキャプチャします。メモリ割り当て(関数オブジェクトとクラス用の両方)と仮想呼び出しは、実行時のオーバーヘッドを招きます。
しかし、多くの場合、オーバーヘッドのこの種のラムダ式をインライン化することによって解消することができると思われます。以下に示す関数は、このような状況の良い例です。すなわち、lock()
関数は、簡単に呼び出し箇所でインライン化することができました。次のケースを考えてみます。
lock(l) { foo() }
パラメータやコールの生成のために関数オブジェクトを作成する代わりに、コンパイラは次のコードを放出する可能性があります:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
それは我々が当初から欲しかったものではないですか?
コンパイラがこれをできるようにするために、 inline
修飾子で lock()
関数をマークする必要があります:
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ...
}
inline
修飾子は、関数自体やそれに渡されたラムダの両方に影響を与えます。それらのすべては、呼び出し箇所の中でインライン化されます。
インライン化では生成されるコードが大きくなる可能性がありますが、合理的な方法で(大きな関数をインライン化しないで)実行すると、特にループ内の 「メガモーフィック (megamorphic)」な呼び出し箇所でパフォーマンスが向上します。
noinline
インライン関数に渡されたラムダのうちのいくつかだけをインライン関数にしたい場合は、関数パラメータのいくつかに noinline
修飾子を付けることができます:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
Inlinableラムダは、インライン関数内でのみ呼び出すことができます。または、インライン展開可能な引数として渡すこともできますが、 noinline
は、好きなように操作できます。例えば、フィールドに保持したり、誰かに渡したり等。
インライン関数にインライナブル関数パラメータがなく、具体化型パラメータが指定されていない場合、コンパイラは警告を発します。このような関数のインライン展開は有益ではないためです(インライン展開が必要な場合は警告を抑制できます)。
非局所リターン
Kotlinでは、名前付き関数または無名関数を終了するには、通常、ラベル無し return
のみを使用することができます。これは、ラムダを終了するにはラベルを使用しなければならず、ラムダが自身を内包する関数からの return
を作ることができないため、ラムダ内での裸のリターンは禁止されていることを意味します。
fun foo() {
ordinaryFunction {
return // エラー: `foo` をここで return することはできない
}
}
しかし、ラムダがインライン化されるために渡された関数の場合は、リターンも同様にインライン化することができ、それが許可されています。
fun foo() {
inlineFunction {
return // OK: ラムダはインライン
}
}
(ラムダに位置するが、内包する関数から抜ける)このようなリターンは、 非局所リターン と呼ばれています。私たちは、インライン関数がしばしば内包するこのようなループの構造に慣れています。
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // hasZeros から return する
}
return false
}
インライン関数の中には、渡されたラムダを、関数本体から直接ではなく、ローカルオブジェクトやネストされた関数などの別の実行コンテキストからのパラメータとして呼び出すものがあります。このような場合には、非局所制御フローもラムダでは許可されません。それを示すために、ラムダパラメータを crossinline
修飾子でマークする必要があります
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
break
と continue
はインライン化されたラムダではまだ利用できませんが、我々はそれらをサポートすることを計画しています。
具体化型パラメータ (Reified type parameters)
時にはパラメータとして渡された型にアクセスする必要があります。
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p?.parent
}
@Suppress("UNCHECKED_CAST")
return p as T
}
ここでは、ツリーをたどってリフレクションを使用して、ノードに特定のタイプがあるかどうかを確認します。全く問題はないのですが、呼び出し箇所はそれほど美味しくなりません:
myTree.findParentOfType(MyTreeNodeType::class.java)
私たちが実際にしたいのはこの関数に型を渡すだけ、すなわち、このように呼び出すだけです:
myTree.findParentOfType<MyTreeNodeType>()
これを有効にするには、インライン関数が 具体化型パラメータ をサポートするので、私たちはこのように書くことができます:
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p?.parent
}
return p as T
}
私たちは reified
修飾子で型パラメータを修飾しました。これで、関数内でアクセス可能になり、これは通常のクラスと同じように機能します。関数はインライン化されているので、リフレクションは必要ありません。 !is
や as
のような通常の演算子が動作するようになります。また、前述したようなやりかたで呼び出すことができます:myTree.findParentOfType<MyTreeNodeType>()
リフレクションは多くの場合に必要とされないかもしれませんが、まだ具体化型パラメータでそれを使用することができます:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
通常の機能(インラインとしてマークされていない)は具体化パラメータをもつことはできません。実行時表現を持たない型(例えば、非reified型パラメータや Nothing
のような架空の型)は、reified 型のパラメータの引数として使用できません。
低レベルの説明については、仕様書を参照してください。
※権利関係について
リファレンスのオリジナルを作ったJetBrain社はContributing to Kotlinで、「あなたの言語に翻訳し、Webサイト上で閲覧可能にすることを歓迎する。」 ( You are welcome to translate the Kotlin documentation into your own language and to publish your translation on your Web site. ) と宣言しています。
また、オリジナルはgithub上のプロジェクトで管理されており、ライセンス表示がApache License ver. 2.0で宣言されています。従って本ドキュメントもそれにならって、ライセンスをApache License ver. 2.0とします。