見出し画像

「COMPOSE を用いた ANDROID アプリ開発の基礎」の学習支援⑨ -ユニット3パスウェイ1

皆さん、こんにちは!又はこんばんは!初めての方は初めまして!
Google Codelabsの「COMPOSE を用いた ANDROID アプリ開発の基礎」コースのお手伝いをする「りおん」です。
今回は、ユニット3「リストの表示とマテリアル デザインの使用」の中のパスウェイ1「Kotlinの基礎の詳細」です。

補足なので、「COMPOSE を用いた ANDROID アプリ開発の基礎」コースで心配になった時、エラーが起きて詰まった時や、分からないことがあった時、軽く復習したい時に見てください!
また、目次を見て、自分に必要なところだけ見るのをお勧めします!

この記事を作成するにあたり使用しているAndroid StudioのバージョンはGiraffeです。バージョンによってはUIが違ったりもするのでご了承ください。
また、2024年2月19日現在の「COMPOSE を用いた ANDROID アプリ開発の基礎」コースを参考にしています。


学習内容

②ジェネリック、オブジェクト、および拡張機能

Codelabs内でも言われていた通り、この章で学んだ内容はどれも必須とは言えません。しかし、人為的なミスを防いだり、コードの簡潔化に役立ったりととても便利ですし、様々なところで使われているので重要です。
この章で学んだジェネリック、enumクラス、データクラス、シングルトンオブジェクト、拡張プロパティ、拡張関数、インターフェース、スコープ関数これらを働きで分けると、

  • コードの簡潔化->ジェネリック、データクラス、スコープ関数

  • ミス防止->enumクラス、シングルトンオブジェクト、インターフェース

  • 拡張性->拡張プロパティ、拡張関数

と大きく三つに分けることができます。どれもよく使われているので一つ一つ簡潔に解説します。

ジェネリックは、再利用可能なクラスを作成する時に用いられます。具体的には、以下のコードの様に同じプロパティを持っているがデータ型が違う場合(ここではanswerのデータ型)に使用します。ジェネリックを用いることで下の例だとクラスが3つから1つになるようにコードが短くなります。

class FillInTheBlankQuestion(
    val questionText: String,
    val answer: String,
)

class TrueOrFalseQuestion(
    val questionText: String,
    val answer: Boolean,
)
class NumericQuestion(
    val questionText: String,
    val answer: Int,
)
//ジェネリックを使用した場合
class Question<T>(
    val questionText: String,
    val answer: T,
)

enumクラスは値が限られている時に使用します。enumクラスを用いることで人為的なミスを防ぐことが出来ます
たとえば、値が東西南北に限られているとします。何かの拍子で誤ってsouthをtouthと書いてしまった場合enumクラスを作っていないと、エラーが出ないため誤字に気づきません。

fun main() {
    //エラーが出ない
    println("南は英語でtouth")
    //エラーが出る
    println("南は英語で${Direction.touth}")
}

//enumクラス
enum class Direction{
    east,west,north,south
}

データクラスはクラスがデータのみでメソッドを持たない場合に使用します。データクラスにすることでtoString()などのメソッドを自動で実装してくれます。

fun main() {
    //普通のクラスでtoString()を使用
    println(Message1(1,"佐藤").toString())
    //データクラスでtoString()を使用
    println(Message2(1,"佐藤").toString())
}

//クラス
class Message1(
    val number: Int,
    val text: String
)
//データクラス
data class Message2(
    val number: Int,
    val text: String
)

//結果
//message1@7cc355be
//message2(number=1, text=佐藤)

シングルトンオブジェクトはクラスにインスタンスを一つだけ持たせる時に使用します。そうすることで設計上インスタンスを複数作ってはいけないのに作ってしまったことが原因で起きるバグを防ぎます
下のコードを見ていただくと分かりますが、TokyoSkytreeクラスの場合片方のオブジェクトを操作しても、もう片方には影響ありません。しかし、TokyoTowerシングルトンオブジェクトの場合は片方のオブジェクトを操作したらもう片方にも影響が出ています。つまり、二個のオブジェクトを作成したように見えるが実はオブジェクトは一つだけしか作成されていないのです。

fun main() {
    val tokyoTower = TokyoTower
    val tokyoTower2 = TokyoTower
    val tokyoSkytree = TokyoSkytree(634,"日建設計")
    val tokyoSkytree2 = TokyoSkytree(634,"日建設計")
    
    println(tokyoTower.height) //answer -> 333
    println(tokyoTower2.height) //answer -> 333
    tokyoTower2.height = 120
    println(tokyoTower.height) //answer -> 120
    println(tokyoTower2.height) //answer -> 120
    
    println(tokyoSkytree.height) //answer -> 634
    println(tokyoSkytree2.height) //answer -> 634
    tokyoSkytree2.height = 480
    println(tokyoSkytree.height) //answer -> 634
    println(tokyoSkytree2.height) //answer -> 480
}
//普通のクラス
class TokyoSkytree(
    var height: Int,
    val architect: String
)
//シングルトンオブジェクト
object TokyoTower{
    var height: Int = 333
    val architect: String = "内藤多仲"
}

拡張プロパティと拡張関数を用いると後からクラスに関数又はプロパティを追加することが出来ます
下のコードはコメントアウトしたところと拡張プロパティと拡張関数はほぼ同じ機能を持ちます。厳密に言うと拡張プロパティの方はゲッターしか定義できません。ここからも分かる通り必要とまでは言えませんが、何らかの理由があってクラスを変えないままクラスを拡張したいときに継承を行わずに簡単に拡張できるのは便利です。

fun main() {
    val nabe = Nabe()
    println(nabe.yasai)
    println(nabe.dashi)
    nabe.shime()
}

class Nabe(){
    val yasai: String = "hakusai"
    val niku: String = "buta"
    //val dashi: String = "mizu"
    /*
    fun shime(){
        println("udon")
    }*/
}

val Nabe.dashi: String 
	get() = "mizu"

fun Nabe.shime(){
    println("udon")
}

次にインターフェースです。Codelabsの「インターフェースは契約です」という言葉が分かりやすいように、インターフェースに準拠するクラスはインターフェースで指定されたすべてのプロパティとメソッドを実装する必要があります。勿論、拡張するのでクラスはインターフェースで指定されたプロパティとメソッド以外を持つことはできます。
インターフェースにより、クラスの構造が分かりやすくなったり、インターフェースを変える時にそれに準拠するクラスを全て変える必要が出るので変更漏れが生じるのを防いだりします。役割でいうとenumに似ています。

fun main() {
    val nabe = Nabe()
    println(nabe.yasai)
    println(nabe.dashi)
    nabe.shime()
}

interface sozainoazi{
    val dashi: String 
    fun shime()
}

class Nabe(): sozainoazi{
    val yasai: String = "hakusai"
    val niku: String = "buta"
    override val dashi: String = "mizu"
    
    override fun shime(){
        println("udon")
    }
}

スコープ関数はコードを簡潔にするために使用されます。
下の例にある通り、itを使うことで同じ変数名を繰り返し書かなくてよくなるのでより少ないコードで済みます。

fun main() {
    val google = Google()
    google.repeat()
}

class Google(){
    val googleCodelabs: String = "googleCodelabs"
    
    fun repeat(){
        println(googleCodelabs)
        println(googleCodelabs)
        println(googleCodelabs)
        //上と同じ機能を持つ
        googleCodelabs.let{
            println(it)
        	println(it)
        	println(it)
        }
    }
}

また、apply()関数を用いると上のコードをさらに短くできます。
apply()関数はインスタンスへの参照がなくともメソッドを呼び出すことが出来ます。

fun main() {
    Google().apply{
        repeat()
    }
}

class Google(){
    val googleCodelabs: String = "googleCodelabs"
    
    fun repeat(){
        println(googleCodelabs)
        println(googleCodelabs)
        println(googleCodelabs)
        //上と同じ機能を持つ
        googleCodelabs.let{
            println(it)
        	println(it)
        	println(it)
        }
    }
}

③Kotlin でコレクションを使用する

多数の値で構成されるデータを扱う方法としてコレクション型(=データ構造)があり、そのコレクション型として、配列(Array)、リスト、セット、マップの4つを下の画像の様に特徴によって使い分ける必要があると学習したと思います。

コレクション型(=データ構造)の分類

④コレクションを使用した高階関数

ここではコレクションの並べ替えなどの処理をより少ないコードで行わせるために、コレクションを使用した高階関数(関数をパラメーターに受け取ったり関数を返したりする関数)について学びました。
具体的には

  1. forEach()    -> 要素の数だけループ処理を行う

  2. map()         -> 元のコレクションと要素数が同じ新しいコレクションの作成する(元のコレクションの要素を基に新しいコレクションを作成したりする時に使用)

  3. filter()         -> 元のコレクションから任意の条件を満たす新しいコレクションを作成する

  4. groupBy()   -> リストをマップに変換する

  5. fold()          -> コレクションから単一の値を生成する(要素の総和など)

  6. sortedBy()  -> 任意の基準でコレクションを並べ替える

用語

今回は新しく出た用語がほとんどなかったと思います。また、下で説明している用語もメモリを除き今後出ない可能性が高いです。

③Kotlin でコレクションを使用する

メモリ

メモリはデータを短期間格納する場所で、ストレージは長期間保存する場所です。さらに言うとパソコンの電源を落とした際にデータが消えるのがメモリ、データが残るのはストレージです。
メモリはストレージに比べてデータを取り出したり収納したりする時間が速いので、今回のCodelabsの学習内容である配列はここに置かれるわけです。

ランダムアクセスメモリ(RAM)

Iメモリに保存されているデータにアクセスする際にアドレスのような番地に従って直接データにアクセスできるメモリのことです。データにアクセスする際に一番目から目的のデータがないか順に調べていく(シーケンシャルアクセス)よりデータにアクセスするまでの時間が早いです。

ハッシュコード

ハッシュコードとは、文字列などをハッシュ関数に入れることで一意の数値(ハッシュ値)を返すメソッドです。一意のハッシュ値というのがミソで、同じ文字列ならば必ず同じ数値になりますが、少しでも異なる文字列ならば違う数値になります。
セットのようにハッシュ値をインデックスに使えば、既に要素としてある文字列を追加しても、同じところに収納されるだけなので要素数は増えません。
ここからは完全に余談ですが、元の値が何ビットの値でもハッシュ値は固定長(SHA-256なら256ビットの数値のように)のイメージでしたが、hashCode()関数だとそうではないらしいですね。

fun main() {
    println("記事を読んでくれて誠にありがとうございます。".hashCode())//-671952523
    println("記事を読んでくれて誠にありがとうございます!".hashCode())//-671964780
    println("学習お疲れ様です".hashCode())//67145586
}

最後に

今回もCodelabsでの学習お疲れさまでした!
今回学んだ内容は結構使われているので、是非今後のCodelabsで注意深く確認してみてください!

お知らせ

都合により、来週の火曜日(2024/2/27)は記事の投稿が出来ません。
再来週(2024/3/5)からはまた投稿を再開するので是非見に来てください。

この記事が気に入ったらサポートをしてみませんか?