(競プロメイン)C++erから見たKotlin勉強記録-3 class
はじめに
Kotlinのクラス関係について。
(競プロメイン)C++erから見たKotlin勉強記録-1 型、基本制御文 - Senの競技プログラミング備忘録
(競プロメイン)C++erから見たKotlin勉強記録-2 Array, List, 関数 - Senの競技プログラミング備忘録
(競プロメイン)C++erから見たKotlin勉強記録-4 Null許容型変数、スマートキャスト、総称型、パッケージ - Senの競技プログラミング備忘録
テスト環境
Try Kotlin
try.kotlinlang.org
paizaのKotlin環境
paiza.io
クラス
C++では
class CL{ private: int a = 114, b =514; public: //コンストラクタB CL(int x, int y){a = x, b = y;} //コンストラクタC CL(int x){a = x, b = x * 3;} void print(){ cout << a << " " << b << endl; } }; //main内では CL obj1();//デフォルトコンストラクタ CL obj2 = CL();//デフォルトコンストラクタ CL obj3(34);//コンストラクタB CL obj4 = CL(46, 31);//コンストラクタC //このように、=を使って右辺値で定めても、そのまま()でコンストラクタ呼び出してもよい。
Kotlinでは、同じようにコンストラクタをこう、複数設定して呼び分けたり、デフォルト値を設定したり、アクセス権限を指定したりできる。
class CL { private var a : Int = 114 private var b : Int = 514 //何も記述しない場合は、デフォルトでpublicになる。 //記述するのなら、public fun print()...になる。 fun print() : Unit{ println("a is ${a} and b is ${b}.") } } //プライマリーコンストラクタとデフォルト値定義をセットで書くのなら、このように書ける。 //この書き方だと、constructorを略して、calss CL_1 (private var a : int = ...){} のようにもかける。 class CL_1 constructor (private var a : Int = 114, private var b : Int = 514){ init { //値のセット、変数の宣言以外の作業をしたい場合、constructorのinitを使用する。 イニシャライザと呼ぶ println("Constructor's initializer is called!") println("You set a == ${a}, b == ${b}") } //セカンダリコンストラクタ constructorを明示するこのワードは必須 //引数には、varやvalがつかない! // constructor (x : Int){ a = x; b = x * 3 println("セカンダリコンストラクタで値をセットした。") //プライマリコンストラクタをセカンダリコンストラクタ内で呼ぶ場合、以下のようにthis()で書く。 /* this(x, x * 3) */ //この場所はセカンダリコンストラクタのイニシャライザでもある。 } /* //上のセカンダリコンストラクタはこうと書ける。 constructor (x : Int) : this(x, x * 3){ println("セカンダリコンストラクタで値をセットした。") } //この場合、呼び出しの順番は、 //セカンダリコンストラクタ-> //プライマリコンストラクタ-> //プライマリのinit-> //セカンダリのinit */ fun print() : Unit{ println("a is ${a} and b is ${b}.") } }
public, private, protectedはC++と同じ挙動をすると思う。
継承
クラス名の前にopenと書くと、継承できるクラスとなる。
デフォルトでは省略されているが、finalとなり継承できない。
open class Car (var name : String = "Prius", var distance : Int = 21){ open fun print() = println("${name} runs ${distance}km."); } //継承先のコンストラクタを指定したい場合、FlyingCar : Car("Ferrari", 466)のように書けばいい class FlyingCar : Car(){ fun addDis(_adddis : Int) : Unit{ if(_adddis >= 0) distance += _adddis else println("You add a wrong value to distance in ${name}") } //C++と同じように、基底クラスの関数をoverrideで上書きできる。 override fun print() = println("FlyingCar ${name} runs ${distance}km. Ofcause it can fly!"); } fun main(args: Array<String>){ var obj1 = Car(name = "Toyota Prius!") var obj2 = FlyingCar() obj1.print() obj2.print() obj2.addDis(19) obj2.print() }
出力
Toyota Prius! runs 21km. FlyingCar Prius runs 21km. Ofcause it can fly! FlyingCar Prius runs 40km. Ofcause it can fly!
多様性(ポリモーフィズム)
Humanクラスを継承したStudentクラスと、Workerクラスを考える。
次のように、書くことが可能。
open class Human(name : String = "yajusenpai"){ open fun printname() = println(name) } //StudentクラスとWorkerクラスはHumanを継承してる fun printStudent(m : Student) : Unit{ m.printname() } fun printWorker(m : Worker) : Unit{ m.printname() } //StudentもWorkerもHumanが基底クラスで、行われる機能もHumanクラスの中にあるものだけなので、次のように、基底クラスを型として受け取っていい。 fun printHuman(m : Human){ m.printname() } fun main(args: Array<String>){ obj_stu = Student() printHuman(obj_stu)//このようにできる }
抽象クラス
クラスの中には、基底クラスとして設計され、継承されることを前提としてるようなクラスが存在している。これらは、そのまま実体化することを想定していない。
オブジェクト指向の考えで、明確的にテンプレート用で、実体化できない!と強調するワードとして、abstractがある。
openは継承OKであり、abstractは継承必須。
abstract class Human(var name : String){ abstract fun printName() } class Student(var studentNum : Int = 21616676) : Human("Sen"){ override fun printName() : Unit { println("${name} has student ID ${studentNum}.") } } fun main(args : Array<String>){ var Me = Student() Me.printName() //var Mybody = Human() //abstractと指定されたクラスは上記のような実体化が不能 }
Anyクラス
Kotlinの内部実装の話だが、すべてのクラスはAnyクラスというのを継承してる。
Anyクラスには3つのメソッドが含まれていて、
open fun toString() : String open operator fun equals(other: Any?) : Boolean open fun hashCode() : Int
上から順に
- このクラスを文字列化した時の情報。overrideするとデバッグしやすくなる。(もちろん、ほかの用途もOK)
- 2つの生成されたインスタンスが同じもの(値が同じではなく、同じメモリ上を指してるの意味)かどうかを返す。==演算子のオーバーロードでもある。
- クラスの情報をhash化した時のhash値。
である。
これらは自前でoverrideしてもかまわないが、Kotlinのライブラリ等では
euqal()がtrueを返すときにのみ、hashCode()は同じ値を返す。逆もしかり。
overrideする際には、注意する必要がある。
データクラス
Anyクラスから来てるequal()は、データの一致を判定してるのではなく、インスタンスが同一(メモリ上の同じ場所にいるか?)を判定していると、いえる。
しかし、中身で比較をしたいときも実装上多々ある。方法としてはequal()をoverrideするというのが思い浮かぶが、それではhashCode()も適切にoverrideしなければならないので、難しく面倒である。
そんなお気持ちを察してくれたKotlinには、データクラスという「格納されてるデータで比較」できるクラスが用意されている。
以下のようにdata classとして宣言すれば、「格納されてるデータで比較」できる上に、toString()もより意味のあるデータを出力してくれる。
注意すべき点としては、dataとopen, abstractとは共存できない点と、なにかしらを継承したData Classではその継承元のデータは出力されないこと。(そこは自分でtoString()をoverrideするしかない)
open class Human(var name : String = "Sen") data class Student(var studentID : Int = 21616676, var GPA : Double = 3.14) : Human("Sen") class notDataClass(var a : Int = 3, var s : String = "something") fun main(args : Array<String>){ var obj1 = Human() var obj2 = Student() var obj3 = notDataClass() println(obj1.toString() + " " + obj2.toString() + " " + obj3.toString()) }
出力
Human@28a418fc Student(studentID=21616676, GPA=3.14) notDataClass@5305068a
インターフェイス
クラスHumanを継承したStudentクラスと、クラスAnimalを継承したTigerクラスを考える。
元は違うものとはいえ、共通の機能を持たせたい、という実装は多い。例えば、どちらにも、run()という関数を実装したいと。
C++だと多重継承は存在しているが、Javaとその流れをくむKotlinには多重継承はありません。(終)
そのため、Kotlinではインターフェイスという概念があり、複数の継承元が違うクラスたちに同一の機能を追加するときに使われる。
interface Running{ fun run(Who : String) = println("${Who} is running!") } abstract class Human(var name : String){ abstract fun printName() } class Student(var studentNum : Int = 21616676) : Human("Sen"), Running{ override fun printName() : Unit { println("${name} has student ID ${studentNum}.") } } abstract class Animal(var name : String){ abstract fun printName() } class Tiger(var location : String = "Jungle") : Animal("Steve"), Running{ override fun printName() = println("${name}, the tiger lives in ${location}.") } fun main(args : Array<String>){ var Me = Student() Me.printName() var Him = Tiger() Him.printName() Me.run(Me.name) Him.run(Him.name) }
run()が含まれてるインターフェイスをRunningと名付けて、StudentとTigerで使えるようにしている。
ここでは、run()はインターフェイス内で既に定義済みだが、もちろん抽象関数(C++でいうと関数テンプレート)で定義しても構わない。それなら継承?先でoverrideしないとCEになるけど。