サンプルで学ぶ!Kotlin速習ツアー(後編)

Kotlin

こんにちは、ECF Techブログ
担当 Michiharu.Tです。

今回はAndroidアプリ開発にも使用されているプログラミング言語、速習Kotlinの後編です。前編は下記よりご覧頂けます。Kotlinの実行環境についても下記をご覧ください。

サンプルで学ぶ!Kotlin速習ツアー(前編)
こんにちは、ECF Techブログ 担当 Michiharu.Tです。 今回はAndroidアプリ開発にも使用されているプログラミング言語、Kotlinを速習してみたいと思います。 対象読者 本記事は次のような読者を想定しています。...

対象読者

本記事は次のような読者を想定しています。文法などについて丁寧な説明は割愛していますので、ご了承ください。

  • 何らかのプログラミング言語の学習経験がある方
  • Kotlinの使い方を2時間くらいでさくっと一通り使ってみたい。という方

クラス

クラスの定義

クラスを定義する基本的文法は次のようになります。

class クラス名{
    //ここに、プロパティやメソッドの定義
}

上記コメント部分に記述できるプロパティやメソッドの定義文法は次のようになります。
プロパティ

    val プロパティ名: 型 = 値;

※valの部分はvarでも可能

メソッド

fun メソッド名(引数...):戻り値{
    //何らかの処理
}

インスタンスの生成

クラスを作成したら、インスタンスを生成して使用します。インスタンス生成の基本はつぎのようになります。

val 変数名:型 = クラス名()

サンプル01

//クラスの定義
class Student {
    val name: String = "Taro"
}

//ここからmain
fun main() {
    //インスタンスの生成
    val student1: Student = Student()
    //プロパティの表示
    println(student1.name)
}

実行結果

Taro

アクセサメソッド

プロパティはアクセサメソッドを持つことができます。基本形は次のとおりで、プロパティ定義の下に書きます。
ゲッター

get() = 返す値

セッター

set(value){
    //何らかの処理
}

アクセサメソッド内ではプロパティ値をfieldという表記で表すことができます。

サンプル02

class Student {
    var name: String = ""
        //ゲッター(すべて大文字にして返す)
        get() = field.toUpperCase()
    var age: Int = 0
        //セッター
        set(value) {
            field = if (value < 0) 0 else value
        }
}

fun main() {
    val taro: Student = Student()
    taro.name = "Yamada Taro"
    taro.age = 10
    println("名前:${taro.name} 年齢:${taro.age}")
    val hanako: Student = Student()
    hanako.name = "Yamada Hanako"
    hanako.age = -10
    println("名前:${hanako.name} 年齢:${hanako.age}")
}

実行結果

名前:YAMADA TARO 年齢:10
名前:YAMADA HANAKO 年齢:0

ポイント

  • taro.nameを表示すると、すべて大文字で表示されています。nameプロパティのゲッターが、すべて大文字で返すように定義されているためです。
  • hanako.age = -10のように、ageプロパティに負の値を入れようとすると0が設定されます。ageプロパティに定義したセッターの働きです。

コンストラクタの利用

Kotlinではコンストラクタは、大きく「プライマリコンストラクタ」と「セカンダリコンストラクタ」と区別されます。

プライマリコンストラクタ

クラス定義と共に定義されるコンストラクタです。定義文法は次のようになります。

class クラス名(引数...){
    //プロパティやメソッドの定義
}

引数部分をvalvarで書くと、プロパティの宣言を兼ねた表記となります。

サンプル03

//クラスの定義(コンストラクタつき)
class Student(val name: String) {
    //コンストラクタ引数は、プロパティ定義を兼ねている
    //ため、プロパティ定義不要
}

fun main() {
    //インスタンスの生成(引数あり)
    val student1: Student = Student("Hanako")
    //プロパティの表示
    println(student1.name)
}

実行結果

Hanako

コンストラクタの色々な使い方

サンプル04

//クラスの定義(コンストラクタつき)
class Product constructor(
        val name: String,
        val price: Int,
        val category: String = "未分類"
) {
    //セカンダリコンストラクタ
    constructor(name: String) : this(name, 100, "100円均一") {
        println("セカンダリコンストラクタを呼び出し")
    }

    //プライマリコンストラクタは処理記述をinitで行なう
    init {
        println("${name}(${price}円)[${category}]を登録しました。")
    }
}

fun main() {
    //プライマリコンストラクタ呼び出し
    val product1 = Product("りんご", 150, "食品")
    //プライマリコンストラクタ呼び出し(省略あり)
    val product2 = Product("パーティーグッズ", 300)
    //セカンダリコンストラクタ呼び出し
    val product3 = Product("B5ノート")
}

実行結果

りんご(150円)[食品]を登録しました。
パーティーグッズ(300円)[未分類]を登録しました。
B5ノート(100円)[100円均一]を登録しました。
セカンダリコンストラクタを呼び出し

ポイント

  • プライマリコンストラクタは処理を記述できません。処理を記述したい場合はinitブロックを使います。
  • コンストラクタの3つ目の引数は、デフォルト値(未分類)を設定しているため、呼び出し時に省略可能です。
  • クラス内部に定義されるコンストラクタはセカンダリコンストラクタと呼ばれています。
  • セカンダリコンストラクタはconstructorキーワードで記述します。何らかの形でプライマリコンストラクタが呼び出されなければいけません。結果的にinitブロックも必ず実行されます。

継承

基本的な継承

基本的な継承の記述は次のようになります。

class SubClass(...) : SuperClass(...)

:の後ろはスーパークラスのコンストラクタ呼び出しとなります。

サンプル05

open class Item(val name: String, val price: Int) {
    open fun display(): Unit {
        println("${name}(${price}円)")
    }
}

class Book(
    name: String,
    price: Int,
    val author: String
) : Item(name, price) {
    override fun display(): Unit {
        println("書籍名:${name}(${price}円)")
        println("著者名:${author}")
    }
}

fun main() {
    val item1 = Item("りんご", 150)
    val item2 = Book("Kotlin基礎", 1500, "山田太郎")
    item1.display()
    item2.display()
}

ポイント

  • Kotlinはデフォルトで継承禁止なので、継承されるスーパークラスにはopenを書く必要があります。
  • メソッドについても、オーバーライドされるメソッドはopenをつける必要があります。

サンプル06

//従業員
open class Employee(val firstName: String, val lastName: String)

//パートタイマー
class PartTimer(
        firstName: String,    //名
        lastName: String,    //姓
        val hourlyPay: Int        //時給
) : Employee(firstName, lastName) {
    //時給と勤務時間数から給与を計算
    fun calcPay(hour: Int): Int = hour * hourlyPay
}

//正社員
class FullTimer(
        firstName: String,    //名
        lastName: String,    //姓
        val monthlyPay: Int        //基本給
) : Employee(firstName, lastName) {
    val BASETIME: Int = 160    //月の勤務時間
    val OVERPAY: Int = 2000    //残業手当(1時間)

    //給与額 = 基本給 + 残業時間 * 残業手当(1時間)
    fun calcPay(hour: Int): Int = monthlyPay + ((hour - BASETIME) * OVERPAY);
}

fun main() {
    val taro = FullTimer("太郎", "山田", 200000)
    val hanako = PartTimer("花子", "山田", 1200)
    println(taro.calcPay(180))
    println(hanako.calcPay(90))
}

実行結果

240000
108000

Anyクラス

(自作を含む)すべてのクラスのスーパークラスです。equals()hashCode()toString()の3つのメソッドを持っています。

サンプル07

class Person(
    val lastName: String,
    val firstName: String
){
    override fun toString():String = lastName + firstName
}

fun main() {
    val taro: Person = Person("山田","太郎")
    //オーバーライドしたtoStringの戻り値が表示される
    println(taro)
}

実行結果

山田太郎

抽象クラス・抽象メソッド

抽象クラス・抽象メソッドを定義することができます。通常のクラスと違い、次の特徴があります。

  • 継承を前提としているため、openキーワードが不要となります。
  • インスタンス生成ができません。
  • 継承したクラスでは、抽象メソッドを実装しなければいけません。

サンプル07のプログラムを抽象クラスで表現したバージョンを示します。変更箇所のみコメントを追記しています。

サンプル08

//abstractを追加
abstract class Employee(val firstName: String, val lastName: String){
    //abstractを追加
    abstract fun calcPay(hour: Int):Int
}
class PartTimer(
        firstName: String,
        lastName: String,
        val hourlyPay: Int
) : Employee(firstName, lastName) {
    //overrideを追加
    override fun calcPay(hour: Int): Int = hour * hourlyPay
}

class FullTimer(
        firstName: String,
        lastName: String,
        val monthlyPay: Int
) : Employee(firstName, lastName) {
    val BASETIME: Int = 160
    val OVERPAY: Int = 2000

    //overrideを追加
    override fun calcPay(hour: Int): Int = monthlyPay + ((hour - BASETIME) * OVERPAY);
}

fun main() {
    val taro = FullTimer("太郎", "山田", 200000)
    val hanako = PartTimer("花子", "山田", 1200)
    println(taro.calcPay(180))
    println(hanako.calcPay(90))
}

実行結果

240000
108000

インタフェース

抽象クラスと類似していますが、クラスというよりはクラスに持たせるべき仕様。に近いものです。使用例を示します。

サンプル09

//注文後のオペレーション
interface Operation {
    fun cook()

    //デフォルト処理つき
    fun setting() {
        println("紙で包む");
    }
}

//ハンバーガーの注文処理
class HamburgerOperation : Operation {
    override fun cook() {
        println("ミートとレタスとチーズをパンではさむ");
    }
}

//ポテトの注文処理
class PotetoOperation : Operation {
    override fun cook() {
        println("ポテトを揚げて、塩をふる");
    }

    override fun setting() {
        println("紙のボックスにポテトを詰める");
    }
}

class Operator(
        val myope: Operation
) {
    //各注文に応じた処理を実行
    fun operation() {
        myope.cook()
        myope.setting()
    }
}

fun main() {
    println("ハンバーガー担当");
    val operator1: Operator = Operator(HamburgerOperation())
    operator1.operation()
    println("ポテト担当");
    val operator2: Operator = Operator(PotetoOperation())
    operator2.operation()
}
  • interface内に定義されたメソッドは自動的に抽象メソッドとなります。
  • 但し、メソッドをデフォルト実装しておくことも可能です。Operationインタフェースではsettingメソッドがデフォルト実装されています。
  • OperatorのプロパティはHamburgerOperation、PotetoOperationのいずれも代入できるようにインタフェースの型になっています。
  • HamburgerOperationはsettingメソッドをオーバーライドしていないため、デフォルト実装が実行されています。

null許容型

nullの代入が可能な変数やプロパティ値を宣言する場合は、次のようにします。

var 変数名:型? 

サンプル10

fun main(){
    var name:String? = null
    println(name)
}

null許容型の変数において、そのまま変数内のインスタンスが持つプロパティやメソッドを利用することはできません。たとえば次の文はエラーです。

サンプル11

fun main(){
    val name:String? = null
    //nameの持つ文字列の長さを表示
    println(name.length)
}

String型の持つlengthはその文字列の長さを表すプロパティですが、上の例では変数nameがnullの可能性があるため、このlengthの利用はエラーとなります。

以降、null許容型の変数からプロパティやメソッドを利用するための方法をいくつか紹介します。

スマートキャスト

null許容型の変数をif文などでnullチェックをすることで、nullでないことが確実な部分を判断しプロパティやメソッドを呼び出せるようにしてくれるしくみです。

サンプル12

fun main() {
    val name: String? = null
    //nameの持つ文字列の長さを表示
    if (name != null) {
        //このブロック内ではnullで無いことが保証されているため、
        //プロパティやメソッドの利用が可能
        println(name.length)
    } else {
        println("nameはnullです")
    }
}

実行結果

nameはnullです

安全呼び出し

?を変数名の後につけて、nullでない場合のみにプロパティやメソッドを利用する記述方法です。使用例を紹介します。

サンプル13

fun main() {
    val name: String? = null
    val len = name?.length
    println(len)
}

実行結果

null

?を付けた場合、その変数がnullだったらプロパティやメソッド呼び出しの結果はnullとなります。

エルビス演算子

null許容型変数がnullだった場合に、別の値を返してくれる演算子です。?と合わせて使用します。

サンプル14

fun main() {
    val name: String? = null
    //nameがnullだった場合は0を返す
    val len = name?.length ?: 0
    println(len)
}

実行結果

0

便利なクラス

データクラス(Data Class)

いわゆる「入れ物クラス」として便利なクラスです。Anyクラスのメソッドを自動作成したり、プロパティを番号で取り出せるメソッドが提供されたりします。使用例を紹介します。

サンプル15

data class Person(
        val name: String,    //名前
        val age: Int        //年齢
)

fun main() {
    val taro: Person = Person("山田太郎", 30)
    val taro2: Person = Person("山田太郎", 30)
    println(taro)    //1.
    //2.
    println(taro.hashCode())
    println(taro2.hashCode())
    //3.
    println(taro.component1());
    println(taro.component2());
}

実行結果

Person(name=山田太郎, age=30)
1297102971
1297102971
山田太郎
30

次の事が自動で実現しています。

  1. toStringメソッドがプロパティの値を表示するようにオーバーライドされている。
  2. hashCodeメソッドがプロパティの値に基づいて同一の値を返す。
  3. componentNメソッドによりプロパティ値を参照できる。※Nは1から始まる連続数値

シールドクラス(Sealed Class)

sealedキーワードをクラス定義に付加することにより、次の制限をかけることができます。

  • デフォルトで抽象クラスとなる
  • 同一ファイル内でのみ、継承を許可する。
  • シールドクラスを継承したサブクラスは、継承不可能なクラスとなる。

サンプル16

//図形
sealed class Figure {
    //面積を求める
    abstract fun getArea(): Int
}

//三角形
class Triangle(
        val bottom: Int,
        val height: Int
) : Figure() {
    override fun getArea(): Int {
        return bottom * height / 2
    }
}

//四角形
class Rectangle(
        val width: Int,
        val height: Int
) : Figure() {
    override fun getArea(): Int {
        return width * height
    }
}

fun main() {
    val triangle: Figure = Triangle(10, 20)
    val rectangle: Figure = Rectangle(10, 20)
    println(triangle.getArea())
    println(rectangle.getArea())
}

列挙型クラス(Enum Class)

いわゆる列挙型を表すクラスですが、プロパティを持ったりメソッドを持ったりすることができます。enumキーワードを使って作成できます。実行例を紹介します。

サンプル17

//トランプのマーク
enum class Suit {
    CLUB, DIAMOND, HEART, SPADE
}

//トランプのマーク
enum class Suit2(val japanese: String) {
    CLUB("クラブ"),
    DIAMOND("ダイヤ"),
    HEART("ハート"),
    SPADE("スペード")
}

//トランプのマーク
enum class Suit3(val japanese: String) {
    CLUB("クラブ") {
        override fun display(): String = "${japanese}は黒です"
    },
    DIAMOND("ダイヤ") {
        override fun display(): String = "${japanese}は赤です"
    },
    HEART("ハート") {
        override fun display(): String = "${japanese}は赤です"
    },
    SPADE("スペード") {
        override fun display(): String = "${japanese}は黒です"
    };

    abstract fun display(): String
}

fun main() {
    println(Suit.CLUB)
    //Enumクラスデフォルト保持のプロパティ(name)を表示
    println(Suit.CLUB.name)
    //Enumクラスデフォルト保持のプロパティ(ordinal)はインデックスを返す
    println(Suit.CLUB.ordinal)
    //独自のプロパティを持たせて表示
    println(Suit2.DIAMOND.japanese)
    //独自のメソッドを持たせて表示
    println(Suit3.SPADE.display())
}

実行結果

CLUB
CLUB
0
ダイヤ
スペードは黒です

トランプのマーク(スート)を表すクラスを3種類作成しています。

  • 列挙型クラスは自動的にnameordinalというプロパティを持ちます。
  • nameは定義名を持っており、ordinalは、定義順に割り振られた0から始まる連番を持っています。
  • 独自のプロパティや独自のメソッドを持たせることもできます。

コンパニオンオブジェクト(Companion Object)

クラス内部にオブジェクトを直接定義したい場合に用います。コンパニオンオブジェクト内にはプロパティやメソッドを記述できます。コンパニオンオブジェクト内のプロパティやメソッドは、インスタンスごとに保持されません。利用する際はクラス名.プロパティ名などとします。一般的に静的フィールドやメソッドと呼ばれる定義の代替として用いることができます。

サンプル18

class Product(
    val name: String,
    val price: Int
) {
    init {
        productCount++
    }

    companion object {
        //生成された製品の個数
        var productCount: Int = 0
    }
}

fun main() {
    val product1: Product = Product("りんご", 150)
    val product2: Product = Product("みかん", 200)
    println(Product.productCount)
}

実行結果

2

コンパニオンオブジェクト内にインスタンス数を数えるプロパティを定義し、コンストラクタで1加算する定義を書いています。2つのインスタンス生成後に参照すると「2」と表示されます。

コレクション

複数データを保持するオブジェクトはコレクションと呼ばれることがあります。代表的なコレクションであるリスト、セット、マップの例をご紹介します。

リスト

順序の概念を持った、いわゆる配列に近いデータ構造です。利用例を示します。

サンプル19

fun main(){
    //リスト内データの変更不可能(イミュータブル)なリストを作成
    val list1: List<String> = listOf<String>("りんご","みかん","バナナ")
    println(list1)
    //データ個数の取得
    println(list1.size)
    //[]で参照
    println(list1[1])
    //getで参照
    println(list1.get(2))
    //for文でリストを表示
    println("--for利用--")
    for ( data in list1 ){
        println(data)
    }
}

実行結果

[りんご, みかん, バナナ]
3
みかん
バナナ
--for利用--
りんご
みかん
バナナ

listOfで作成されるリストは内容を変更(追加削除も含め)できません。この性質はイミュータブルと呼ばれます。次は内容を変更できるリストを定義します。

サンプル20

fun main() {
    //リスト内データの変更不可能(イミュータブル)なリストを作成
    val list1: MutableList<String> = mutableListOf("りんご", "みかん", "バナナ")
    println(list1)
    //「ぶどう」をリストに追加
    list1.add("ぶどう")
    println(list1)
    //(0から数えて)1番目の値を削除
    list1.removeAt(1)
    println(list1)
}

実行結果

[りんご, みかん, バナナ]
[りんご, みかん, バナナ, ぶどう]
[りんご, バナナ, ぶどう]

セット

セットは重複を持てず、また順序の概念がないデータ構造です。本記事ではデータ構造の動作概要を示す目的で、すべてミュータブルなコレクションを利用します。利用例を示します。

サンプル21

fun main() {
    //セットの作成
    val set: MutableSet<String> = mutableSetOf("りんご", "みかん", "バナナ")
    println(set)
    //「ぶどう」をセットに追加
    set.add("ぶどう")
    println(set)
    //「バナナ」をセットに追加
    set.add("バナナ")
    println(set)
}

実行結果

[りんご, みかん, バナナ]
[りんご, みかん, バナナ, ぶどう]
[りんご, みかん, バナナ, ぶどう]

「バナナ」をセットに追加する処理があとますが、「バナナ」は既存しているので重複して登録されることはありません。

マップ

マップはキーと値のペアでデータを持つ構造です。利用例を示します。

サンプル22

fun main() {
    //マップの作成
    val map: MutableMap<String, Int> = mutableMapOf("りんご" to 5, "みかん" to 8, "バナナ" to 12)
    println(map)
    //マップにデータのペアを追加
    map.set("ぶどう", 1)
    println(map)
    //マップから「キーと値のペア」の集合を取り出し、for文で全件表示
    for (data in map.entries) {
        println("${data.key}の在庫は${data.value}個")
    }
}

実行結果

{りんご=5, みかん=8, バナナ=12}
{りんご=5, みかん=8, バナナ=12, ぶどう=1}
りんごの在庫は5個
みかんの在庫は8個
バナナの在庫は12個
ぶどうの在庫は1個
  • キーと値のペアでデータを保持します。
  • entriesプロパティを使うと、「キーと値のペア」の集合を取り出せるのでfor文で全件表示可能です。

おわりに

本日は以上とさせていただきます。Kotlin速習記事は本記事で終了となります。最後までご覧くださりありがとうございました。引き続き、困ったときのチートシートとして、お役立ていただければ幸いです。


合同会社イー・シー・エフでは、子ども向けプログラミングなどの教育講座を実施しています。プログラミング教室の案内や教育教材の情報、また関連するご相談・問い合わせにつきましては下記よりご確認ください。

ECFエデュケーション
キッズも大人も。沖縄からITのマナビを応援します
タイトルとURLをコピーしました