0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kotlinプログラミングを読んで気になった箇所メモ ④

Last updated at Posted at 2021-12-26

Kotlinプログラミングを読んで気になった箇所メモ ①
Kotlinプログラミングを読んで気になった箇所メモ ②
Kotlinプログラミングを読んで気になった箇所メモ ③
の続き
14章 ~ 19章 まで

残りの章もあるが、
あとはJavaとの相互運用とAndroid開発に関することなので、ここまで。

第14章 継承

14.2 サブクラスを作る

Javaとは違い、継承させることを明示する必要があるのが特徴。

//  サブクラスに継承させるために「open」キーワードを付与する
open class Vehicle(val name: String, val wheel: Int) {
  fun printVehicle() = println("この乗り物は: $name, 車輪: $wheel")

  // オーバーライドさせたいメソッドにも「open」キーワードが必要
  open fun horn() = println("...")
}

class Car(name: String, wheel: Int = 4) : Vehicle(name, wheel) {
  override fun horn() = println("プップー!")
}

open class Bike(name: String, wheel: Int = 2) : Vehicle(name, wheel) {
  // 「final」キーワードを加えることで、オーバーライド不可になる。
  final override fun horn() = println("チリンチリン♪")
}

fun main(args: Array<String>) {
  val car: Vehicle = Car("GT-R")
  car.printVehicle() // この乗り物は: GT-R, 車輪: 4
  car.horn() // プップー!
}

14.4 Kotlinにおける型階層

型キャスト

「as」キーワードを使うことで型キャストが可能になる。
だた、キャストできない場合はExceptionになる。

fun main(args: Array<String>) {
  val car: Vehicle = Car("GT-R")
  printVehicleName(car) // 乗り物名:  GT-R
  printVehicleName("トレノ") // java.lang.ClassCastException
}

fun printVehicleName(any: Any) {
  // any as Vehicle でVehicleに型キャストする
  println("乗り物名:  ${(any as Vehicle).name}")
}

回避方法としては「as?」か、スマートキャストを使う。

「as?」はキャストできない場合、nullが返却される。

fun main(args: Array<String>) {
  printVehicleName("トレノ") // None
}

fun printVehicleName(any: Any) {
  println("乗り物名:  ${(any as? Vehicle)?.name ?: "None"}") 
}

スマートキャストは、
型チェックで使われる「is」キーワードを使って判定式を使う。

fun printVehicleName(any: Any) {
  // 「is」で型チェックをしてスマートキャストを利用する
  if (any is Vehicle) {
    println("乗り物名:  ${any.name}")
  }
}

第15章 オブジェクト

15.1 objectキーワード

オブジェクト宣言(object declaration)
オブジェクト式(object expression)
コンパニオンオブジェクト(companion object)

オブジェクト宣言(object declaration)

クラスにobjectキーワードを付与すると シングルトン として定義される。
呼び出し時に初期化される。

fun main(args: Array<String>) {
  Sun.shine() // 呼び出し時に初期化
}

// オブジェクト宣言
object Sun {
  fun shine() {
    println("光を照らします")
  }
}
オブジェクト式(object expression)

無名クラス定義と初期化を同時に行う。
他のクラス同様、継承もできる。

fun main(args: Array<String>) {
  // オブジェクト式 定義された時点で初期化
  val obj = object {
    fun hello() = println("hello world")
  }
  obj.hello() // hello world
}
コンパニオンオブジェクト(companion object)

クラスの中に companion object を付与する。
Javaにおけるstaticメソッド的に呼び出せる。
初期化のタイミングは囲っているクラスが初期化された時。
囲っているクラスが何個作られても、コンパニオンオブジェクトのインスタンスは常に1個。

fun main(args: Array<String>) {
  println(FileUtil.existRootPATH()) // false
}

class FileUtil {
  // companion objectにも名前を付けることができ、
  // 「FileUtil.名前.existRootPath」のようにアクセス可能。だが省略できるのであまり意味はないと思われる。
  companion object {
    private val ROOT_PATH = "/Users/foo"
    fun existRootPATH(): Boolean {
      return File(ROOT_PATH).exists()
    }
  }
}

15.2 ネストしたクラス

クラスの中にクラスを定義できる。

fun main(args: Array<String>) {
  Computer.Cpu().turboBoost() // ターボブーストします
}

class Computer(val name: String = "iMac", var isBoost: Boolean = false) {
  // ネストしたクラス
  class Cpu() {
    fun turboBoost() {
      println("ターボブーストします")
    }
  }
}

外部からアクセスさせたくない場合はprivateを付与すること。
「inner」キーワードを付与すれば囲っているクラスのオブジェクト(プロパティ・メソッド)にアクセスできる。

fun main(args: Array<String>) {
  val computer = Computer()
  computer.boost() // ターボブーストします
  println("isBoost: ${computer.isBoost}") // isBoost: true
}

class Computer(val name: String = "iMac", var isBoost: Boolean = false) {
  // Cpuクラスを囲っているComputerクラスだけがアクセスできる
  fun boost() {
    Cpu().turboBoost()
  }
  // 「inner」キーワードを付与したprivate内部クラス
  private inner class Cpu() {
    fun turboBoost() {
      println("ターボブーストします")
      isBoost = true // 「inner」キーワードを付与しているのでComputerクラスのオブジェクトにアクセスできる
    }
  }
}

15.3 データクラス

データクラスとして定義すると、自動で以下のメソッドがデータクラス専用に実装される

  • toString()
  • equals()
  • hashCode()
  • [データクラスのみ]copy() 関数
  • [データクラスのみ]宣言した順番でプロパティに対応する componentN() 関数。分解宣言で利用される

自動生成されるメソッドはプライマリコンストラクタで定義されたプロパティのみ扱われる。
それ以外で定義した場合、無視されるので注意。

データクラス

// データクラス
data class Computer(val name: String, val price: Int, var isBoost: Boolean) {
  var memory = "8GB"
}


fun main(args: Array<String>) {
  val computer= Computer("iMac", 150000, false)

  println("## toString ##")
  // プライマリコンストラクタで宣言されたプロパティを出力する
  // それ以外で宣言されたプロパティは出力しないので注意
  println(computer.toString()) // Computer(name=iMac, price=150000, isBoost=false)
  println(computer) // computer.toString()と同じ

  println("## equals ##")
  // プライマリコンストラクタで宣言されたプロパティの値で比較する
  // それ以外で宣言されたプロパティは出力しないので注意
  println(computer.equals(Computer("iMac", 150000, false))) // true

  // equalsと同じ。こっちを使うのが一般的
  println(computer == Computer("iMac", 150000, false)) // true

  // 「===」はインスタンスが同じかをみる
  println(computer === Computer("iMac", 150000, false)) // false

  // プライマリコンストラクタ以外のプロパティは比較しないので注意!
  computer.memory = "16GB" // プライマリコンストラクタで定義していないプロパティを変更
  println(computer == Computer("iMac", 150000, false)) // true 


  println("## hashCode ##")
  // プライマリコンストラクタで宣言されたプロパティが同じであれば同じハッシュコード値になる
  println(computer.hashCode()) // -1210160458
  println(Computer("iMac", 150000, false).hashCode()) // -1210160458


  println("## copy ##")
  computer.memory = "16GB" // ※プライマリコンストラクタ以外のプロパティを変更してもコピーに反映されない

  // 変更したいプロパティがあれば指定することもできる
  val copy_computer= computer.copy(price = 200000)
  println(copy_computer) // Computer(name=iMac, price=200000, isBoost=false)
  println(copy_computer.memory) // 8GB プライマリコンストラクタ以外のプロパティ反映されていない
  

  println("## 分解宣言 ##")
  // componentN()関数に依存する
  val (name, price, isBoost )= computer
  println("name: $name, price: $price, isBoost: $isBoost") // name: iMac, price: 150000, isBoost: false
}

通常のクラス

// 通常のクラス
class Computer(val name: String, val price: Int, var isBoost: Boolean) {
  var memory = "8GB"
}

fun main(args: Array<String>) {
  val computer = Computer("iMac", 150000, false)

  println("## toString ##")
  println(computer.memory)
  println(computer.toString()) // メモリ空間のリファレンス src.Computer@58ceff1


  println("## equals ##")
  println(computer == Computer("iMac", 150000, false)) // false 通常のクラスは同じインスタンスかどうかをチェックする


  println("## hashCode ##")
  // インスタンスごとにハッシュコード値は異なる
  println(computer.hashCode()) // 93122545
  println(Computer("iMac", 150000, false).hashCode()) // 1239731077
}

15.5 演算子の多重定義

==equals
+plus のように演算子はある関数と関連している。
この演算子の処理を追加/オーバーライドすることができる。
「operator」キーワードを付与する。

以下の例はクラスに + 演算子の処理を追加する

data class ComboProduct(val food: String, val price: Int) {
  // operatorキーワードでplus(+)の処理を追加
  operator fun plus(other: ComboProduct): ComboProduct {
    return ComboProduct("${food}/${other.food}", price + other.price)
  }

  fun printProduct() = println("ご注文は「${food}」です。お会計は${price}yenです。")
}

fun main(args: Array<String>) {
  val product = ComboProduct("ハンバーガー", 200)
  // 「+」演算子を利用 
  (product + ComboProduct("ポテトS", 100)).printProduct() // ご注文は「ハンバーガー/ポテトS」です。お会計は300yenです。
}

第17章 ジェネリクス

17.1 ジェネリックス型を定義する

ジェネリックス型はどんな型でも受け付ける型
クラスで使うジェネリック型を<> で囲んで指定する。
以下の例ではプライマリコンストラクタでどんな型でも受け取れるBoxクラスを定義。

// ジェネリックス型を持つクラスBoxを定義
class Box<T>(val item: T)

class Cat(val color: String, val age: Int)

fun main(args: Array<String>) {
  val box1:Box<String> = Box("white")
  println(box1.item) // white
  // 自作したクラスも受け取れる
  val box2 = Box(Cat("black", 5)) // 他の型と同じく型推論してくれるので型定義は省略可能
  println("${box2.item.color}, ${box2.item.age}") // black, 5
}

17.3 複数のジェネリック型パラメータ

ジェネリック型は関数にも使用ができる。
また複数のジェネリック型も定義することができる。

以下の例では 仮引数「T」 -> 「R」に変換して返却する関数を定義。
String -> Cat に変換している。

class Box<T>(val item: T) {
  // ジェネリックス型Rを返却する変換関数 引数はFunction
  fun <R> convert(convertFunction: (T) -> R): R {
    return convertFunction(item)
  }
}

class Cat(val color: String, val age: Int)

fun main(args: Array<String>) {
  val box1:Box<String> = Box("white")

  // convertの最後の引数がFunctionなので () の外に定義できる
  val cat = box1.convert() {Cat(it, 5)}
  println("${cat.color}, ${cat.age}") // white, 5
}

17.5 varargとget

varargキーワードを付与することで可変数の値が入れることができる。

// varargキーワードを付与することによって可変数の引数を渡せる
class Box<T>(vararg val items: T) {
  // ジェネリックス型Rを返却する変換関数 引数はFunction
  fun <R> convert(index: Int, convertFunction: (T) -> R): R {
    return convertFunction(items[index])
  }
}

class Cat(val color: String, val age: Int)

fun main(args: Array<String>) {
  //  コンストラクタに可変数の値が入れられる
  val box1 = Box(
    "white",
    Cat("black", 5)
  )

  val cat1 = box1.convert(0) {
    Cat(it as String, 3)
  }
  println("${cat1.color}, ${cat1.age}") // white, 3

  val cat2 = box1.items[1] as? Cat
  println("${cat2?.color}, ${cat2?.age}") // black, 5
}

17.7 もっと知りたい? refieldキーワード

ジェネリック型(T)は型情報を実行時に利用することができず、型消去(type ensure)されてしまう。これはJavaと同じ。
以下の例のような型チェックなどには利用できず、コンパイルエラーになる。

fun <T> filterByType(list: List<Any>) : List<T> {
  val result = mutableListOf<T>()
  list.forEach {
    // ジェネリックの型引数に指定した型はコンパイル時にeraureによって削除される。
    // よって以下のような型判定で使うことはできない
    if (it is T) result.add(it) // ERROR: cannot check for instance of erased type: T
  }
  return result
}

fun main(args: Array<String>) {
  val list = listOf("Apple", 120, "Orange", 150)
  val intList = filterByType<Int>(list)

型情報を持たせたいなら、Kotlinではreifiedキーワードが提供されているので、それを使うと良い。
reifiedキーワードを付与するには、inline関数で定義すること。

// inline関数で定義し、型パラメータにreifiedを定義することで実行時まで型情報が残され、型判定ができるようになる
// List<*> は <out Any?> 型パラメータに関心がないときに使う
inline fun <reified T> filterByType(list: List<*>) : List<T> {
  val result = mutableListOf<T>()
  list.forEach {
    if (it is T) result.add(it)
  }
  return result
}

fun main(args: Array<String>) {
  val list = listOf("Apple", 120, "Orange", 150)

  val intList = filterByType<Int>(list)
  println(intList)  //=> [120, 150]

  val strList = filterByType<String>(list)
  println(strList)  //=> ["Apple", "Orange"]
}

第18章 エクステンション

エクステンションは型定義を直接変更しないで、型に機能を加える。
型のクラス定義を制御できない場合や、クラスにopenキーワードがなく継承できない場合などにエクステンションを用いるのに適している。

18.1 拡張関数を定義する

以下の例では、String型に拡張関数を定義する。
機能を追加する型(ここではString)をレシーバ型と呼ばれる。

// String に語尾を追加するエクステンションを追加
fun String.addEndOfWords(word: String = "ですわ") = this + word

fun main(args: Array<String>) {
  println("こんにちは".addEndOfWords("でち")) // こんにちはでち
}

18.3 拡張プロパティ

エクステンションは、プロパティを指定することもできる。
ただし、算術プロパティと同様バッキングフィールドを持たないので、値を算出するget()やset()を定義する必要がある。

val String.replacePoliteLang
  get() =  replace("でち", "です")

fun main(args: Array<String>) {
  println("こんにちはでち。今日も良いお日柄でちね。".replacePoliteLang) // こんにちはです。今日も良いお日柄ですね。
}

第19章 関数型プログラミングの基礎

19.3 シーケンス

List, Set, Map等は先行評価コレクション(eager collections)と呼ばれる。
これらの型は、インスタンス生成時に値すべてがコレクションに追加され、すぐにアクセス可能になる。

これとは別に遅延評価コレクション(lazy collections)というものがあり、必要なときにだけ値が生成される。
大きなコレクションを使う場合に良い性能が得られる。
Kotlinは、遅延評価コレクションの1つとして、シーケンス型(Sequence)を提供される。

例では、1000個の素数を求めるプログラム。
遅延評価のほうが早いことがわかる。

//  素数か判定する Intのエクステンション関数
fun Int.isPrime(): Boolean {
  (2 until this).map {
    if (this % it == 0) {
      return false
    }
  }
  return true
}

fun main(args: Array<String>) {
  // 実行速度をプロファイリングする
  val listInNanos = measureNanoTime {
    val resultList = (1..7919).toList()
      .filter { it.isPrime() }
      .take(1000) // 1000個まで取得。だが、シーケンスは5000と決まっているので1000にいかずに終わる可能性がある
    println(resultList.size) // 1000
  }
  val sequenceInNanos = measureNanoTime {
    generateSequence(3) { value ->
      value + 1
    }.filter { it.isPrime() }
      .take(1000)
  }
  println("List completed in $listInNanos ns") // List completed in 63916262 ns
  println("Sequence completed in $sequenceInNanos ns") // Sequence completed in 898968 ns
}

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?