Open51

Kotlin学習メモ

jnuankjnuank

教材

https://www.amazon.co.jp/dp/B076Q2L1M6/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

  1. 2021/03/01(月) https://zenn.dev/jnuank/scraps/e53bf9a1afdcb5#comment-a9ba8a47c5aa99

個人的にKotlinが良いなって思うところ

  • フォーマット文に余計なものを入れなくていい。

    • println("hello $name") こんな感じで書ける。Pythonとかだと頭にf付けたり、C#だと$を頭に付けたりする。
  • when式が強力

  • Mapのコレクション操作時に、for((key, value) in Map) みたいな形で要素を分解して取り出せる

    • これはPythonでもあった気はする
jnuankjnuank
  • 2021/03/01(月)
    第1章 10:00-11:51
jnuankjnuank
jnuankjnuank

Kotlinの第一目標が、Javaの代替言語を目指している。
Javaより短いコードで書けるようにする。感じかな?

主に使う一般的な場面は以下の2つ。

  • サーバサイドの実装(Webアプリケーションのバックエンド)
  • Androidデバイス上で動くモバイルアプリケーションの実装

JSにもコンパイルできること知らなかった!

ES5まで対応しているとのこと。

https://kotlinlang.org/docs/js-overview.html

jnuankjnuank

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"}
jnuankjnuank

【疑問】
Kotlinは既存のJavaと完全に適合します。と本書では書かれているけど、なにをもって完全に適合していると言えるのだろうか。テストを入念にしている?

jnuankjnuank

【疑問】

P.10 たとえば、Builderパターンのサポートのよって簡潔な構文で任意のオブジェクトグラフを作ることができ、抽象化を保ちつつ再利用のためのツールを言語内に保持することが可能

KotlinでDSLを作りやすい、というのがよくわかっていない。

というよりも、そもそもDSLってなんだろうって感じ

jnuankjnuank

哲学

  • 実用主義(pragmatic)
  • 簡潔(concise)
  • 安全(safe)
  • 相互運用性

安全の話で、null安全であること。
ClassCastExceptionの話。
Javaの場合は型チェックとキャストを繰り返し書かなければいけないけど、Kotlinの場合はチェックした後はそのまま扱える。

    if(value is String)
    	println(value.toUpperCase())

Pythonもある意味、キャストせずに扱えるから動的言語の良さはそういうところにあるのかもしれない。

jnuankjnuank

JavaからKotlinへコンバートできるのを初めて知った。

InteliJのサポート強い。

KotlincというREPLがあるのも良い

jnuankjnuank

2章〜2.2まで

  • 2021/03/01(月)
  • 12:20 - 13:00
  • 15:00 - 18:25

集中力がなかなか続かない。

jnuankjnuank

main.ktに書いた内容。

関数がファイルのトップレベルに書ける。
System.out.println() と書かなくていい。
配列もクラスとして扱われているから、String args[] みたいな独自な記法は必要がない。
セミコロン要らない

fun main(args: Array<String>){
    println("Hello, world!")
}
jnuankjnuank

仮引数(parameter)と実引数(argument)の違いを初めて知った!

日本語では引数 として区別していないパターンが多いみたいだが、
英語圏では、比較的よく使い分けられるらしいので、覚えておくと良さげ。

http://www.cc.kyoto-su.ac.jp/~yamada/ap/parameter_argument.html

下記のコードだと、ab が仮引数。
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
}
jnuankjnuank

文と式の違い

式は何かしらの値を返すもの。
文は値を返さないことがある。

式は別の式の一部として使用できる値を持つ。

Javaの場合はすべての制御構造(if、for、whileなど)は文だが、Kotlinの場合はほぼ式で成り立つ。

逆に、Javaの値割り当ては式であり、Kotlinでは文となっている。

以下のコード例だと、 b に trueを割り当てた結果として持つ式となるので、この判定は常にtrueとなる。

if ( b = true) { /* 何かしらの処理 */ }

Kotrinの場合はエラーとなった。

jnuankjnuank

InteliJで、関数にカーソル当てた状態で、Option + Enterすると、

Convert to expression body と Convert to block bodyアクションを選んで、ブロック本体か式本体の関数に変更ができる

jnuankjnuank

val : イミュータブルな参照。一度初期化されると、再割り当ては不可能。

var:ミュータブルな参照。

jnuankjnuank

文字列テンプレート

他の言語でもよくあるやつ。頭に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]
jnuankjnuank

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にしたのか?

jnuankjnuank

カスタムアクセサ

getterを持つプロパティと引数の無い関数のどちらも実装内容、実行速度の面では違いがない。違いがあるのは可読性。

一般的にはプロパティのほうが良い。

【疑問】基本的に中身の値は変わらないから、毎回計算するのってもったいない気もしているが、キャッシュされているのだろうか?

カスタムゲッターでは無い、getterのみのプロパティはコンパイル時にconstとしてマークされるっぽい。
https://kotlinlang.org/docs/properties.html#compile-time-constants

class Rectangle(val height: Int, val width: Int) {
    val isSqure: Boolean
        get() {
            return height == width
        }
}
jnuankjnuank

パッケージ構成

Javaのように1クラス1ファイルといった制約は、Kotlinには無い。
ただ、InteliJではオススメとして、合わせるようにって出てくる。

Kotlinの場合、インポートするクラスと関数を区別しない。

パッケージ名の後の * を使うことで、パッケージ内のクラスだけではなく、トップレベルの関数やプロパティも見えることに注意。

jnuankjnuank

2.3章

  • 2021/03/04(木)
jnuankjnuank

enum、when

Kotlinのenumはsoft keywordであり、classの前に置いたときだけ特別な意味となる。

jnuankjnuank
// 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っていう話だったから、そういうものか

jnuankjnuank

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

jnuankjnuank

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")
        ^
jnuankjnuank

スマートキャスト

一度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かな?

https://qiita.com/propella/items/33433278497f290ceadb

jnuankjnuank

when式は、breakを使わなくていいのがとても楽ちん。

when だったらおそらくbreakがあったのかもしれない。

jnuankjnuank

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 // これはエラー
}

jnuankjnuank

kotlinのrangeは閉じている。閉区間みたいなものかな?
1..10 だと、1も10も含む

>>> for(i in 1..10) { println(i)}
1
2
3
4
5
6
7
8
9
10
jnuankjnuank

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) + " ")
    }
}
jnuankjnuank

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)にも書き換えられる。

https://qiita.com/s_9_i/items/64d7766e139c80ce6cb6

    // こちらと書き方同じ。
//    for (i in 100 downTo 1 step 2 ){
//        print(fizzBuzz(i) + " ")
//    }
    for (i in 100.downTo(1).step(2) ){
        print(fizzBuzz(i) + " ")
    }
jnuankjnuank

2.3.4章〜2章最後

  • 2021/03/19 (金) 120分
jnuankjnuank

引数なしwhenだと、switch文みたいに型で見るわけではなく、
任意のbool式でOK

jnuankjnuank

P.54

ほかのモダンなJVM言語と同じように、Kotlinでは検査例外と非検査例外を区別していません
ある関数からスローされる例外を指定する必要はなく、使用する側では、どの例外についても、処理してもいいし、しなくてもよいのです

そもそも検査例外と非検査例外がよくわかっていないな?

Javaは検査例外だからこそ、関数定義のあとに、 throws IOExpetion と投げるかもしれない例外を定義する必要があったらしいけど。

この設計方針は、Javaでの検査例外の経験に基づいています。Javaのようなルールは、例外を再度スローしたり無視したりするための意味のないコードが数多く必要になる上に、実際に起こりうるエラーから常に実装者を守るようになっていないことが、経験的にわかっています。

つまり、ある関数Aから、関数Bを呼んだときに、関数Bが例外を出して、関数Aが握りつぶす可能性もあるし、再度スローすることもある。

また想定外のエラーも出てくるから、それが守られてないよね、ということかな

jnuankjnuank

わかりやすいのがあった
https://qiita.com/yuba/items/d41290eca726559cd743

検査例外は呼び出し側の責任ではない異常系

IOException(ディスク満杯や故障などが起きたときにも出るので、呼び出し側で防げない異常)など

非検査例外は、NumberFormatExceptionやNullPointerExceptionなど、渡す引数が正しくなかったり、nullだっりすることで発生する類のエラーなので、
これは呼び出し側が気をつければ良いということになる。

→ ここは、型で表明ができれば守れる部分でもありそう。

0〜9以外の文字を渡されてほしくなければ、それ用の型を定義しておき、それを引数に指定すれば間違いは防げそうである。

jnuankjnuank

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()
    }
}

jnuankjnuank

3章

jnuankjnuank

javaClass は JavaのgetClass()と同じとのこと

>>> println(set.javaClass)
class java.util.HashSet
jnuankjnuank

拡張関数

Stringがレシーバ型、thisがレシーバオブジェクト

//  ↓レシーバ型      ↓レシーバオブジェクト
fun String.lastChar(): Char = this.get(this.length - 1)
println("Kotlin".lastChar())
n

上記の例だと、Stringがレシーバ型。"Kotlin"がレシーバオブジェクト。

lastChar()はStringの一部では無いけど、あたかもあるように使える

jnuankjnuank

継承させるやつ

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!")
jnuankjnuank

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
jnuankjnuank

拡張関数は、その場限りでしか使わないようなものを作るのにはちょうどいい。

toJsonとかtoDomainなど。
場合によってはクラス内に生やすでも良いのかも知れないけど

jnuankjnuank

4章

クラスの型システム

jnuankjnuank

Kotlinのoverrideキーワードは、Javaとは違って必須

jnuankjnuank

型システム

インターフェース

インターフェース定義と、インターフェース上でデフォルト実装が出来る


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
}
jnuankjnuank

可視性修飾子

  • public

    • クラスメンバはどこからも参照可能
    • トップレベルはどこからでも参照可能
  • internal

    • クラスメンバはモジュール内からのみ参照可能
    • トップレベルはモジュール内からのみ参照可能

→ モジュールというのは、Kotlinのファイルが一体としてコンパイルされるまとまりのこと。
 jarファイルとかかな??

  • protected

    • サブクラスから参照可能
  • private

    • クラス内からのみ参照可能
jnuankjnuank

内部クラスとネストされたクラス

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
    }
}

jnuankjnuank

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)
    }
jnuankjnuank

コンストラクタ関連

スーパークラスを継承するときは後ろに () 付ける
インターフェースはコンストラクタ持たないので必要ない

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
>>> 

jnuankjnuank

JDK 16 - e: java.lang.NoClassDefFoundError: Could not initialize class org.jetbrains.kotlin.com.intellij.pom.java.LanguageLevel

なぜこうなるかはわからないが、以下の方法どおり、 ~/.gradle/gradle.properties に設定を追加したらいけた(もともと設定ファイルが無かったので作った)

illegal-accessを許可しないと使えないのだろうか?

https://youtrack.jetbrains.com/issue/KT-45566