00. 目次
-
何らかの言語修得者用 差分で覚えるKotlin その一
変数, 分岐, ループ, 関数等のまとめ。オブジェクト指向の知識不要 -
何らかの言語修得者用 差分で覚えるKotlin その二
現在のページ。クラスとインターフェースのまとめ。オブジェクト指向の知識が必要 -
何らかの言語修得者用 差分で覚えるKotlin その三
ジェネリクスと、配列やコレクションのまとめ。オブジェクト指向の知識が必要
初見の方は"差分で覚えるKotlin その一"の「はじめに」に目を通して頂けると、何故にこんなざっくりした説明なのかお分かり頂けるかと思います。
# 09. クラス ## Kotlinのクラスにあるもの,ないもの
用語 | 存在の有無 |
---|---|
プロパティ変数(メンバ変数) | ○ |
メソッド | ○ |
コンストラクタ | ○ |
デストラクタ | × |
アクセス修飾子 | ○ |
継承クラス | ○ |
クラスの多重継承 | × |
抽象クラス | ○ |
メソッドのオーバーライド | ○ |
メソッドのオーバーロード | ○ |
インターフェース | ○ |
演算子のオーバーロード | ○ |
クラスの特徴的な(?)機能
そんな珍しい機能じゃねーよ!という場合はご指摘ください
- セカンダリコンストラクタ(=コンストラクタの多重定義だがこう呼ばれている)
- プライマリコンストラクタの引数はプロパティ変数として扱うことができる
- バッキングフィールド
- アクセサ
- データクラス
- Enum(列挙型)クラス
- クラスの入れ子
- sealedクラス
- 拡張関数
- 委譲プロパティ
- 委譲クラス
クラスの定義
[アクセス修飾子等] class クラス名 [constructor](コンストラクタの引数){プロパティやメソッド}
- 上記構文の[ ]で囲った部分は省略可
- コンストラクタの引数が無い場合は( )も省略可
アクセス修飾子一覧
種類 | 範囲 |
---|---|
省略(未指定) | 全てのクラスからアクセス可能 |
public | 全てのクラスからアクセス可能 |
protected | 現在のクラスとサブクラスからのみアクセス可能 |
private | 現在のクラスからのみアクセス可能 |
internal | 同じモジュール内のクラスからのみアクセス可能 |
// 3つのクラス全て同じ意味
public class Book constructor(){
}
class Book(){
}
class Book{}
コンストラクタ
- Kotlinではクラスの定義がコンストラクタも兼ねる
- 複数のコンストラクタを定義できるが、クラスの定義部分をプライマリコンストラクタ、それ以外をセカンダリコンストラクタと呼ぶ
- プライマリコンストラクタで行いたい処理があれば、
init
メソッドを定義しその中で行う - プライマリコンストラクタの引数に
var
やval
をつけるとプロパティ変数として扱われる
/* このBookクラスはプロパティ変数無し */
class Book(title: String, totalnumber: Int){ // この行がクラス宣言兼プライマリコンストラクタ
init{ // クラスが実体化した時呼び出される
println("本のタイトル:${title} 総ページ数:${totalnumber}")
}
}
/* 下記二つのクラスは同じ意味 */
// 引数titleとtotalnumberはプロパティ変数として扱われる
class Book(var title: String = "", var totalnumber: Int = 0){
}
class Book(){
// プロパティ変数 publicやprivateもつけられる
var title: String = ""
var totalnumber: Int = 0
}
セカンダリコンストラクタ
constructor (コンストラクタの引数) :this(他コンストラクタをコールするための引数){初期化処理}
- プライマリコンストラクタと違い
constructor
は省略不可 - セカンダリコンストラクタは複数定義可
- セカンダリコンストラクタ2→セカンダリコンストラクタ1→プライマリコンストラクタといった呼び出しも可能。ただし最後は必ずプライマリコンストラクタを呼び出さなければならない
- セカンダリコンストラクタの初期化処理は、セカンダリコンストラクタ直後の
{}
内に記述する
class Book(var title: String, var totalnumber: Int){ // プライマリコンストラクタ
init{
println("プライマリコンストラクタの初期化処理ですよ")
}
// セカンダリコンストラクタ1
constructor (title: String) :this(title, 0){
println("セカンダリコンストラクタ1の初期化処理ですよ")
}
// セカンダリコンストラクタ2
constructor (totalnumber: Int) :this("タイトル不明", totalnumber){
println("セカンダリコンストラクタ2の初期化処理ですよ")
}
}
/*
* 下記処理の出力結果は
* >プライマリコンストラクタの初期化処理ですよ
* >セカンダリコンストラクタ2の初期化処理ですよ
* >本のタイトル:タイトル不明 総ページ数:52
* となる。プライマリコンストラクタの初期化処理の方が先に呼び出される
*/
fun main() {
val book = Book(52)
println("本のタイトル:${book.title} 総ページ数:${book.totalnumber}")
}
プロパティ変数
- プロパティ変数はプライマリコンストラクタの引数、もしくはclassの
{}
内に定義する - アクセス修飾子有り
- 初期値を設定すれば型宣言は省略可
- 参照する時は
クラスのインスタンス.プロパティ変数名
と書く
アクセサとバッキングフィールド
アクセサ
- プロパティ変数の参照, 設定時はアクセサというメソッドが呼び出される
- 参照時は
get
, 設定時はset
という名前のアクセサになる - アクセサは省略可(省略してもプロパティ変数の参照, 設定は可能)
- アクセサを定義する場合は、該当のプロパティ変数の直後にメソッドを記述する
- 実体の無いプロパティ変数へのアクセサも作成可(詳しくは下記コード参照)
バッキングフィールド
- アクセサメソッド内部でプロパティ変数にアクセスするための代替(?)変数
-
field
と書く -
get
,set
メソッド内からプロパティ変数に直接アクセスは出来ない模様
(コンパイルは通るが、実行時エラーが発生した)
class Book(booktitle: String, booktotalnumber: Int){
var title: String = ""
// プロパティ変数の参照
get(){
if(field == "")
return "タイトル不明"
else
return field // バッキングフィールドを返す
}
var totalnumber: Int = 0
// プロパティ変数の設定
set(value){ // valueという引数名は慣習。変更可
if(value < 0){
throw NumberFormatException()
}
field = value // バッキングフィールドに値を設定
}
// 実体の無いプロパティ変数
val bookdetail: String
get() = "${title} , ${totalnumber}" // 実体の無いプロパティ変数のアクセサ
init{
title = booktitle
totalnumber = booktotalnumber
}
}
fun main() {
var book = Book("",0)
println(book.title) // タイトル不明と出力
book.title = "豆本"
println(book.bookdetail) // 豆本 , 0と出力
}
クラスの継承
- 親(基底,スーパー)クラスの定義に
open
をつける - 派生(子,サブ)クラスの定義の後ろに
:親クラス()
をつける
public open class NoteBook(){} //親クラス
class AddressBook(): NoteBook(){} //派生クラス
抽象クラス
- 抽象クラスの定義に
abstract
をつける - 継承クラスの定義の後ろに
:抽象クラス()
をつける
public abstract class NoteBook(){ //抽象クラス
abstract fun getPage(index: Int)
}
class AddressBook(): NoteBook(){ //継承クラス
final override fun getPage(index: Int){}
}
クラスのメソッド
- クラスの
{}
内に処理を記述する - メソッド名の前に
fun
をつける - アクセス修飾子有り、省略可。省略した場合のデフォルト値及びアクセス範囲はクラスと同じ
- 抽象メソッドを定義する場合は
abstract
をつける - メソッドをオーバーライドする場合は
override
をつける - 派生クラスでメソッドをオーバーライドさせたくない場合は
final
をつける
public abstract class NoteBook(){
abstract public fun getPage(index: Int) // 抽象メソッド
}
class AddressBook(): NoteBook(){
final override public fun getPage(index: Int){} // オーバーライドしたメソッド
}
# 10. インターフェース ## インターフェースの定義
- 構文は、
interface インターフェース名{メソッド定義}
- インターフェースを実装するクラス側の定義は以下の通り。[ ]内は省略可
class クラス名(): [親クラス(),] インターフェース名{}
- インターフェースの継承も可能
interface インターフェース名: 親インターフェース名[,複数指定可]
- アクセス修飾子は
public
のみ指定可
interface Operation { // インターフェースの定義
fun play()
fun stop()
fun pause()
}
class DvdPlayer(): Operation{ // Operationインターフェースを実装したDvdPlayerクラスの定義
override fun play(){}
override fun stop(){}
override fun pause(){}
}
# 11. 特殊なクラス ## データクラス
- クラス宣言の際に
class
の前にdata
をつけることによって、データを管理するクラスとして認識される - データクラスは
abstract
,open
等のキーワードは指定不可 - データクラスのメリットは
==
演算子やtoString
メソッドの挙動の違い等色々ある
詳しくは下記コード参照
データクラスとして定義しない場合、下記コードのaとbの比較はオブジェクトの比較となり、結果異なるものと判断される。
/*
* データクラスとして定義しない場合
* "a と b は異なる"と出力される
*/
class Book(var title: String)
fun main() {
val a: Book = Book("電話帳")
val b: Book = Book("電話帳")
if(a == b)
println("a と b は同じ")
else
println("a と b は異なる")
}
データクラスとして定義した場合、下記コードのaとbの比較はプライマリコンストラクタで定義されたプロパティ変数の比較となり、結果同じものと判断される。
/*
* データクラスとして定義した場合
* "a と b は同じ"と出力される
*/
data class Book(var title: String)
fun main() {
val a: Book = Book("電話帳")
val b: Book = Book("電話帳")
if(a == b)
println("a と b は同じ")
else
println("a と b は異なる")
}
toString
メソッドの出力内容も変わる
class Book(var title: String)
data class DataBook(var title: String)
fun main() {
val a: Book = Book("電話帳")
val b: DataBook = DataBook("電話帳")
println(a.toString()) // Book@28a418fcと出力
println(b.toString()) // DataBook(title=電話帳)と出力
}
Enum(列挙型)クラス
- 月や曜日, トランプのマークなど特定の値の集合をEnum(列挙型)クラスとして表現できる
- 構文は
enum class クラス名 {値, 値,…}
- クラスなので、プロパティ変数やメソッドも一緒に定義可
/* トランプのマーク列挙型クラス */
enum class Suit(){
HEART, DIAMOND, SPADE, CLUB
}
/* プロパティ変数付 */
enum class Suit(val color: String){
HEART("red"), DIAMOND("red"), SPADE("black"), CLUB("black")
}
fun main() {
println(Suit.HEART.color) // redと表示
}
/* ループも出来る */
enum class Suit(){
HEART, DIAMOND, SPADE, CLUB
}
fun main() {
for(i: Suit in Suit.values()){
println(i) // HEART DIAMOND SPADE CLUBと表示
}
}
クラスの入れ子
- クラスの内部にクラスを定義できる(ネストクラス, クラスの入れ子)
- 内部クラスから外部クラスへのアクセスは通常不可
- 内部クラスに
inner
修飾子をつけると外部クラスへアクセス可能になる - 外部クラスへの参照は
this@外部クラス名.プロパティ変数
と書く
class Parent(val name: String){ // 外部クラス
inner class Child(){ // 内部クラス
init{
println(this@Parent.name)
}
}
}
sealedクラス
- クラスに
sealed
修飾子をつけることで、継承可能なサブクラスを制限できる - 同一ファイル内のクラスは継承可
これ、わかりやすい例が思いつかない…
sealed class Fruit(){} // sealedクラス
// 以下クラスが同一ファイル内ならOK、異なるファイルだとNG
class Apple(): Fruit()
class Orange(): Fruit()
拡張関数
- 既存クラスを継承せず、メソッドだけを追加することができる(拡張関数)
- 構文は
fun 拡張対象クラス.メソッド名(引数){処理}
/*
* Array(配列)クラスに、配列をCSV形式の文字列に変換するメソッドを追加
* 単純に区切り文字で配列を繋げるだけなら、joinToStringというメソッドが用意されています
*/
fun Array<String>.ArrayToCSV(separator: String): String {
/* ~ 色々処理 ~ */
return "配列をCSV形式にフォーマットした文字列を返却"
}
委譲クラス
- インターフェースの実装を他クラスに委譲することが出来る
- 構文は
class クラス名(): インターフェース by 移譲先クラスのオブジェクト{}
- クラスの継承に比べ、必要なメソッドのみオーバーライドすれば済む利点がある
以下はOparationインターフェースを実装したDefaultPlayerクラスの定義と、DefaultPlayerクラスに処理を委譲した二つのクラスの例。
/* オーディオプレイヤーのインターフェース定義 */
interface Operation {
fun powerOn()
fun powerOff()
fun play()
fun stop()
fun next()
fun previous()
}
/* Operationインターフェースを実装した基本のオーディオプレイヤークラス */
class DefaultPlayer: Operation{
override fun powerOn(){} // 電源LED 点灯
override fun powerOff(){} // 電源LED 消灯
override fun play(){} // 再生
override fun stop(){} // 停止
override fun next(){} // 次の曲
override fun previous(){} // 前の曲
}
/*
* MP3プレイヤークラス
* 実装は全部DefaultPlayerクラス任せ
*/
class Mp3Player(val defalut: DefaultPlayer) : Operation by defalut {}
// 昔何でもウォークマンと呼んでいたら、S○NYおんりーだと指摘された記憶…
/*
* Operationインターフェースを実装したワイヤレスヘッドホンクラス
* ヘッドホン一体型プレイヤーとお考えください(S○NYのアレ)
* ほとんどの機能はDefaultPlayerクラスに丸投げ
* powerOn, powerOffメソッドだけオーバーライド
*/
class WirelessHeadphone(val defalut: DefaultPlayer) : Operation by defalut {
override fun powerOn() {
// kotlinにbeep関数は無いと思われるので脳内変換してね!
beep() // ヘッドホン装着時は電源LEDが見えないので、音を鳴らして電源ONをお知らせ
defalut.powerOn()
}
override fun powerOff() {
// kotlinにbeep関数は無いと思われるので脳内変換してね!
beep() // ヘッドホン装着時は電源LEDが見えないので、音を鳴らして電源OFFをお知らせ
defalut.powerOff()
}
}