Kotlin学習メモ
教材
個人的にKotlinが良いなって思うところ
-
フォーマット文に余計なものを入れなくていい。
-
println("hello $name")
こんな感じで書ける。Pythonとかだと頭にf
付けたり、C#だと$
を頭に付けたりする。
-
-
when式が強力
-
Mapのコレクション操作時に、
for((key, value) in Map)
みたいな形で要素を分解して取り出せる- これはPythonでもあった気はする
- 2021/03/01(月)
第1章 10:00-11:51
まずははじめに、Kotlin Playgroudを使用してみる。
(本書ではkotlin tryを使用して、と書いてあるが、obsolete:廃止となっていた)
?:
のことをエルビス演算子ということを初めて知った。
C#でも同じのはあったけど、名前があったのか。
Kotlinの第一目標が、Javaの代替言語を目指している。
Javaより短いコードで書けるようにする。感じかな?
主に使う一般的な場面は以下の2つ。
- サーバサイドの実装(Webアプリケーションのバックエンド)
- Androidデバイス上で動くモバイルアプリケーションの実装
JSにもコンパイルできること知らなかった!
ES5まで対応しているとのこと。
Kotlinの言語性質
静的型付き言語(statically typed)
- パフォーマンス
- 信頼性
- メンテナンス性
- ツールサポート
関数型、オブジェクト指向
関数型の簡潔さも扱える。
関数型は関数を値として扱えるのでコードの重複を避け、より強力な抽象化が実現ができる。
たとえば、コレクションから一致する要素を探す、という処理で抽象化されたfindを使えば、
一致する条件の関数を、引数として渡すことができるので、簡潔に違う条件で書くことができる。
C#でLinqを初めて使った時に感動したことを思い出した。
val findAlice = persons.find{ it.name == "Alice" }
val findBob = persons.find{it.name == "Bob"}
そういえば、findのあとにラムダ式を入れるのは、{}
じゃないと駄目なのか。
あと、itも固定っぽい。
C#の場合は、他のメソッドと同じく()
を使っていたし、ラムダの引数は好きに扱えている。
でも、デフォルトと言っているので、もしかしたら何か指定が可能なのか?
persons.Select( x => x.name == "Alice")
引数名 ->
でいけるようだ
val findBob = persons.find{ x -> x.name == "Bob"}
【疑問】
Kotlinは既存のJavaと完全に適合します。と本書では書かれているけど、なにをもって完全に適合していると言えるのだろうか。テストを入念にしている?
【疑問】
P.10 たとえば、Builderパターンのサポートのよって簡潔な構文で任意のオブジェクトグラフを作ることができ、抽象化を保ちつつ再利用のためのツールを言語内に保持することが可能
KotlinでDSLを作りやすい、というのがよくわかっていない。
というよりも、そもそもDSLってなんだろうって感じ
哲学
- 実用主義(pragmatic)
- 簡潔(concise)
- 安全(safe)
- 相互運用性
安全の話で、null安全であること。
ClassCastExceptionの話。
Javaの場合は型チェックとキャストを繰り返し書かなければいけないけど、Kotlinの場合はチェックした後はそのまま扱える。
if(value is String)
println(value.toUpperCase())
Pythonもある意味、キャストせずに扱えるから動的言語の良さはそういうところにあるのかもしれない。
JavaからKotlinへコンバートできるのを初めて知った。
InteliJのサポート強い。
KotlincというREPLがあるのも良い
2章〜2.2まで
- 2021/03/01(月)
- 12:20 - 13:00
- 15:00 - 18:25
集中力がなかなか続かない。
main.ktに書いた内容。
関数がファイルのトップレベルに書ける。
System.out.println() と書かなくていい。
配列もクラスとして扱われているから、String args[] みたいな独自な記法は必要がない。
セミコロン要らない
fun main(args: Array<String>){
println("Hello, world!")
}
仮引数(parameter)と実引数(argument)の違いを初めて知った!
日本語では引数
として区別していないパターンが多いみたいだが、
英語圏では、比較的よく使い分けられるらしいので、覚えておくと良さげ。
下記のコードだと、a
と b
が仮引数。
1, 2が実引数となっている。
fun main(args: Array<String>){
println(max(1, 2))
}
fun max(a: Int, b: Int): Int{
return if (a > b) a else b
}
文と式の違い
式は何かしらの値を返すもの。
文は値を返さないことがある。
式は別の式の一部として使用できる値を持つ。
Javaの場合はすべての制御構造(if、for、whileなど)は文だが、Kotlinの場合はほぼ式で成り立つ。
逆に、Javaの値割り当ては式であり、Kotlinでは文となっている。
以下のコード例だと、 b に trueを割り当てた結果として持つ式となるので、この判定は常にtrueとなる。
if ( b = true) { /* 何かしらの処理 */ }
Kotrinの場合はエラーとなった。
InteliJで、関数にカーソル当てた状態で、Option + Enterすると、
Convert to expression body と Convert to block bodyアクションを選んで、ブロック本体か式本体の関数に変更ができる
val : イミュータブルな参照。一度初期化されると、再割り当ては不可能。
var:ミュータブルな参照。
文字列テンプレート
他の言語でもよくあるやつ。頭にfとか、$とか付けなくていいのが手軽で良さげ。
fun main(args: Array<String>){
val name = if (args.size > 0 ) args[0] else "Kotlin"
print("Hello, $name")
}
$変数名
でも展開ができるが、配列はオブジェクトのアドレスのほうが出てきてしまうので、${}
で囲う必要があり
fun main(args: Array<String>){
val name = if (args.size > 0 ) args[0] else "Kotlin"
print("Hello, ${args[0]}")
}
fun main(args: Array<String>){
val name = if (args.size > 0 ) args[0] else "Kotlin"
print("Hello, $args[0]")
}
>>> Hello, [Ljava.lang.String;@e9e54c2[0]
Conver Java to Kotlin
下記のjavaファイルを作成し、Code -> Convert Java File to Kotlin Fileを実行する。
public class Person {
private final String name;
public Person(String name){
this.name = name;
}
public String getName(){
return name;
}
}
Convert後
class Person(val name: String)
このKotlinのコードには、メンバ変数 name
とそれを受け取るコンストラクタと、getterが内包されている?
Kotlinはpublicがデフォルトらしい
【疑問】Javaではprivateだったのに、なんでKotlinではデフォルトをpublicにしたのか?
カスタムアクセサ
getterを持つプロパティと引数の無い関数のどちらも実装内容、実行速度の面では違いがない。違いがあるのは可読性。
一般的にはプロパティのほうが良い。
【疑問】基本的に中身の値は変わらないから、毎回計算するのってもったいない気もしているが、キャッシュされているのだろうか?
カスタムゲッターでは無い、getterのみのプロパティはコンパイル時にconstとしてマークされるっぽい。
class Rectangle(val height: Int, val width: Int) {
val isSqure: Boolean
get() {
return height == width
}
}
パッケージ構成
Javaのように1クラス1ファイルといった制約は、Kotlinには無い。
ただ、InteliJではオススメとして、合わせるようにって出てくる。
Kotlinの場合、インポートするクラスと関数を区別しない。
パッケージ名の後の *
を使うことで、パッケージ内のクラスだけではなく、トップレベルの関数やプロパティも見えることに注意。
2.3章
- 2021/03/04(木)
enum、when
Kotlinのenum
はsoft keywordであり、class
の前に置いたときだけ特別な意味となる。
// enumの簡単な宣言
enum class Color {
RED, ORAGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
// プロパティと一緒に宣言
enum class Colors (
val r: Int, val g: Int, val b: Int
){
RED(255, 0, 0),
ORANGE(255, 165, 0),
YELLOW(255, 255, 0),
GREEN(0, 255, 0),
BLUE(0, 0, 255),
INDIGO(75, 0, 130),
VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
>>> :load 2chapter/Color.kt
>>> println(Colors.BLUE)
BLUE
>>> println(Colors.BLUE.r)
0
>>> println(Colors.BLUE.g)
0
>>> println(Colors.BLUE.b)
255
>>>
普通に内部プロパティはpublicになっている。
デフォルトはpublicっていう話だったから、そういうものか
when式
fun getMnemonic(color: Colors) =
when (color) {
Colors.RED -> "Richard"
Colors.ORANGE -> "Of"
Colors.YELLOW -> "York"
Colors.GREEN -> "Gave"
Colors.BLUE -> "Battle"
Colors.INDIGO -> "In"
Colors.VIOLET -> "Vain"
}
fun getWarmth(color: Colors) =
when(color){
Colors.ORANGE, Colors.YELLOW -> "warm"
Colors.GREEN -> "neutral"
Colors.BLUE, Colors.INDIGO, Colors.VIOLET -> "cold"
else -> "else"
}
when式は網羅的でなければいけない。
でなければ、elseを使う。
>>> println(getWarmth(Colors.BLUE))
cold
>>> println(getWarmth(Colors.RED))
else
setOfで集合のwhenもいける
fun mix(c1: Colors , c2: Colors) =
when (setOf(c1, c2)) {
setOf(Colors.RED, Colors.YELLOW) -> Colors.ORANGE
setOf(Colors.YELLOW, Colors.BLUE) -> Colors.GREEN
setOf(Colors.BLUE, Colors.VIOLET) -> Colors.INDIGO
else -> throw Exception("Dirty color")
これはちゃんとエラーとなる
fun mix(c1: Colors , c2: Colors) =
when (setOf(c1, c2)) {
setOf(Colors.RED, Colors.YELLOW) -> Colors.ORANGE
else -> throw Exception("Dirty color")
setOf(Colors.YELLOW, Colors.BLUE) -> Colors.GREEN
setOf(Colors.BLUE, Colors.VIOLET) -> Colors.INDIGO
}
error: 'else' entry must be the last one in a when-expression
else -> throw Exception("Dirty color")
^
スマートキャスト
一度if文を通って判定した後は、自動的にキャストがされている。
interface Expr
class Num (val value: Int) : Expr
class Sum (val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int {
if ( e is Num){
return e.value
}
if (e is Sum){
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
IDE上でも色が変わって表示されている。
valかつ、カスタムアクセサを使わないことが条件らしい
似たような機能は、TypeScriptのTypeGuardかな?
when式は、breakを使わなくていいのがとても楽ちん。
when文
だったらおそらくbreakがあったのかもしれない。
if,when式の分岐先はブロックでもOK.
このときはreturnではなく、ブロック最後の値が評価されて戻り値となる
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value // ブロック最後が、関数の戻り値となる。returnが要らない。
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right
}
else -> throw IllegalArgumentException("Unknwn expression")
}
ただし、関数でのブロックは、明示的なreturnが必要
fun test(): Int {
1 // これはエラー
}
kotlinのrangeは閉じている。閉区間みたいなものかな?
1..10
だと、1も10も含む
>>> for(i in 1..10) { println(i)}
1
2
3
4
5
6
7
8
9
10
1..10がrange。
downTo 1 step 2 みたいなやり方もできる
fun fizzBuzz(i: Int) = when{
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "$i"
}
fun main() {
for (i in 1..10){
print(fizzBuzz(i) + " ")
}
for (i in 100 downTo 1 step 2){
print(fizzBuzz(i) + " ")
}
}
downToとstepはどうやら関数らしくて、定義に飛んで中身を見ることができた
public infix fun Int.downTo(to: Int): IntProgression {
return IntProgression.fromClosedRange(this, to, -1)
}
public infix fun IntProgression.step(step: Int): IntProgression {
checkStepIsPositive(step > 0, step)
return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
infixというのはなんだろうと思ったけど、中置みたい。
関数を中置記法で書くためのキーワードか。
100 downTo 1は、100.downTo(1)にも書き換えられる。
// こちらと書き方同じ。
// for (i in 100 downTo 1 step 2 ){
// print(fizzBuzz(i) + " ")
// }
for (i in 100.downTo(1).step(2) ){
print(fizzBuzz(i) + " ")
}
2.3.4章〜2章最後
- 2021/03/19 (金) 120分
引数なしwhenだと、switch文みたいに型で見るわけではなく、
任意のbool式でOK
P.54
ほかのモダンなJVM言語と同じように、Kotlinでは検査例外と非検査例外を区別していません
ある関数からスローされる例外を指定する必要はなく、使用する側では、どの例外についても、処理してもいいし、しなくてもよいのです
そもそも検査例外と非検査例外がよくわかっていないな?
Javaは検査例外だからこそ、関数定義のあとに、 throws IOExpetion
と投げるかもしれない例外を定義する必要があったらしいけど。
この設計方針は、Javaでの検査例外の経験に基づいています。Javaのようなルールは、例外を再度スローしたり無視したりするための意味のないコードが数多く必要になる上に、実際に起こりうるエラーから常に実装者を守るようになっていないことが、経験的にわかっています。
つまり、ある関数Aから、関数Bを呼んだときに、関数Bが例外を出して、関数Aが握りつぶす可能性もあるし、再度スローすることもある。
また想定外のエラーも出てくるから、それが守られてないよね、ということかな
わかりやすいのがあった
検査例外は呼び出し側の責任ではない異常系
IOException(ディスク満杯や故障などが起きたときにも出るので、呼び出し側で防げない異常)など
非検査例外は、NumberFormatExceptionやNullPointerExceptionなど、渡す引数が正しくなかったり、nullだっりすることで発生する類のエラーなので、
これは呼び出し側が気をつければ良いということになる。
→ ここは、型で表明ができれば守れる部分でもありそう。
0〜9以外の文字を渡されてほしくなければ、それ用の型を定義しておき、それを引数に指定すれば間違いは防げそうである。
Kotlinは検査例外と非検査例外を区別しない。
これはどういうことかというと、使用する側のコードでは処理しても、しなくてもよい。完全任意
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
// Javaでは非検査例外ではあるけど、不正な入力データは一般的に起こりうるのでキャッチしたほうがいいかもしれない?
catch (e: NumberFormatException){
return null
}
finally {
// closeするときには、IOExceptionをスローする可能性があるが、ストリームを閉じる時に意味のあるアクションを取ることができない。
// 大体が、もう一度試みるか、握りつぶすしかない、というお話なのかな?
// そうなると意味のないボイラーテンプレートとなってしまい、コードの可読性を下げてしまうことに繋がる、ということみたいだ
reader.close()
}
}
3章
javaClass
は JavaのgetClass()と同じとのこと
>>> println(set.javaClass)
class java.util.HashSet
Kotlin REPL を使いたかった
javaとkotlinをterminalから使えるように、SDKMANをインストールする。
snap install kotlinをやろうとしたけど、ちょっと微妙そうなので止めた
拡張関数
Stringがレシーバ型、thisがレシーバオブジェクト
// ↓レシーバ型 ↓レシーバオブジェクト
fun String.lastChar(): Char = this.get(this.length - 1)
println("Kotlin".lastChar())
n
上記の例だと、Stringがレシーバ型。"Kotlin"がレシーバオブジェクト。
lastChar()はStringの一部では無いけど、あたかもあるように使える
継承させるやつ
open classを付けることによって継承が出来るようになるらしい
メンバ関数と拡張関数のオーバーライド
拡張関数はオーバーライドは不可
// メンバ関数のオーバーライド
// val view: View = Button()
// view.click()すると、 Button clickedが表示される
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
// メンバーバーライド
// val view: View = Button()
// view.click()すると、 I'm a view! が表示される
fun View.showOff() = println("I'm a view! ")
fun Button.showOff() = println("I'm a button!")
Kotlinのラムダ式
kotlinのラムダ式は{}
で囲む
>>> val sum = { x: Int, y:Int -> x + y }
>>> sum(1, 2)
res55: kotlin.Int = 3
>>> sum
res56: (kotlin.Int, kotlin.Int) -> kotlin.Int = (kotlin.Int, kotlin.Int) -> kotlin.Int
拡張関数は、その場限りでしか使わないようなものを作るのにはちょうどいい。
toJsonとかtoDomainなど。
場合によってはクラス内に生やすでも良いのかも知れないけど
4章
クラスの型システム
Kotlinのoverrideキーワードは、Javaとは違って必須
型システム
インターフェース
インターフェース定義と、インターフェース上でデフォルト実装が出来る
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!")
}
interface Focusable {
fun setFocus(b: Boolean) = println("I ${if (b) "got" else "lost"} focus.")
fun showOff() = println("I'm focusable!")
}
class Button : Clickable, Focusable {
override fun click() =
println("I was clicked")
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
fun main(args: Array<String>)
{
val button = Button()
button.showOff()
button.setFocus(true)
button.click()
}
継承できる型
Kotlinの場合は、open
を明示的に付けないと、継承をすることができないようになっている。
これは、Effective Javaに「継承のために設計および文書化する。でなければ継承を禁止する」と勧められているため。
openはクラスにも付けられるし、メンバメソッドにも付けられる。
逆にインターフェースでoverrideしたものをそのクラスから先に継承できないように、finalを明示的に付けることもできる
// openなので、他のクラスはこれを継承することができる
// 基底クラスやインターフェースのメンバをオーバーライドした場合は、デフォルトでopenである
open class RichButton: Clickable {
fun disable() {} // final扱い
open fun animate() {} // openなのでサブクラスでオーバーライドできる
override fun click() {} // openな関数をオーバーライドしているので、openとなる
override final fun showOff() {} // 明示的にfinalを付けられる
}
class VeryRichButton: RichButton() {
override fun showOff() {} // 'showOff' in 'RichButton' is final and cannot be overridden
}
可視性修飾子
-
public
- クラスメンバはどこからも参照可能
- トップレベルはどこからでも参照可能
-
internal
- クラスメンバはモジュール内からのみ参照可能
- トップレベルはモジュール内からのみ参照可能
→ モジュールというのは、Kotlinのファイルが一体としてコンパイルされるまとまりのこと。
jarファイルとかかな??
-
protected
- サブクラスから参照可能
-
private
- クラス内からのみ参照可能
内部クラスとネストされたクラス
import java.io.Serializable
interface State: Serializable
interface View {
fun getCurrentState(): State
fun restoreState(state: State) {}
}
class Button: View {
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) {}
class ButtonState: State {}
}
class ABC {
inner class Inner {
fun getOuterRefernce(): ABC = this@ABC
}
}
Seledクラス
when式で型の判定が出来るが、通常のスーパークラスとサブクラスだと、elseを必ず付けないといけないし、
サブクラスが増えた場合に検知ができない(when式に追加しないと、elseに通ってしまう)
sealedキーワードを付けることによって、あるクラスがサブクラスしか取れないようになっている。
sealed class Expr {
class Num(val value: Int): Expr()
class Sum(val left: Expr, val right: Expr): Expr()
class hoge(): Expr() // これを追加すると、下のwhen式で怒られる
}
fun eval(e: Expr): Int =
when (e) { // 'when' expression must be exhaustive, add necessary 'is hoge' branch or 'else' branch instead
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
}
コンストラクタ関連
スーパークラスを継承するときは後ろに ()
付ける
インターフェースはコンストラクタ持たないので必要ない
open class Button2()
class RadioButton: Button2 // This type has a constructor, and thus must be initialized here
open class Button2()
class RadioButton: Button2()
Privateなコンストラクタを定義する
private constructorで付けることで、外部からは生成を出来ないようにすることができる
代わりにcompanion objectでファクトリメソッドを用意する
class PrivateConstructor private constructor(val nickname: String) {
companion object {
fun create(nickname: String) = PrivateConstructor(nickname)
}
}
実行結果
>>> val con = PrivateConstructor("abc")
error: cannot access '<init>': it is private in 'PrivateConstructor'
val con = PrivateConstructor("abc")
^
>>> val con = PrivateConstructor.create("abc")
>>> con.nickname
res8: kotlin.String = abc
>>>
Kotlinプロジェクトの作り方
JDK 16 - e: java.lang.NoClassDefFoundError: Could not initialize class org.jetbrains.kotlin.com.intellij.pom.java.LanguageLevel
なぜこうなるかはわからないが、以下の方法どおり、 ~/.gradle/gradle.properties
に設定を追加したらいけた(もともと設定ファイルが無かったので作った)
illegal-accessを許可しないと使えないのだろうか?