初めてのkotlin

kotlinによるAndroidアプリ開発での気付き

親クラス内の処理から呼ばれた継承可能なメソッドは、どれが動くのか?

多分、知ってる人は知ってるごく当たり前な話なのだとは思いますが、 私は知らなかったので、実験です。

親クラス内から呼び出された継承可能なメソッドは、 子クラスから呼び出された時に、 オーバーライドされたメソッドが動くのか?と言う実験です。

ついでに、親クラスにCastした子クラスでも、 子クラス側のメソッドが動くのか?についても実験しました。

答えを先に書きますと、全てYesです。

open class BaseClass() {

    open fun method1() {
        println("BaseClass.method1")
    }

    fun methodBase() {
        method1()
    }

}

class ChildClass() : BaseClass() {

    override fun method1() {
        println("ChildClass.method1")
    }

    fun methodChild() {
        method1()
    }

}

fun main() {
    val child1 = ChildClass()
    child1.methodBase()        //ChildClass.method1
    val base1 = child1 as BaseClass
    base1.methodBase()         //ChildClass.method1
    //base1.methodChild()       //さすがに親クラスにないメソッドは呼び出せない
    val base2 = BaseClass()
    base2.methodBase()         //BaseClass.method1
}

90%の機能が共通のViewはどのように継承すべきか?

ここに機能の90%が共通のViewが2種類あります。 これらのViewは、以下のように5パターンの作り方があるかと思います。

1.それぞれ別に作る

2.一方のViewをもう一方が継承する

3.共通機能部分でBaseになるViewを作り、それぞれがBaseを継承する

4.抽象クラスを作り、それぞれが抽象クラスを継承する

5.継承せずにひとつのクラスの内部で条件分岐させる

1.それぞれ別に作る

たまたま設計時の機能が似ているだけで、用途の違うものだったら、 迷うことなくこうすべきですね。

2.一方のViewをもう一方が継承する

Viewでなければこれでもいいでしょう。 でも、形の違うViewだとこの方法は使えません。 また、継承しなければprivateで宣言できたメソッドが、 継承するためにpublicになってしまうと言うデメリットを生んだりします。

3.共通機能部分でBaseになるViewを作り、それぞれがBaseを継承する
4.抽象クラスを作り、それぞれが抽象クラスを継承する

実は、私はこの2つの違いを理解できていません。 クラスの定義でopenと書くかabstractと書く以外に何が違うのかが、分かっていません(^^; openで書いて、完成してからabstractに変えてもそのまま動きますし(^^; 今回の要件では、直接インスタンス化しないものなので、4でいいのかな?とは思いますが。

それ以上の違いってないのかな?

5.継承せずにひとつのクラスの内部で条件分岐させる

これでも可能。 99%共通ならこれでもじゅうぶんだと思う。 しかし、特定のViewでしか使わない変数やメソッドを持つとしたら? どの程度の差異があれば分けるのがいいんだろう?

判断基準が意外と難しい。

演算子のオーバーロード

Operator overloading

Java演算子オーバーロードができないそうですが、 kotlinはできるようです。

なので、昨日UPしたMyDateクラスに早速実装してみましたヾ(´∀`)ノ

計算式によって、日付を加算したり差分(期間)を求めたりできるようになりました。 (今作ってるプログラムでは使わないのですが...(^^;)

class MyDate(
    zone: TimeZone = TimeZone.getDefault(),
    aLocale: Locale = Locale.getDefault()
) {
    ////////////////////////////////////////////////////////////////
    // Data
    ////////////////////////////////////////////////////////////////
    private var date : Calendar

    ////////////////////////////////////////////////////////////////
    // Property
    ////////////////////////////////////////////////////////////////
    var year : Int
        get() = date.get(Calendar.YEAR)
        set(value) {
            date.set(Calendar.YEAR, value)
        }
    var month : Int
        get() = date.get(Calendar.MONTH) + 1
        set(value) {
            date.set(Calendar.MONTH, value - 1)
        }
    var day : Int
        get() = date.get(Calendar.DAY_OF_MONTH)
        set(value) {
            date.set(Calendar.DAY_OF_MONTH, value)
        }

    ////////////////////////////////////////////////////////////////
    // Method
    ////////////////////////////////////////////////////////////////
    fun addDay(day: Int) {
        date.add(Calendar.DAY_OF_MONTH, day)
    }

    fun getTimeInMillis(): Long {
        return date.getTimeInMillis()
    }

    override fun toString(): String {
        return "%1\$d-%2\$02d-%3\$02d".format(year, month, day)
    }

    ////////////////////////////////////////////////////////////////
    // Operator
    ////////////////////////////////////////////////////////////////
    /**
     * date++
     */
    operator fun inc(): MyDate {
        date.add(Calendar.DAY_OF_MONTH, 1)
        return this
    }
    operator fun dec(): MyDate {
        date.add(Calendar.DAY_OF_MONTH, -1)
        return this
    }

    /**
     * date += n
     */
    operator fun plusAssign(day: Int) {
        date.add(Calendar.DAY_OF_MONTH, day)
    }
    operator fun minusAssign(day: Int) {
        date.add(Calendar.DAY_OF_MONTH, -day)
    }

    /**
     * newDate = date + n
     */
    operator fun plus(day: Int): MyDate {
        val newDate = MyDate(date)
        newDate += day
        return newDate
    }
    operator fun minus(day: Int): MyDate {
        val newDate = MyDate(date)
        newDate -= day
        return newDate
    }

    /**
     * days = date1 - date2
     */
    operator fun minus(date2: MyDate): Int {
        val diffTime = date.getTimeInMillis() - date2.getTimeInMillis()
        return (diffTime / (1000 * 60 * 60 * 24)).toInt()
    }

    ////////////////////////////////////////////////////////////////
    // Initialize
    ////////////////////////////////////////////////////////////////
    init{
        date = Calendar.getInstance(zone, aLocale)
        date.set(Calendar.HOUR, 0)
        date.set(Calendar.MINUTE, 0)
        date.set(Calendar.SECOND, 0)
        date.set(Calendar.MILLISECOND, 0)
    }

    constructor(Year: Int, Month: Int, Day: Int) : this() {
        this.year = Year
        this.month = Month
        this.day = Day
    }

    constructor(date: Calendar) : this() {
        this.date = date.clone() as Calendar
    }
}

ネットで調べたところ、演算子は必ずしも全てを実装しなくても使えるようなことが書かれた説明がありましたが、 ケースバイケースのようですから、一通りの書き方は試しておいた方がいいようです。 (+=を実装しても、++は使えませんでした。)

以下は使い方

    val date1 = MyDate(2020, 8, 20)
    println(date1.toString())   //2020-08-20
    date1 += 2                       //この書き方はvalで宣言した時だけ有効
    println(date1.toString())   //2020-08-22
    var date2 = date1 + 3
    println(date2.toString())   //2020-08-25
    date2++                           //この書き方はvarで宣言した時だけ有効
    println(date2.toString())   //2020-08-26
    date2 = date2 + 10          //varに複数日加算したい時は、+=ではなくこう書く
    println(date2.toString())   //2020-09-05
    val days = date2 - date1
    println(days)                     //14

サンプル中でも触れていますが、 +=と言う書き方は、varで宣言されたクラスでは使えないようです。 これは、リストへの追加と区別できないからだとか? kotlinでは、計算で+=と言う代入演算式での書き方ではなく、 二項演算式で書くのが混乱を避ける上では良いのかもしれません。

独自の日付クラス

kotlinで日付を扱おうと検索したところ、 LocalDateを使うとのことでしたが、 これを使うと @RequiresApi(Build.VERSION_CODES.O) と言うアノテーションを入れろと言われました。

調べてみると、Android 8.0 Oreo (API Level 2.6)以降でしか動かないことの宣言だと言うではありませんか。

いや、私の持ってる古いタブレットは、未だAndroid5.0なのですぅ(/_;)4949

と言うわけで、条件分岐させるほどのものでもないので、 Calendarクラスを使って簡単なものを作ってみました。

わざわざ独自クラスを作る必要もないかとは思いましたが、 今後日付型を見直す必要ができた時に、 このクラスだけ見直せばいいように。

class MyDate(
    zone: TimeZone = TimeZone.getDefault(),
    aLocale: Locale = Locale.getDefault()
) {
    ////////////////////////////////////////////////////////////////
    // Data
    ////////////////////////////////////////////////////////////////
    private val date : Calendar

    ////////////////////////////////////////////////////////////////
    // Property
    ////////////////////////////////////////////////////////////////
    var year : Int
        get() = date.get(Calendar.YEAR)
        set(value) {
            date.set(Calendar.YEAR, value)
        }
    var month : Int
        get() = date.get(Calendar.MONTH) + 1
        set(value) {
            date.set(Calendar.MONTH, value - 1)
        }
    var day : Int
        get() = date.get(Calendar.DAY_OF_MONTH)
        set(value) {
            date.set(Calendar.DAY_OF_MONTH, value)
        }

    ////////////////////////////////////////////////////////////////
    // Method
    ////////////////////////////////////////////////////////////////
    fun addDay(p1: Int) {
        date.add(Calendar.DAY_OF_MONTH, p1)
    }

    ////////////////////////////////////////////////////////////////
    // Initialize
    ////////////////////////////////////////////////////////////////
    init{
        date = Calendar.getInstance(zone, aLocale)
        date.set(Calendar.HOUR, 0)
        date.set(Calendar.MINUTE, 0)
        date.set(Calendar.SECOND, 0)
        date.set(Calendar.MILLISECOND, 0)
    }

    constructor(year: Int, month: Int, day: Int) : this() {
        this.year = year
        this.month = month
        this.day = day
    }
}

Calenderクラスを継承せずに中に持たせている(移譲=コンポジション)のは、 抽象クラスのため、 継承すると色々と書かなければならなくなるから。

継承していない方が中身入れ替える時も、 条件分岐させる必要が出た時も影響が小さいでしょうし。

さて、これは転ばぬ先の杖かはたまた下手な考えか...(^^;

initブロックは一番下に書く

kotlin流の書き方では、 プログラムでクラスを初期化する際には、 initブロックに書くようです。

ただ、 このinitブロックの実行タイミングが変数の初期化と同様書かれた順であることから、 変数の宣言より上に書かれていると、 初期化される前に参照することになりコケる原因になるようです。 (直接そのような順でコードを書くとIDEが警告を出してくれますが、  関数経由ではコンパイルできてしまうようです)

なので、initブロックはできるだけ下の方に書いた方が、 無用なトラブルを避けられるでしょう、と言うお話です(^^;

また、initブロックだけでなく、 companion objectの実行タイミングも同じ感じなので、 そちらはクラスの先頭に書くのが良いように思います。

class Example {
    companion object {
        ・・・・
    }

    ・・・・

    init {
        ・・・・
    }
}

なぜkotlinなのか?

初めまして、ことりん(仮名)です。
開発の備忘録として、blogを作ってみました。

何を書くか深くは考えていませんが、
プログラミングしていると色々と感じることが出てくるので、
それらをつらつらと書いて行きたいと思います。

 

まず初めに、

 

なぜkotlinを使おうと思ったかと言うと、

久しぶりにAndroidで開発しようと思い、

Android Studioをアップデートしたら、

イチオシ的に薦められている感じがしたのと、

ネーミングが可愛かったのと、

今まで開発で使っていたInteliJ IDEAを開発したJetBeans製だったことと、

Javaの冗長性をある程度隠蔽してくれるらしかったからです。
(kotlinの特徴ほぼ全部?(^^;)

 

プログラミング経験はそれなりにありますが、

kotlinは初めてなので、

色々勘違いなことも書くかもしれませんが、

コメント欄は開けておきますので、

やさしくご指導くださいねm(__)m