Open11
速習Kotlin 読書メモ
Part 2: 基本構文
- Any型の変数は、途中で別の型を代入できる
実験コード
package com.example.kotlinlearning
fun main() {
var num: Any = 10
println(num)
println(num.javaClass)
println(num::class)
num = "aaa"
println(num)
println(num.javaClass)
println(num::class)
}
出力
10
class java.lang.Integer
class java.lang.Integer (Kotlin reflection is not available)
aaa
class java.lang.String
class java.lang.String (Kotlin reflection is not available)
- nullチェックの後はスマートキャストされる
実験コード
Part 3: 制御構文
-
ifは式として扱えるので、そのまま変数に代入できる。
- ただし式として扱うときはelseが必要(何かしら値を返すため)
- ただし式として扱うときはelseが必要(何かしら値を返すため)
-
when(switch文的な)も式として扱える
実験コード
fun main() {
val obj: Any = "aaa"
when(obj) {
is Int -> println("Int")
is String -> println("String")
}
val msg = when(obj) {
is Int -> "Int"
is String -> "String"
else -> "Unknown"
}
println(msg)
}
出力
String
String
- 入れ子になっているループで、外側のループを抜ける場合は @outerを利用する
実験コード(continue, break, break@outer)
- continue
fun main() {
for (i in 1..10) {
if (i % 3 == 0) continue
println(i)
}
}
出力
1
2
4
5
7
8
10
- 通常のbreak
fun main() {
for (i in 1..10) {
for (j in 1..10) {
if (i * j >= 30) break
print("${i * j} ")
}
println()
}
}
出力
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28
5 10 15 20 25
6 12 18 24
7 14 21 28
8 16 24
9 18 27
10 20
- outerの例
fun main() {
outer@ for (i in 1..10) {
for (j in 1..10) {
if (i * j >= 30) break@outer
print("${i * j} ")
}
println()
}
}
出力
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27
Part 4: 関数
高階関数
- 関数そのものを引数として受け渡す
-
::関数名
で関数への参照を引き渡す
::関数名の実験
fun main() {
val list = intArrayOf(2, 3)
println("=== list.forEach(::myPrint) ===")
// メソッド参照
list.forEach(::myPrint)
println("=== list.forEach { item -> myPrint(item) } ===")
// ラムダ式
list.forEach { item -> myPrint(item) }
// 引数が単一の場合はitで受け取る
println("=== list.forEach { myPrint(it) } ===")
list.forEach { myPrint(it) }
}
fun myPrint(item: Int) {
println("myPrint: $item")
}
出力
=== list.forEach(::myPrint) ===
myPrint: 2
myPrint: 3
=== list.forEach { item -> myPrint(item) } ===
myPrint: 2
myPrint: 3
=== list.forEach { myPrint(it) } ===
myPrint: 2
myPrint: 3
- ()の省略など
package com.example.kotlinlearning
fun main() {
val list = intArrayOf(2, 3)
// 最も省略しない形
list.forEach({ num -> println(num) })
// 最後の引数がラムダ式であれば、ラムダ式をカッコの外に出すことが可能
list.forEach() { num -> println(num) }
// 引数がラムダ式のみであれば、カッコを省略可能
list.forEach { num -> println(num) }
// 引数が1つの場合、itで省略可能
list.forEach { println(it) }
}
Part 5: オブジェクト指向構文
- プロパティ
- setterの設定値は、慣例的に引数名はvalueだが、何でも良い。
- fieldはバッキングフィールド
アクセサーを伴うプロパティ
fun main() {
val p = Person()
p.age = 20
p.show()
p.age = 17
p.show()
}
class Person() {
val isAdult
get() = age >= 18
var age = 18
set(value) {
if (value < 18) {
throw IllegalArgumentException("年齢は18歳以上にしてください")
}
field = value
}
fun show() {
println("年齢は${age}です")
println("成人ですか?: ${isAdult}")
}
}
出力
年齢は20です
成人ですか?: true
Exception in thread "main" java.lang.IllegalArgumentException: 年齢は18歳以上にしてください
プライマリコンストラクター
fun main() {
val p = Person("たろう", 20)
p.show()
}
class Person(name: String, age: Int) {
val name: String
val age: Int
// 初期化ブロック
init {
this.name = name
this.age = age
}
fun show() {
println("名前は${name}です。年齢は${age}です。")
}
}
出力
名前はたろうです。年齢は20です。
- initブロックを使わずにプロパティに渡すことも出来るが、
Property is explicitly assigned to parameter name, so it can be declared directly in the constructor
と言われる。
class Person(name: String, age: Int) {
val name: String = name
val age: Int = age
fun show() {
println("名前は${name}です。年齢は${age}です。")
}
}
- コンストラクタでプロパティを定義
class Person(val name: String, val age: Int) {
fun show() {
println("名前は${name}です。年齢は${age}です。")
}
}
セカンダリコンストラクター
fun main() {
val p = Person()
p.show()
}
class Person(private val name: String, private val age: Int) {
constructor(name: String) :this(name, 18)
constructor() :this("名無し")
fun show() {
println("名前は${name}です。年齢は${age}です。")
}
}
出力
名前は名無しです。年齢は18です。
プライマリコンストラクターの引数の既定値でもっとすっきり
fun main() {
val p = Person()
p.show()
}
class Person(private val name: String = "名無し", private val age: Int = 18) {
fun show() {
println("名前は${name}です。年齢は${age}です。")
}
}
出力
名前は名無しです。年齢は18です。
Part 6: 継承とインターフェイス
- Kotlinでは規定でクラスは継承できない。継承させたい場合は明示的に
open
修飾子を付与する- メソッドも同様
- メソッドをオーバーライドする際には、子のメソッドに
override
修飾子をつける(親にはopen
)
継承の例
package com.example.kotlinlearning
fun main() {
val b = BusinessPerson("山田", "開発部")
println(b.show())
}
open class Person(private val name: String = "名無し") {
open fun show(): String {
return "名前は${name}です。"
}
}
class BusinessPerson(name: String, val depart: String) : Person(name) {
override fun show(): String {
return "${super.show()} 部署は${depart}です。"
}
}
出力
名前は山田です。 部署は開発部です。
- openは、オーバーライド「してもよい」だったのに対して、abstractは「しなければならない」となる。
抽象クラス、メソッド
fun main() {
val t = Triangle(10.0, 5.0)
println("三角形の面積は${t.getArea()}です")
}
abstract class Figure(val width: Double, val height: Double) {
// てきとーにreturn 0.0をするのでは意味がない。なので実装を持たずに、abstractなメソッドとする
abstract fun getArea(): Double
}
class Triangle(width: Double, height: Double) : Figure(width, height) {
override fun getArea(): Double {
return width * height / 2
}
}
出力
三角形の面積は25.0です
インターフェイス
fun main() {
val t = Triangle(10.0, 5.0)
println("三角形の面積は${t.getArea()}です")
t.hello()
}
interface Figure {
// プロパティを定義可能(インターフェイスでは実装を持てないので、初期値は渡せない)
var name: String
// インターフェイスのデフォルト実装。プロパティを参照可能
fun hello() {
println("Hello, I'm $name.")
}
fun getArea(): Double
}
class Triangle(private val bottom: Double, private val height: Double) : Figure {
override var name: String = "Triangle"
override fun getArea(): Double {
return bottom * height / 2
}
}
出力
三角形の面積は25.0です
Hello, I'm Triangle.
- Kotlinでは基本スマートキャストだけ使うと思ったほうがよい
キャストの実験コード
fun main() {
val p: Person = BusinessPerson("山田", "開発部")
println(p.show())
if (p is BusinessPerson) {
p.work() // スマートキャスト
}
// 明示的なキャスト。失敗する可能性もあるので、Unsafe Castと呼ばれる。
(p as BusinessPerson).work()
}
open class Person(private val name: String = "名無し") {
open fun show(): String {
return "名前は${name}です。"
}
}
class BusinessPerson(name: String, val depart: String) : Person(name) {
override fun show(): String {
return "${super.show()} 部署は${depart}です。"
}
fun work() {
println("働いています。")
}
}
出力
名前は山田です。 部署は開発部です。
働いています。
働いています。
Part 7: 特殊なクラス(dataクラス)
データクラス
fun main() {
val person = Person("太郎", 20)
println(person)
}
data class Person(val name: String, val age: Int)
出力
Person(name=太郎, age=20)
- dataクラスでは、==演算子によって、プライマリコンストラクターで定義されたプロパティが等しいかを判定される。
dataクラスと通常のクラスの比較
fun main() {
val person1 = Person("太郎", 20)
person1.height = 170
val person2 = Person("太郎", 20)
person2.height = 150
println(person1 == person2) // true
val normalPerson1 = NormalPerson("太郎", 20)
val normalPerson2 = NormalPerson("太郎", 20)
println(normalPerson1 == normalPerson2) // false
}
data class Person(val name: String, val age: Int) {
var height: Int = 160
}
class NormalPerson(val name: String, val age: Int)
出力
true
false
分解代入
fun main() {
val person = Person("太郎", 20)
val (nameVal, ageVal) = person
println("名前: $nameVal, 年齢: $ageVal")
}
data class Person(val name: String, val age: Int) {
var height: Int = 160
}
出力
名前: 太郎, 年齢: 20
copyメソッド
- プライマリコンストラクターの値以外はコピーされない
fun main() {
val person = Person("太郎", 20)
person.height = 170
val personCopied = person.copy(age = 21)
println(person)
println(person.height)
println(personCopied)
println(personCopied.height)
}
data class Person(val name: String, val age: Int) {
var height: Int = 160
}
出力
Person(name=太郎, age=20)
170
Person(name=太郎, age=21)
160
Part 7: 特殊なクラス(オブジェクト宣言、
オブジェクト式
- 以下のコードは、オブジェクト式をSAM変換によってラムダ式に書き換え、さらにラムダ式の書き方を簡略化した結果である。
// object: parent { ..body.. }で、その場限りのクラスを定義することができる。これがオブジェクト式
// SAM(Single Abstract Method)インターフェースは、1つの抽象メソッドしか持たないインターフェイスのことで、
// View.OnClickListenerインターフェースもSAMに該当
btn.setOnClickListener(object: View.OnClickListner {
override fun onClick(view: View) {
Log.v("Kotlin Sample", "Clicked!!")
}
})
// SAM変換によって、ラムダ式で書くことができる。
btn.setOnClickListener({ view: View -> Log.v("Kotlin Sample", "Clicked!!") })
// ラムダ式の簡略化によってこのような記述になる
btn.setOnClickListener { Log.v("KotlinSample","Clicked!!") }
コンパニオンオブジェクト
- クラス内部でのオブジェクト宣言のこと。
- Kotlinにはstaticメンバーは存在しないが、コンパニオンオブジェクトを用いることでその代替ができる。
実験コード
fun main() {
val person = Person.getInstance("山田はなこ")
println(person.name)
}
class Person private constructor(var name: String) {
companion object {
fun getInstance(name: String): Person {
return Person(name)
}
}
}
出力
山田はなこ
- companion objectに名前をつけない場合はデフォルトの名前になる。Factoryと名前をつけると意図が明確になる。
- ただし、
Person2.Factory.getInstance()
とFactoryを挟むのは冗長と判断される。
- ただし、
- コンパニオンオブジェクトはクラスに1つ
Part 7: 特殊なクラス(Enumクラス、ジェネリック)
Enumクラス
fun main() {
val s = Season.FALL
println(s) // FALL
println(s.name) // FALL
println(s is Season) // true
println(s is Enum<Season>) // true
println(s.ordinal) // 2
}
enum class Season {
SPRING,
SUMMER,
FALL,
WINTER
}
- プロパティやメソッドをもたせることが出来る
- SUMMERなどの定義によってインスタンス化しているイメージ
- っメソッドを定義するためには列挙定数の末尾を
;
で終了し、メンバー定義との区切りを明確にする。
package com.example.kotlinlearning
fun main() {
val summer = Season.SUMMER
println(summer.value)
summer.sayValue()
summer.hello()
val fall = Season.FALL
println(fall.value)
fall.sayValue()
fall.hello()
}
enum class Season(val value: Int) {
SPRING(1),
SUMMER(2),
FALL(4) {
// オーバーライドできる
override fun hello() {
println("Hello, It's fall")
}
},
WINTER(8);
open fun hello() {
println("Hello")
}
// プロパティにアクセス可能
fun sayValue() {
println("Value is $value")
}
}
出力
2
Value is 2
Hello
4
Value is 4
Hello, It's fall
- abstractの場合はちゃんと実装しないとコンパイルエラー
ジェネリック
実験コード
- クラス名の後方に<T>のように型引数を定義する。
fun main() {
val myGeneric = MyGeneric("abc")
println(myGeneric.getProp()) // abc
myGeneric.value = "def"
// myGeneric.value = 123 // コンパイルエラー The integer literal does not conform to the expected type String
}
class MyGeneric<T>(var value: T) {
fun getProp(): T {
return value
}
}
- Tの型を制約する
fun main() {
val hoge = Hoge()
val myGenericHoge = MyGeneric(hoge)
val hogeChild = HogeChild()
val myGenericHogeChild = MyGeneric(hogeChild)
val notHoge = NotHoge()
// val myGenericNotHoge = MyGeneric(notHoge) // Type mismatch.
}
open class Hoge {}
class HogeChild: Hoge() {}
class NotHoge {}
class MyGeneric<T: Hoge>(var value: T) {}
// または以下のように記述
// class MyGeneric<T>(var value: T) where T: Hoge {}
ジェネリック関数
- 関数名のtyぉく前に<T>で型引数を宣言する
- ジェネリック関数は、非ジェネリックな型の中でも定義できる。そもそもクラスと独立して定義可能。
fun main() {
val array = arrayOf(3, 4, 5)
val myClass = MyClass()
println(myClass.pickFirst(array)) // 3
}
class MyClass {
fun <T> pickFirst(list: Array<T>): T {
return list[0]
}
}
- トップレベルにも宣言できるからこれでもよい
fun main() {
val array = arrayOf(3, 4, 5)
println(pickFirst(array)) // 3
}
fun <T> pickFirst(list: Array<T>): T {
return list[0]
}
不変・共変・反変
- Kotlinの配列は不変(JavaやC#は共変)
実験コード
fun main() {
// コンパイルエラー
val anyArray: Array<Any> = arrayOf<String>(1, 2, 3)
}
public class Program
{
public static void Main()
{
object[] objectArray = new string[] { "a", "b", "c" };
objectArray[0] = 1; // コンパイルは通るが、ランタイムエラー
}
}
KotlinのListは変更不可能、MutableListは変更可能
- mutableListOf
fun main() {
val list = mutableListOf("a", "b", "c")
println(list[1]) // b
list[1] = "d" // 変更可能
println(list[1]) // d
}
- listOf
fun main() {
val list = listOf("a", "b", "c")
println(list[1])
list[1] = "d" // コンパイルエラー
}
共変が認められるList
fun main() {
// コンパイルエラーなく通る
val anyList: List<Any> = listOf<String>("a", "b", "c")
}
- Listの定義を見ると、out修飾子がついている
public interface List<out E> : Collection<E> {
- MutableListの方は、変更が可能であるためランタイムエラーが発生する可能性がある。そのため不変。
fun main() {
// コンパイルエラー
val anyList: MutableList<Any> = mutableListOf<String>("a", "b", "c")
}
- MutableListの定義を見ると、out修飾子はついていない
public interface MutableList<E> : List<E>, MutableCollection<E> {
反変
- in修飾子を利用することで、T型、もしくはその上位型」を認める
- 比較をする機構(Comperator)はInt型に対応している必要がある。それはInt型でなくてもInt型を包含するInt型より上位の型であれば問題ない。そのため、ComparatorをInt型に限定する必要はなく、上位型を認めてもよい。
fun main() {
val list = listOf(10, 2, 30, 4)
// 当然Int型は渡せるが、
println(list.sortedWith(ReverseComparatorInt()))
// Any型でも渡せる(反変)
println(list.sortedWith(ReverseComparatorAny()))
}
class ReverseComparatorAny : Comparator<Any> {
override fun compare(x: Any, y: Any): Int = -1
}
class ReverseComparatorInt : Comparator<Int> {
override fun compare(x: Int, y: Int): Int = -1
}
- sortedWithメソッドの定義
public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
- String型は上位型ではないので渡せない
Part 8: もっとオブジェクト指向
拡張関数
fun main() {
val str = "Hello!"
println(str.myRepeat(3)) // 出力: Hello!Hello!Hello!
}
fun String.myRepeat(num: Int): String {
val builder = StringBuilder()
for (i in 1..num) {
builder.append(this)
}
return builder.toString()
}
入れ子クラス
入れ子クラス
- 入れ子のクラスを含んだクラスのことをアウタークラスと呼ぶ(下の例ではMyClass)
- 入れ子クラスは、アウタークラスのメンバーにアクセスできない。
fun main() {
val myClass = MyClass("MyClass")
myClass.callHelper() // 出力: Helping
MyClass.MyHelper().help() // 出力: Helping
}
class MyClass(val name: String) {
class MyHelper {
fun help() {
println("Helping")
// println(name) コンパイルエラー: Unresolved reference: name
}
}
fun callHelper() {
val helper = MyHelper()
helper.help()
}
}
- MyHelperをMyClassプライベートとする場合は、MyHelper()の宣言時にprivate修飾子をつける
インナークラス
インナークラス
- 入れ子となったクラスからアウタークラスのメンバーにアクセスしたい場合にはinner修飾子を付与する
fun main() {
val myClass = MyClass("MyClass")
myClass.callHelper()
myClass.MyHelper("MyHelper").help()
}
class MyClass(val name: String) {
inner class MyHelper(val name: String) {
fun help() {
println("====")
println(name)
println(this@MyClass.name)
}
}
fun callHelper() {
val helper = MyHelper("MyHelperFromMyClass")
helper.help()
}
}
出力
====
MyHelperFromMyClass
MyClass
====
MyHelper
MyClass
Part 8: (続き)移譲プロパティ
実験コード
- プロパティを変更しつつ、追加の処理を呼ぶ感じ?
import kotlin.properties.Delegates
fun main() {
val p = Person()
p.lastName = "太郎"
p.printName()
p.lastName = "次郎"
p.printName()
}
class Person() {
var lastName: String by Delegates.observable("初期値") {
prop, prev, next -> println("${prop.name}が${prev}から${next}に変わりました")
}
fun printName() {
println(lastName)
}
}
出力
lastNameが初期値から太郎に変わりました
太郎
lastNameが太郎から次郎に変わりました
次郎
vetoableで値を変更する前にチェックする
- ラムダ式がfalseの場合は変更されない
import kotlin.properties.Delegates
fun main() {
val p = Person()
p.printAge() // 0
p.age = 10
p.printAge() // 10
p.age = -1
p.printAge() // 10
}
class Person() {
// vetoとは、「拒否する」の意味
var age: Int by Delegates.vetoable(0) {
prop, old, new -> new >= 0
}
fun printAge() {
println(age)
}
}
lazyによる遅延プロパティ
fun main() {
val myRandom = MyRandom()
println("=== first time ==")
println(myRandom.randomValue)
println("=== second time ==")
println(myRandom.randomValue)
}
class MyRandom {
val randomValue: Double by lazy {
println("Initializing")
Math.random()
}
}
出力
=== first time ==
Initializing
0.4276934288775911
=== second time ==
0.4276934288775911
自作移譲プロパティ
package com.example.kotlinlearning
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun main() {
val person = Person()
println("=== println(person.name) ===")
println(person.name)
println("=== person.name = \"太郎\" ===")
person.name = "太郎"
println("=== println(person.name) ===")
println(person.name)
val book = Book()
println("=== println(book.title) ===")
println(book.title)
println("=== book.title = \"速習Kotlin\" ===")
book.title = "速習Kotlin"
println("=== println(book.title) ===")
println(book.title)
}
class LoggableProp<T>(var value: T): ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, prop: KProperty<*>): T {
println("get ${prop.name}")
println("thisRef: $thisRef::class.simpleName")
return this.value
}
override fun setValue(thisRef: Any, prop: KProperty<*>, value: T) {
println("set ${prop.name} to $value")
this.value = value
}
}
class Person {
var name: String by LoggableProp("名無し")
}
class Book {
var title: String by LoggableProp("無題")
}
出力
=== println(person.name) ===
get name
thisRef: com.example.kotlinlearning.Person@67b6d4ae::class.simpleName
名無し
=== person.name = "太郎" ===
set name to 太郎
=== println(person.name) ===
get name
thisRef: com.example.kotlinlearning.Person@67b6d4ae::class.simpleName
太郎
=== println(book.title) ===
get title
thisRef: com.example.kotlinlearning.Book@2c9f9fb0::class.simpleName
無題
=== book.title = "速習Kotlin" ===
set title to 速習Kotlin
=== println(book.title) ===
get title
thisRef: com.example.kotlinlearning.Book@2c9f9fb0::class.simpleName
速習Kotlin
移譲クラス
fun main() {
val myClass = MyClass()
myClass.log("Hello World")
val myClass2 = MyClass2(LoggableImpl("MyClass2"))
myClass2.log("Hello World")
}
interface Loggable {
fun log(value: String)
}
class LoggableImpl(val header: String) : Loggable {
override fun log(value: String) {
println("${this.header}: $value")
}
}
class MyClass : Loggable by LoggableImpl("MyClass") {}
// プライマリコンストラクタにすることで付け替えることもできる
class MyClass2(val log: Loggable) : Loggable by log {}