明日からちょっと KotlinでAndroidが書きたくなる(かもしれない?) SAM変換と拡張関数 室星亮太 2014/7/4(金) 第2回 かわいいKotlin勉強会 #jkug
明日からちょっと
KotlinでAndroidが書きたくなる(かもしれない?)
SAM変換と拡張関数
室星亮太
2014/7/4(金))第2回)かわいいKotlin勉強会)#jkug
突然ですが質問です!
Kotlin書いたことある方?
「Kotlin'書いたことない」ってイベントページに書いたけれど本当は書いたことある方?
Androidアプリ開発したことある方?
ありがとうございました!
お前だれよ• 名前":"室星亮太
• 仕事":"Androidアプリ開発(Java)、Unityゲーム開発(C#)
• Twi6er":"@RyotaMurohoshi
• 投稿先":"h6p://qiita.com/RyotaMurohoshi
• 興味":"Kotlin,"Groovy,"C#,"Unity,"SonyのWearable"Device
AndroidアプリをJavaで開発していて、
「なんでこんなに冗長なコードが必要なんだ!」
ってイラっとすることはありませんか?
私はあります!
C#を業務で使い始めたりしたり、
Groovyをほんのちょっと勉強したら、
「なんでJava、〇〇できないのー!」って、イラッとなりました
そこでKotlinですね!
明日からちょっと
KotlinでAndroidが書きたくなる(かもしれない?)
SAM変換と拡張関数と題して今日はLTします!
いらっとする冗長なコード1
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.v(TAG, "clicked"); }});
どんなAndroid入門書にも載っている、アクティビティでよくみるコード
Kotlinで書いたやつと比べると、冗長なのが一目瞭然!
Kotlinだとすっきり
button.setOnClickListener { Log.v(TAG, "clicked") }
Java、KotlinそれぞれのクリックリスナーKotlin
button.setOnClickListener { Log.v(TAG, "clicked") }
Java
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.v(TAG, "clicked!"); }});
行数が短くなったことではなく、「冗長な部分は書く必要がなく
本質的なことだけ書けばよくなった」というのがミソ
Javaのコードをもう一度見てみましょうbutton.setOnClickListener(new View.OnClickListener() { // new以降本質じゃない @Override // <- 本質じゃない public void onClick(View v) { // <- 本質じゃない Log.v(TAG, "clicked!"); } // <- 本質じゃない}); // <- 本質じゃない
Javaは本質じゃない部分が多い。Kotlinは本質のみを記述すればいい
Kotlinはなぜあんな記述ができるのか?
Single'Abstract'Method'Conversions
SAM変換h"p://blog.jetbrains.com/kotlin/2013/08/kotlin;m6;is;here/
SAM$インターフェース一つの(Single))抽象(Abstract))メソッド(Method)
をもつインターフェース
• Runnable):)void)run()
• View.OnClickListener):)void)onClick(View)v)
• Response.Listener<T>):)void)onResponse(T)response))
など、他にもたくさん
SAM変換関数リテラル!">!SAMインターフェース!な変換
Kotlinでは、SAMインターフェースを引数にとるメソッドで、
引数の型と順序そして返値型が一致する関数リテラルを渡すと
インターフェースに変換してくれる
SAMインターフェースが必要な所も、関数リテラルでスッキリ!
SAM変換できる例button.setOnClickListener( { (v : View): Unit -> Log.v(TAG, "clicked") })button.setOnClickListener( { v -> Log.v(TAG, "clicked")})button.setOnClickListener{ v -> Log.v(TAG, "clicked") }button.setOnClickListener{ Log.v(TAG, "clicked") }
val listener : (View) -> Unit = {v -> Log.v(TAG, "clicked") }//orval listener : (View) -> Unit = { Log.v(TAG, "click") }//orval listener = { (v : View) : Unit -> Log.v(TAG, "clicked") }button.setOnClickListener(listener)
SAM変換できない例// 下記はコンパイルエラー// Type mismatchval listener : View.OnClickListener = { (v : View) : Unit -> Log.v(TAG, "clicked") }
// 下記は実行時エラー// java.lang.ClassCastExceptionval listener = { (v : View) : Unit -> Log.v(TAG, "clicked") } as View.OnClickListener
引数に関数リテラルを渡さないといけない
SAM変換で無駄コードを無くせますね!もう一例
VolleyのStringRequest
VolleyのStringRequest0Java版 StringRequest request = new StringRequest( "https://www.google.co.jp/", new Response.Listener<String>() { @Override public void onResponse(String response) { Toast.makeText(getApplicationContext(), response, Toast.LENGTH_LONG).show(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Toast.makeText(getApplicationContext(), "onErrorResponse", Toast.LENGTH_LONG).show(); } } );
無駄な部分が多く、縦に長い...
VolleyのStringRequest0Kotlin版 val request = StringRequest( url = "https://www.google.co.jp/", listener = { response -> Toast.makeText(this, response, Toast.LENGTH_LONG).show() }, errorListener = { volleyError -> Toast.makeText(this, "onErrorResponse", Toast.LENGTH_LONG).show() } );
型パラーメータがあるインターフェースもSAM変換可能
名前付き引数で可読性アップ
ちょっとGroovyもみてみましょうGroovyもSAM変換が使えるようです。
button.setOnClickListener { v -> Toas.makeText(this, "click", Toast.LENGTH_LONG).show() }
Groovy&2.2からas演算子がいらなくなったそうです。
h"p://groovy.codehaus.org/Groovy+2.2+release+notes
ちょっとGroovyもみてみましょうSAMインターフェースだけでなく、複数メソッドを持つインターフェースも
Map+クロージャー+as演算子でこんな感じに!
viewPager.setOnPageChangeListener ([ onPageScrollStateChanged: { state -> Log.v(TAG, state) }, onPageScrolled : { position, positionOffset, positionOffsetPixels -> /*略*/ }, onPageSelected : { position -> Log.v(TAG, position) }] as ViewPager.OnPageChangeListener)
h"p://groovy.codehaus.org/Groovy+way+to+implement+interfaces
実はAndroidStudioとIntelliJだと一つだけメソッドを実装した匿名クラスがいい感じに!(SAM型も)
button.setOnClickListener((v) -> {Log.v(TAG, "clicked");});
IDE上ではいい感じで折り畳まれてますが、
githubとかでコードリビューするときは、
ほら畳まれないし...
h"p://qiita.com/RyotaMurohoshi/items/0ce799c747d91756131a
以上SAM変換でした。
いらっとする冗長なコード2
ImageView imageView = (ImageView)findViewById(R.id.image_view);
どんなAndroid入門書にも載っている、アクティビティでよくみるコード
けれど「ImageView」ってなんで2回書く必要あるん?型推論してくれてもいいじゃん!
とりあえず継承します?Ac#vityを継承したクラスBaseAc#vityにて、こんなメソッド定義すれば、
public <T extends View> T findById(int viewId) { return (T)findViewById(viewId); }
BaseAc'vityを更に継承したクラスでは、こんな感じでキャスト不要になりますね
ImageView imageView = findById(R.id.image_view);
けど継承って• 継承むずい
• 「継承が許されるのは小学生までだよね」、だと...
• Ac$vityのサブクラスいくつかあるけど、全部に更にサブクラス作ってメソッド定義する?
ちょっとないかな...
さてどうしましょう?• 冗長な記述が頻発したり、よく使う処理がある
• Ac$vityとかに、メソッドを追加したい!
• けど継承したくない)or)できない
Extension)Funcitons
拡張関数h"p://confluence.jetbrains.com/display/Kotlin/Extension+func=ons
Extension)Funcitons(拡張関数)とは• 継承せずにメソッドを追加できる"
• 継承禁止なクラスにもメソッドを追加できる
• privateなメンバにアクセスはできない
• メソッドをオーバーライドはできない
Extension)Func-onsの例)定義側extensions.kt内にて
package com.mrstar.extensions
import android.app.Activityimport android.view.View
fun <T : View> Activity.findById (id : Int) : T = findViewById(id) as T
Extension)Func-onsの例)利用側package com.mrstar.android_with_kotlin
// 略import com.mrstar.extensions.findById // <- 注目
public class MainActivity() : FragmentActivity() { // <- 注目 protected override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
val textView: ImageView = findById (R.id.image_view) // <- 注目 }}
• fun$ClassName.methodName(hoge$:$Hoge)$:$Fuga"みたいな感じでメソッドを定義します
• 定義した拡張メソッドをimportします
• そうすると普通のメソッドのように使えます
• この例だとAc+vityのサブクラスだけでなく、FragmentAc+vity、Ac+onBarAc+vity、他のサブクラスでも使えます(継承で定義するなら、各クラスにメソッド定義が必要)
Extension)Func-onsを使えば...• 継承しなくてもメソッドを追加できますね
• たくさんのクラスにメソッドを追加しなくてもいいですね
• 自作のメソッドで冗長な記述をスッキリできますね!
ちょっとC#もみてみましょうC#にも拡張関数と同じような拡張メソッドがあります。
// 定義側 public static class StringExtensions { public static string Decorate(this string str, string symbol) { return string.Format("{0}{1}{2}", symbol, str, symbol); } }
//利用側 string decoratedMessage = "Hello!".Decorate("===")
こんな感じで、sta$cなクラスにsta$cなメソッドとして拡張メソッドを定義します。
ちょっとC#もみてみましょうC#は• Javaのpackageアクセスのような、メンバを同じ名前空間だけに公開するという制限がない
• privateでsta.cな入れ子のクラスは作れる
• が↑なクラスでは拡張メソッドを定義できない
このクラスにだけ、この名前空間でだけ使えるという拡張メソッドを定義できない!
一方Kotlinでは• packageにその名前空間とそのサブ名前空間限定で
• クラス内にprivateアクセスレベルで
• 関数内にローカル関数内として、ローカルスコープで
Kotlinだと拡張関数を定義できる!
以上拡張関数でした。
質問ありますか?
ご清聴ありがとうございました!
SAM変換について(Qiita)
h"p://qiita.com/RyotaMurohoshi