現在、Kotlinの勉強をしていてクロージャを理解しようとしているのですが、考え方があっているのか分かりません。
私の考え方では、クロージャとは、クラスのprivateメンバにアクセスできないのと同様に、本来ならアクセスできないスコープ外の変数を、ラムダ式や無名関数でアクセスできるようにする手段、と理解しています。
この考え方だと、クラスでゲッターやpublicなメソッドを使っているのと何ら変わらない気がしています。
クロージャを使うメリットもあまり理解していないので(オブジェクト指向のカプセル化の概念が邪魔してます…)、分かりやすく説明していただけませんか?
よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答4件
0
私の考え方では、クロージャとは、クラスのprivateメンバにアクセスできないのと同様に、本来ならアクセスできないスコープ外の変数を、ラムダ式や無名関数でアクセスできるようにする手段、と理解しています。
半分合ってますが、半分間違いです。
他スコープの変数であれば、仰る通り他の方法でいくらでもアクセスできます、わざわざクロージャなどを使う必要はありません。
クロージャには色々な使い方があり色々な側面があります、ですので、色々な説明があります
誤解を恐れずにざっくり説明するのであれば、クロージャの特異性は本来破棄されるはずのスコープ外のローカル変数を束縛できるという事です。
ローカル変数の束縛を伴わないクロージャは他の手段で代替可能ですし、かえってわかりにくいコードになりがちです。
なお、こちらの質問で様々な方がクロージャについて説明しています。
ぜひ一読するとよいと思います。
投稿2017/06/21 07:15
編集2017/06/21 10:26総合スコア930
0
kotlin に限らずですが、クロージャはそれを定義したスコープに束縛されます。プロパティアクセス等とは異なるセマンティクスです。例えば javascript で言えば
▼ foo.js
javascript
1function doSomething(f) { 2 f("hello"); 3}
▼ bar.js
javascript
1var a = 1; 2doSomething(function(s) { 3 console.log(a); // a が見える 4 console.log(s); // foo.js から渡された値も扱える 5});
この様に異なるスコープ間でのメッセージパッシングとして使えます。一般的にコールバックという形でクロージャが使われるのはこの理由です。
これを応用すると以下の様に、外からは触れないカウンターを作る事も出来ます。
javascript
1var counter = function() { 2 var count = 0; 3 return function() { 4 count++; 5 return cuont; 6 } 7}(); 8 9console.log(counter()); // 1 10console.log(counter()); // 2 11console.log(counter()); // 3
構造を隠ぺいしつつもスコープ間のデータをやり取りするのがクロージャですので、プロパティとはセマンティクスが異なります。
投稿2017/06/21 07:01
総合スコア5030
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
まずクロージャの使い方や存在意義についてはこちらが分かりやすいと思います。
クロージャってどんなときに使うの? ~ 利用場面を 3つ 挙げてみる - Qiita
また、javascript等のようにメソッド/メンバ変数に対するアクセス修飾子がない言語だとpublicなgetter、privateなsetterを作るのに重宝したりします。
投稿2017/06/21 06:52
総合スコア5572
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
クロージャはオブジェクト指向のカプセル化にすぎないんじゃないか…まさしくその通りです。
次のコードを見てください。
Kotlin
1fun addNum(a: Int) = { b: Int -> a + b } 2 3fun main(args: Array<String>) { 4 val x = addNum(3) 5 val y = x(5) 6 println(y) 7}
addNum
はカリー化されている関数で、クロージャを使っています。引数a
はaddNum
の関数が終了しても、戻り値であるラムダ式で生成され関数でそのまま生き続けるため、x(5)
は前に渡した3をたして、8になります。
これをクロージャを使わずに、クラスで書くとこうなります。
Kotlin
1class AddNum constructor(private val a: Int) { 2 operator fun invoke(b: Int) = a + b 3} 4 5fun main(args: Array<String>) { 6 val x = AddNum(3) 7 val y = x(5) 8 println(y) 9} 10
AddNum
クラスはaddNum
関数と全く同じように使用できます。別にクロージャなんて必要なかったんです。
さらにもう一つ、最初のコードをコンパイルすると"ファイル名$addNum$1.class"という謎のクラスファイルが作られます。このクラスはAddNum
とだいたい同じ事(細かいところは異なります)をしているクラスです。そう、クロージャなんてものはクラスでのカプセル化にすぎず、それを自動生成しているだけなのです。
別の視点から見てみましょう。KotlinはJavaVM上で動きます。ですので、JavaVMで実装できないことはKotlinでは実装できません。しかし、JavaVMはクロージャを考慮した作りにはなっていません。Javaにはそもそもクロージャは存在しません(finalまたは実質finalの変数をラムダ式や匿名クラス内で使用できますが、真のクロージャのように環境を取り込んでいるわけではありません)。しかし、一つの自動生成されるクラスとして作成し、クラスのカプセル化を利用して、クロージャで包むべき環境をクラスのフィールドに持たせれば、クロージャと同じ動きができます。なので、Kotlinはクロージャを実装できたのです。もし、クロージャをクラスを使ってその動きを実現できなければ、そもそもクロージャという機能がKotlinに備わることはなかったでしょう(その時点でJavaVM上に実装するという選択肢は無かったと思われますが)。
なんだ、やっぱりクロージャはいらないんだ…。となるでしょうか?二つのコードを見比べて下さい。後者はいかにもオブジェクト指向的な発想ですが、前者のクロージャは関数型でみられる発想です。現代的なプログラミング言語において、関数型プログラミングがしやすいかどうかは、言語選択基準の重要な要素の一つです。クロージャが使えないような言語では、関数型プログラミングがしやすいとはとてもじゃないですが言えません。Javaの代替、強いては打倒Scalaを目指したKotlinにおいて、関数型プログラミングがしやすいことは必須条件であり、クロージャもまた必須の機能であったと思われます。
私が思う結論から言いますと、関数型プログラミングの発想でプログラミングをしない限り、クロージャは役に立つものではありません。旧来のJava的な発想のコードなら、たぶん、使うことは無いでしょう。ですが、関数型プログラミングを取り入れようとした瞬間、クロージャ無しで様々な動作を実現することはかなり厳しいと言えると思います。逆に言えば、関数型プログラミングを知らない限り、クロージャのありがたみはあまりわからないという事です。
私は今日初めてKotlinの書き方を学びましたので、色々間違っているかと思います。おかしな点はご指摘下さい。
投稿2017/06/21 13:44
編集2017/06/21 21:26総合スコア21741
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。