【Kotlin練習問題】演算子のオーバーロード:応用編

Kotlin

演算子のオーバーロードとは

演算子のオーバーロード:比較演算子で演算子をオーバーロードする方法について解説しました。

復習のために簡単に説明すると、

自分で作ったクラスなどで演算子をオーバーロードすることで、+、-、*、/といった演算子をそのクラスのインスタンスに直接使用することができるようになります。

もっと具体的に言うと、1+1はInt型同士の演算なので当たり前のように2という数値が計算できますが、自分で作ったclass MyAge(val age: Int)というクラスがあったとして、

fun main() {
    class MyAge(val age: Int)
    val age = MyAge(20)
    
    print("あなたは5年後" + (age + 5) + "歳です")
 //コンパイルエラー    
}

上のコードはMyAgeクラスで+演算子をオーバーロードしていないのにageに対して使ってしまっているので、コンパイルエラーがおきてしまいます。下のコードでは+演算子をオーバーロードしているので、25歳が出力されます。

fun main() {
    class MyAge(val age: Int)
    val age = MyAge(20)
    operator fun MyAge.plus(num: Int) =  //+演算子のオーバーロード
    	this.age + num
    
    print("あなたは5年後" + (age + 5) + "歳です")
    
}

二項演算子の対応リスト

a+bやa-bのように、二つの値を計算させる際に使われる演算子を二項演算子と呼びます。

Kotlinで二項演算子をオーバーライドするには以下の表で対応している関数をオーバーライドすることで、その演算子が独自クラスでも機能するようになります。

コンパイル時に使われる関数
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)a.mod(b) (非推奨)
a..ba.rangeTo(b)

上のコードにもあるように、+という演算子をオーバーロードするには実際にはplusという関数を実装するようになります。

問題

今回の問題は少し難易度高めです。

以下のコードを日付の計算をできるようにしましょう。dateに対して、年・週・日の加算に対応させましょう。

例えばdate + YEAR * 2 + WEEK * 3 + DAY * 15のようにコードを書けるようになります。

①はじめにMyDateクラスにTimeIntervalを引数として取るplus()拡張関数を追加してください。
拡張関数の実装にはDateUtil.ktに宣言されているMyDate.addTimeIntervals()関数を使用してください。

②その後、TimeIntervalを乗算(*)もできるようにしてください。新しいクラスを追加する必要があります。

問題コード:

import TimeInterval.*

data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)

// 日付に追加される期間を表すenumクラス
enum class TimeInterval { DAY, WEEK, YEAR }

operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = TODO()

fun task1(today: MyDate): MyDate {
    return today + YEAR + WEEK
}

fun task2(today: MyDate): MyDate {
    TODO("乗算に対応したらコメント解除してください") //return today + YEAR * 2 + WEEK * 3 + DAY * 5
}

DateUtil.kt:

import java.util.Calendar
​
/*
 * 与えられたインターバルを追加した日付を返すメソッド
 * インターバルは与えられた数の日数、週数、年数で指定されます
 * 使い方:
 * 'date.addTimeIntervals(TimeInterval.DAY, 4)'
 * 'date.addTimeIntervals(TimeInterval.WEEK, 3)'
 */
fun MyDate.addTimeIntervals(timeInterval: TimeInterval, amount: Int): MyDate {
    val c = Calendar.getInstance()
    c.set(year + if (timeInterval == TimeInterval.YEAR) amount else 0, month, dayOfMonth)
    var timeInMillis = c.timeInMillis
    val millisecondsInADay = 24 * 60 * 60 * 1000L
    timeInMillis += amount * when (timeInterval) {
        TimeInterval.DAY -> millisecondsInADay
        TimeInterval.WEEK -> 7 * millisecondsInADay
        TimeInterval.YEAR -> 0L
    }
    val result = Calendar.getInstance()
    result.timeInMillis = timeInMillis
    return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE))
}

ヒント

問題①で変更すべきコードは

operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = TODO()

ですが、これは単純にtimeIntervalとして与えられた値(日、週、年のいずれか)を一つ分足す処理です。DAYであれば一日、WEEKであれば一週、YEARであれば一年足されます。日付計算の複雑な処理はDateUtil.ktのaddTimeIntervalsに既に実装されているので、まずは、このメソッドの使い方をよく確認してください。

②の問題は以下の三つの手順で実装します。

  1. TimeIntervalの乗算に対応するために、まずはそのTimeIntervalが何個分なのかという値を保持する新クラスを作成する。
  2. TimeInterval.timesをオーバーライドし、乗算する値を含む先ほど作成したクラスのインスタンスを返すようにする。
  3. 問題①のMyDate.plusはTimeIntervalクラスしか引数にとらないので、自分で作成したクラスに対応したMyDate.plusを新しくオーバーライドする。
ヒント

答え

import TimeInterval.*

data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)

// 日付に追加される期間を表すenumクラス
enum class TimeInterval { DAY, WEEK, YEAR }

operator fun MyDate.plus(timeInterval: TimeInterval) =
        addTimeIntervals(timeInterval, 1)

class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int)

operator fun TimeInterval.times(number: Int) =
        RepeatedTimeInterval(this, number)

operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) =
        addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)

fun task1(today: MyDate): MyDate {
    return today + YEAR + WEEK
}

fun task2(today: MyDate): MyDate {
    return today + YEAR * 2 + WEEK * 3 + DAY * 5
}

答え

その他の問題はこちらからどうぞ。

おすすめ書籍

Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。

created by Rinker
¥2,640 (2021/06/17 22:46:14時点 楽天市場調べ-詳細)

実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。

プロフィール

プロフィール
コードラボJP

大学卒業後SEに就職、現在は退職しフリーランスとして活動中。
『初心者でも挫折せずに一人でプログラミングを学べる』をモットーに、コードラボJPを開設
お問い合わせ等はcodelabsjp@gmail.comまで

コードラボJPをフォローする
タイトルとURLをコピーしました