カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit2 – 1 第1~3回:サブクラスを作る

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit2 – レイアウトUnit2-1 第1~3回 の内容をまとめたものです(翻訳ではありません)


Unit2 – 1 の 第1~3回

第1~3回 の内容は以下の通りです

  • 第1回と第2回は動画(英語)での紹介(観なくても大丈夫)
  • 第3回はKotlinでのサブクラスの考え方、作り方についてです

Kotlinでのクラスの基礎知識

クラスの説明でよく使う用語ですね

  • 今までに出てきた Activity, View, View groups はどれもKotlinではクラスの形で提供されています
  • Kotlinをつかって親子関係にあるクラスの作り方を説明します
  • 親のクラスを parent , superclass , base class(親クラス,スーパークラス,ベースクラス)って言います
  • 子のクラスを child , subclass(子クラス, サブクラス)って言います
  • 親クラスのプロパティやメソッドは子クラスに引き継がれます。これを Inheritance(継承)って言います
  • 親子関係で出来上がったクラスの構造を class hierarchy(クラスヒエラルキー, クラス階層)って言います
  • クラスの親子関係はツリー(ピラミッド)構造になっていて、頂上にいるすべてのクラスの元になるクラスを root , top-level class(ルート, トップレベルクラス)って言います

こんなことをやります

  • まず abstract class(抽象クラス)っていうクラスのひな型みたいなものを作ってみます
  • 次に abstract class を使ってサブクラスを作ります
  • 作成したサブクラスから、さらにサブクラスを作ってみます

abstract class(抽象クラス)を作る

まず Dwelling っていう名前の abstract class(抽象クラス)を作ってみますね

abstract class Dwelling(private var residents: Int) {
   
   abstract val buildingMaterial: String
   abstract val capacity: Int
   
   fun hasRoom(): Boolean {
       return residents < capacity
   }
}
  • クラス名の付けかたは PascalCase です(dwelling でなく Dwelling になります)
  • クラス名に続く()に記述されているのはコンストラクタ(プライマリーコンストラクタ)です。関数の引数みたいな感じに扱えます
    • residents には private が付いているのでクラスの外からは見えません(参照できない)
    • residents は var で宣言されているのでクラスの中でなら値を変更できます(変更の必要がなければ val にします)
    • private をつけないときは public になります(クラスの外からでも参照できる)
  • buildingMaterial, capacity はこの段階では初期化されていないので abstract をつけておきます(つけないとエラーになってしまいます。通常、プロパティは初期化しないといけないのです)
  • abstract をつけたプロパティは、サブクラスをつくるときに必ず値を与えないといけません(エラーになってしまいます)

abstract class(抽象クラス)からサブクラスを作る

こんどは abstract class を使って(継承して)クラスを作ってみます

class SquareCabin(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Wood"
    override val capacity = 6
}
  • abstract class からサブクラスをつくるときはコロン(:)を使って継承をあらわします。親クラス名のあとには必ず () をつけます
  • residents: Int のように、親クラスの residents を引き継ぐときにも型名を付けます
  • abstract で宣言してあったプロパティは override を使ってすべて初期化します(値を与える)
  • hasRoom() メソッドは abstract ではなく、そのまま継承するので記述する必要はありません(何も記述しなくても利用できます)

main() もつけるとこんなかんじです

fun main() {
    val squareCabin = SquareCabin(6)

    println("\nSquare Cabin\n============")
    println("Capacity: ${squareCabin.capacity}")
    println("Material: ${squareCabin.buildingMaterial}")
    println("Has room? ${squareCabin.hasRoom()}")
}

abstract class Dwelling(private var residents: Int) {
    abstract val buildingMaterial: String
    abstract val capacity: Int
       
    fun hasRoom(): Boolean {
       return residents < capacity
   }
}

class SquareCabin(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Wood"
    override val capacity = 6
}

with を使うと println のところが少しすっきりしますね

fun main() {
    val squareCabin = SquareCabin(6)
       
    with(squareCabin) {
        println("\nSquare Cabin\n============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
    }
}


abstract class Dwelling(private var residents: Int) {
    abstract val buildingMaterial: String
    abstract val capacity: Int
       
    fun hasRoom(): Boolean {
       return residents < capacity
   }
}

class SquareCabin(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Wood"
    override val capacity = 6
}

abstract クラスから作成したサブクラスから、さらにサブクラスを作ってみますね

fun main() {
    val squareCabin = SquareCabin(6)
    val roundHut = RoundHut(3)
    val roundTower = RoundTower(4)
       
    with(squareCabin) {
        println("\nSquare Cabin\n============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
    }
    
    with(roundHut) {
        println("\nRound Hut\n=========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
    }
    
    with(roundTower) {
        println("\nRound Tower\n==========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
    }
}


abstract class Dwelling(private var residents: Int) {
    abstract val buildingMaterial: String
    abstract val capacity: Int
       
    fun hasRoom(): Boolean {
       return residents < capacity
   }
}

class SquareCabin(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Wood"
    override val capacity = 6
}

open class RoundHut(residents: Int) : Dwelling(residents) {
   override val buildingMaterial = "Straw"
   override val capacity = 4
}

class RoundTower(residents: Int) : RoundHut(residents) {
    override val buildingMaterial = "Stone"
    override val capacity = 4
}
  • Dwelling(abstract class)から RoundHut(サブクラス)を作成しています
  • 作成した RoundHut クラス から RoundTower クラスを作成しています
  • RoundHut クラスには、class の前に open を付けて継承(サブクラスの作成)を許可します
  • RoundTower クラスの基本的な書き方は abstract class から作成するときと同じです。ただし、すべての abstract プロパティ・メソッドを override しないといけない abstract と違って、変更が必要なプロパティ・メソッドのみを override します

abstract なメソッドをオーバーライドして数値計算してみます

abstract class(Dwelling)に abstract メソッド(floorArea)を追加してみます

floorArea メソッドの中で小数の計算をするんですね

import kotlin.math.PI

fun main() {

    val squareCabin = SquareCabin(6, 50.0)
    val roundHut = RoundHut(3, 10.0)
    val roundTower = RoundTower(4, 15.5)

    with(squareCabin) {
        println("\nSquare Cabin\n============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }

    with(roundHut) {
        println("\nRound Hut\n=========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }

    with(roundTower) {
        println("\nRound Tower\n==========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }
 }


abstract class Dwelling(private var residents: Int) {

    abstract val buildingMaterial: String
    abstract val capacity: Int

    fun hasRoom(): Boolean {
        return residents < capacity
    }

    abstract fun floorArea(): Double
}


class SquareCabin(residents: Int, 
    val length: Double) : Dwelling(residents) {
   
    override val buildingMaterial = "Wood"
    override val capacity = 6

    override fun floorArea(): Double {
       return length * length
    }
}


open class RoundHut(val residents: Int, 
    val radius: Double) : Dwelling(residents) {
   
    override val buildingMaterial = "Straw"
    override val capacity = 4

    override fun floorArea(): Double {
        return PI * radius * radius
    }
}


class RoundTower(residents: Int, radius: Double, 
    val floors: Int = 2) : RoundHut(residents, radius) {

    override val buildingMaterial = "Stone"
    override val capacity = 4 * floors

    override fun floorArea(): Double {
        return super.floorArea() * floors
    }
}
  • クラス名に続けて()内に記述したプロパティ(プライマリコンストラクタ)には初期値の設定をしなくても大丈夫(インスタンスを生成するときに必ず設定する)。クラス内に記述したプロパティは初期値を入れて宣言しないとエラーになるので注意
  • Double型はKotlinで小数を扱うときの型です
  • 小数を表示するのに format() メソッドを使って、 println(“Floor area: %.2f”.format(floorArea())) のように記述することもできます

Dwelling クラスにさらにメソッドを追加します

このメソッドを追加するんですね

fun getRoom() {
    if (capacity > residents) {
        residents++
        println("You got a room!")
    } else {
        println("Sorry, at capacity and no rooms left.")
    }
}

getRoom() メソッドには residents プロパティが使われています。residents プロパティは private var で宣言されているのでクラス内で変更できます

getRoom() メソッドを追加してコメントも入れて仕上げると次のようになりますね

な、ながいかも…

/**
* Program that implements classes for different kinds of dwellings.
* Shows how to:
* Create class hierarchy, variables and functions with inheritance,
* abstract class, overriding, and private vs. public variables.
*/

import kotlin.math.PI
import kotlin.math.sqrt

fun main() {
   val squareCabin = SquareCabin(6, 50.0)
   val roundHut = RoundHut(3, 10.0)
   val roundTower = RoundTower(4, 15.5)

   with(squareCabin) {
       println("\nSquare Cabin\n============")
       println("Capacity: ${capacity}")
       println("Material: ${buildingMaterial}")
       println("Floor area: ${floorArea()}")
   }

   with(roundHut) {
       println("\nRound Hut\n=========")
       println("Material: ${buildingMaterial}")
       println("Capacity: ${capacity}")
       println("Floor area: ${floorArea()}")
       println("Has room? ${hasRoom()}")
       getRoom()
       println("Has room? ${hasRoom()}")
       getRoom()
       println("Carpet size: ${calculateMaxCarpetSize()}")
   }

   with(roundTower) {
       println("\nRound Tower\n==========")
       println("Material: ${buildingMaterial}")
       println("Capacity: ${capacity}")
       println("Floor area: ${floorArea()}")
       println("Carpet size: ${calculateMaxCarpetSize()}")
   }
}


/**
* Defines properties common to all dwellings.
* All dwellings have floorspace,
* but its calculation is specific to the subclass.
* Checking and getting a room are implemented here
* because they are the same for all Dwelling subclasses.
*
* @param residents Current number of residents
*/
abstract class Dwelling(private var residents: Int) {
   abstract val buildingMaterial: String
   abstract val capacity: Int

   /**
    * Calculates the floor area of the dwelling.
    * Implemented by subclasses where shape is determined.
    *
    * @return floor area
    */
   abstract fun floorArea(): Double

   /**
    * Checks whether there is room for another resident.
    *
    * @return true if room available, false otherwise
    */
   fun hasRoom(): Boolean {
       return residents < capacity
   }

   /**
    * Compares the capacity to the number of residents and
    * if capacity is larger than number of residents,
    * add resident by increasing the number of residents.
    * Print the result.
    */
   fun getRoom() {
       if (capacity > residents) {
           residents++
           println("You got a room!")
       } else {
           println("Sorry, at capacity and no rooms left.")
       }
   }

   }

/**
* A square cabin dwelling.
*
*  @param residents Current number of residents
*  @param length Length
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
   override val buildingMaterial = "Wood"
   override val capacity = 6

   /**
    * Calculates floor area for a square dwelling.
    *
    * @return floor area
    */
   override fun floorArea(): Double {
       return length * length
   }

}

/**
* Dwelling with a circular floorspace
*
* @param residents Current number of residents
* @param radius Radius
*/
open class RoundHut(
       val residents: Int, val radius: Double) : Dwelling(residents) {

   override val buildingMaterial = "Straw"
   override val capacity = 4

   /**
    * Calculates floor area for a round dwelling.
    *
    * @return floor area
    */
   override fun floorArea(): Double {
       return PI * radius * radius
   }

   /**
    *  Calculates the max length for a square carpet
    *  that fits the circular floor.
    *
    * @return length of carpet
    */
   fun calculateMaxCarpetSize(): Double {
       val diameter = 2 * radius
       return sqrt(diameter * diameter / 2)
   }

}

/**
* Round tower with multiple stories.
*
* @param residents Current number of residents
* @param radius Radius
* @param floors Number of stories
*/
class RoundTower(
       residents: Int,
       radius: Double,
       val floors: Int = 2) : RoundHut(residents, radius) {

   override val buildingMaterial = "Stone"

   // Capacity depends on the number of floors.
   override val capacity = floors * 4

   /**
    * Calculates the total floor area for a tower dwelling
    * with multiple stories.
    *
    * @return floor area
    */
   override fun floorArea(): Double {
       return super.floorArea() * floors
   }
}

Unit2 – 1 の 第3回 のまとめ

  • class hierarchy(クラス・ヒエラルキー:クラス階層)・・・class hierarchy はクラスの親子関係でできるツリー構造。親クラスから子クラスへ機能(プロパティとメソッド)が引き継がれていく様子をあらわしています
  • abstract class(抽象クラス)・・・クラスのひな型。機能の一部はサブクラスで実装するので、abstract class のままではインスタンスを生成できません
  • サブクラスでプロパティやメソッドをオーバーライドするときは override キーワードを付けます
  • 親クラスのプロパティやメソッドを参照するときは super キーワードを使います
  • サブクラスを持てるようにするには class の前に open を付けます
  • クラス内でのみ利用するプロパティには private を付けます
  • 同じオブジェクトインスタンスを何度も参照するようなときには with を使うと便利です
  • 数値計算には kotlin.math ライブラリを使います