金田著「はじめてのandroidプログラミング 第5版」を使って、勉強しています。
「第8章タイマーを使ってスライドショーを実装する」で使っているkotlinの文法のテクニックを解析してみます。
今日のテーマは、「SAM変換を元に戻す」です。初心者にとって、ラムダ式は難しいですよね。javaのインターフェースでメソッドをひとつしか持たないものをSAMインターフェースと呼び、KolinではSAMインターフェースを引数としたメソッドを、ラムダ式で置き換えることができます。これをSAM変換といいます(本書p121から引用)。
そこで、SAM変換しているコード、変換を使わない元のコードに戻してみようと思います。
以下、本書を持っている前提で説明します。本書を使って勉強中の方、一緒に勉強しましょう。
0.前提
p227まで写経が済んでいるとします。
1.原型
p227まで写経が済んでいる状態のコードを示します。説明に不要な部分は、著作権に配慮して省略しています。
class MainActivity : AppCompatActivity() {
// 省略
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
// 省略
timer(period = 5000) {
handler.post {
// 省略
}
}
}
}
}
2.SAM変換する前に戻す
上のソースコードはp226の下側からp227の上側に書かれたコードに従って書かれている。これをp226の下から11行目から始まるコードに従ってSAM変換する前に戻す。
class MainActivity : AppCompatActivity() {
// 省略
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
// 省略
val handler = Handler(Looper.getMainLooper())
timer(period = 5000) {
handler.post ( // <==ここは丸括弧。波括弧にすると、これ以降のコードが動かなくなる。
object : Runnable { // p120で説明されているオブジェクト式
override fun run() {
// 省略
}
}
}
) // <==ここは丸括弧
}
}
}
Runnableインターフェースはrun()メソッドひとつだけを持つSAMインターフェースだから、SAM変換できる。
3.オブジェクト式で書かれている部分を、オブジェクト式(無名インナークラス)ではない形に戻す
p226に「Handlerクラスを使う」という水色の囲みこみがある。これに従って、無名インナークラスでない、普通のクラスとインスタンスを使ったコードに戻す。コード中に書かれた(1)から(6)のコメントに書き換えの方法を記したので順を追って読んでほしい。
private lateinit var binding: ActivityMainBinding // (4) (3)にあったbindingプロパティーをMainActivityクラスの外に移動(追記)
class MainActivity : AppCompatActivity() {
// 省略
// private lateinit var binding: ActivityMainBinding // (3)TestObjectクラスをMainActivityクラスの外にに作ったため、bindingプロパティーもMainActivityクラスの外に移動(削除)
override fun onCreate(savedInstanceState: Bundle?) {
// 省略
val testObject = TestObject() // (5) (2)で作ったTestObjectクラスのインスタンスを生成する。(追記)
timer(period = 5000) {
handler.post(
// object : Runnable { // (1)Runインターフェースを実装したクラスを無名クラスでない形にするため外に出す(削除)
// override fun run() {
// // 省略
// }
// }
// }
testObject // (6) (5)のインスタンスを(1)の無名クラスのインスタンスの代わりにhandler.post()メソッドに与える
)
}
}
}
private class TestObject : Runnable { // (2)(1)にあった無名クラスを、無名クラスでない普通のクラスに直す(追記)
override fun run() {
// 省略
}
}
}
4.TestObjectクラスをMainActivytyクラスのネストクラスにする
最後に、今回のテーマではないが、TestObjectクラスを既にあるMainActivityクラスの中にあるネストされたMyAdapterクラス同様に、MainActivytyクラスのネストクラスにしてみる。
private lateinit var binding: ActivityMainBinding // (3)TestObjectクラスをMainActivityクラスのネストクラスに変更しても、bindingプロパティーはMainActivityクラスの中には入れられない。
class MainActivity : AppCompatActivity() {
// 省略
class TestObject : Runnable { // (2) (1)のTestObjectクラスをネストクラスにする(移動)
override fun run() {
//省略
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// 省略
val testObject = TestObject()
timer(period = 5000) {
handler.post(
testObject
)
}
}
}
//private class TestObject : Runnable { // (1)テストクラスをMyAdapterクラスのようにMainActivityクラスのようにネストクラスにする(削除)
// override fun run() {
// // 省略
// }
// }
//}
この時、(3)のbindingプロパティーは、MyAdapterクラスの中に戻すことはできない。これは興味深いことだ。おそらくクラスをjavaにデコンパイルすると、TestObjectクラスはMainActivityクラスの中にはないのであろう。AndroidStudioのデコンパイルの方法がわからなくなったため、確証が得られないのが残念である。後日追記したい。
5.TestObjectクラスのインナークラス化(2020年1月20追記)
第4項でbindingプロパティーをMainActivityクラスの中に入れられない現状が起きていていたが、これを解決する方法がインナークラス化である。インナークラス化することにより、インナークラスの外にある外部変数をインナークラス内部から参照できるようになる。このことは、本書p250の中段付近に書かれていたので本記事に反映させた。
// private lateinit var binding: ActivityMainBinding // (2)(1)でTestObjectクラスにinnerを付けたことにより、bindingプロパティーをMainActivityクラスの中に入れることができる。(削除)
class MainActivity : AppCompatActivity() {
// 省略
// class TestObject : Runnable { // (変更)
inner class TestObject : Runnable { // (1) classの前にinnerキーワードを付ける
override fun run() {
// 省略
}
}
private lateinit var binding: ActivityMainBinding // (3)(2)で削除したbindingプロパティーをMainActivityクラスの中に入れる(追加)。
override fun onCreate(savedInstanceState: Bundle?) {
// 省略
}
}