Kotlin今日の発見 2018年8月3日

Kotlin今日の発見 2018年8月3日

android studioのドキュメントを眺めていた。
Kotlinのことが書かれていた。
また、今更ながら便利な構文を見つけた。

Elvis Operator

代入がnullになってしまうときの処理を書ける。

1
2
3
4
5
6
7
val hashMap = hashMapOf<String, String>(
    "A" to "Alpha",
    "B" to "Bravo",
    "C" to "Charlie")
val nameA = hashMap["A"] ?: "no name."
val nameD = hashMap["D"] ?: "no name."
println("nameA:[$nameA], nameD:[$nameD]")

今まで、if (nameA == null) … と書いてしまっていた。

処理分岐に用いることもできる。

1
2
3
4
fun myFun(hashMap: HashMap<String, String>) {
    val name = hashMap["A"] ?: return
    println("nameA:[$nameA]")
}

これも、ifで判定していた。早く知っていれば…

そもそも、hashMapがnullかもしれない場合

1
2
3
var map: MutableMap<String, String>? = null
val mapSize = map?.size ?: -1
println("mapSize:[$mapSize]")

?.と?:で、nullに対処できる。

複数の値を返却する関数

data classを利用することで、複数の値を返却する関数やメソッドを定義できる。

1
2
3
4
5
6
7
8
9
10
11
data class Result(val code: Int, val msg: String)
fun myFun2(i: Int): Result {
    if (i > 0) {
        return Result(0, "OK")
    }
    return Result(9, "NG")
}
val (code1, msg1) = myFun2(1)
println("code:$code1, msg:$msg1")
val (code2, msg2) = myFun2(-1)
println("code:$code2, msg:$msg2")

戻り値はResultクラスのインスタンスのまま、受け取っても良い気もする。戻り値の使い方次第だけれど。
javaだと、クラスを作るのが面倒なので、複数の値が簡単に返却できればと思うけど、Kotlinなら簡単にdata class定義できるし。

まとめ

やっぱりKotlinは便利。

同じタグの記事
同じカテゴリの記事

Kotlinで$(ドルマーク)を出力する

Kotlinで$(ドルマーク)を出力する

Kotlinの文字列操作は便利だ。
ダブルクォーテーションの中で変数を直接展開することができる。

1
2
val amount = "100.00"
println("This ticket is $amount dollars.")

「100.00 dollars」を「$100.00」と出力したい。
そんなときは、

1
2
3
4
println("This ticket is ${'$'}$amount.")
println("This ticket is ${"$"}$amount.")
println("This ticket is ${'$' + amount}.")
println("This ticket is ${"$" + amount}.")

$を使って$を出力するようだ。
これで解決。

同じタグの記事
同じカテゴリの記事

Kotlin1.2.50がリリースされていた

Kotlin1.2.50がリリースされていた

1.2.50になったからと言って何が新しくなったのかわからないが、
せっかくリリースされたので、今日のツールはKotlinで作る。
いつもなら、チョチョイとpython3を使うところだが。

ツールの仕様

日々の積み重ねで480万レコードになってしまったダンプファイルがある。
そのファイルはタブで区切られていて、特定のカラムの中の文字列の最大長が何文字か調べたい。

Pythonでならこう作る

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import codecs

with codecs.open('out1.txt', 'w', 'utf-8') as w1:
  with codecs.open('out2.txt', 'w', 'utf-8') as w2:
    with codecs.open('in.txt', 'r', 'utf-8') as r:
      for line in r:
        arr = line.strip().split('\t')
        if len(arr) >= 4:
          c0, c1, c2, c3 = arr[0:4]
          if len(c2) > 10:
            w1.write('{:03d}\t{}\t{}\n'.format(len(c2), c0, c2))
          if len(c3) > 10:
            w2.write('{:03d}\t{}\t{}\n'.format(len(c3), c0, c2))
        else:
          print('?[{}][{}]'.format(len(arr), line.strip()))

なかなか簡潔に書ける。
withの入れ子が深いけど、これくらいの長さなら気にしない。
適当なサーバで実行したら、480万レコードのファイルを調査するのに50秒かかった。

Kotlinで作ると

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.File

File("out1.txt").bufferedWriter().use { w1 ->
  File("out2.txt").bufferedWriter().use { w2 ->
    File("in.txt").forEachLine {
      val arr = it.split('\t')
      if (arr.size >= 4) {
        val (c0, c1, c2, c3) = it.split("\t")
        if (c2.length > 10) {
          w1.write("%03d\t%s\t%s\n".format(c2.length, c0, c2))
        }  
        if (c3.length > 10) {
          w2.write("%03d\t%s\t%s\n".format(c3.length, c0, c3))
        }  
      } else {
        println("?[%d][%s]".format(arr.size, it))
      }  
    }  
  }
}

閉じカッコの分、Kotlinの方が長くなってしまうが、pythonと同様に簡潔に書ける。
pythonのときと同じサーバで実行したら、同じ480万レコードのファイルを調査するのに3秒かかった。
Kotlin(java vm?)速い。

まとめ

最近、またpythonでツールを作ることが多くなっていたが、
Kotlinでの実装もなかなか楽しい。

処理速度がとても速い事にも驚いた。pythonに高速な印象はなかったが
ツールだったので、あまり遅さを気にしていなかった。
しかし、Kotlinがここまで速いのであれば、データ量に応じて率先してKotlinを使ってもいいのかもしれない。
.ktsであれば、コンパイルの手間もなくて意外と簡単に使うことができるので。

同じタグの記事
同じカテゴリの記事

Kotlin再発見

Kotlin今日の発見

Kotlinは、javaの上にかぶせて動かすもの。
したがって、Kotlinの構文がよくわからない時は、
Javaの構文でほとんど書けてしまう。
しかし、それに甘んじてしまうと、せっかくのKotlinを活かしきることが出来ない。
たまには、マニュアルに戻ってKotlinを知ることにする。

文字列の比較に==が利用できる

今更知ってしまった。

1
2
3
4
5
6
val s1 = "AAA"
val s2 = "BBB"
val s3 = "AAA"
println("s1==s2 ? ${s1 == s2}")
println("s2==s3 ? ${s2 == s3}")
println("s3==s1 ? ${s3 == s1}")

注意:上記の例は、定数の比較なのでjavaでも正しく動くことがある。

Listへの要素追加は+=で行える。

もちろんjavaのように.add()でも追加できるが、やはりKotlin流に。

1
2
3
4
5
6
val l1 = mutableListOf<String>()
l1.add("Zero")
l1.add("One")
l1 += "Two"
l1 += "Three"
println("l1[$l1]")

なんか、スッキリする。

Listから値を取得するときは配列の様に括弧で取得できる。

もちろんjavaのように.get()でも取得できるが、やはりKotlin流に。

1
2
val l2 = listOf("Zero", "One", "Two")
println("l2[1]:${l2[1]} l2.get(2):${l2.get(2)}")

こっちもスッキリ。

Mapへ値を追加するとも+=で行える。

もちろんJavaのようにput()でも行えるが、Kotlin流に。

1
2
3
4
5
6
val m1 = mutableMapOf<String, String>()
m1.put("A", "あ")
m1.put("I", "い")
m1 += "U" to "う"
m1 += "E" to "え"
println("m1:$m1")

素晴らしい。

Mapから値を取得しようとして、キーが存在したらそれを取得し、存在しなかったら、新たに値を作成し、指定したキーで値を追加するgetOrPutというメソッドがある。

1
2
3
4
5
6
val m2 = mutableMapOf("A" to "あ", "U" to "う")
val a = m2.getOrPut("A", {"ああ"})
print("a:$a")
val i = m2.getOrPut("I", {"いい"})
print("i:$i")
print("m2:$m2")

これは便利。入力データの重複を加味しながら処理をすることはよくあるので、このメソッドを使えば繰り返しの処理がかなり綺麗に書ける。

Mapから値を取得しようとして、キーが存在したらそれを取得し、存在しなかったらデフォルト値を取得するgetOrElseというメソッドがある。

1
2
3
4
5
val m3 = mutableMapOf("A" to "あ", "U" to "う")
val a = m3.getOrElse("A", {"ああ"})
print("a:$a")
val i = m3.getOrElse("I", {"いい"})
print("i:$i")

これも使い道はあると思う。getOrPut()ほどの感動はないが。

文字列中に変数を展開するとき、toString()を明示的に書くことは薦められない。

1
2
3
val l4 = listOf("あ", "い", "う")
println("リスト:$l4")
println("リスト:${l4.toString()}")

どちらも同じように動いてくれるが、toString()を呼ぶ必要はない。
toString()も明示的で嫌いじゃないけど。

まとめ

やはり、たまには振り返らなくてはならない。
Kotlin素晴らしい。
でも、一通りわかった気がしてきてしまったので、
次はrubyに手を出したい。

Jythonに変わるJavaモジュールを使える言語として選択したKotlinだったが、
やはりコンパイルは面倒。コンパイル時間も微妙にかかるし。
コンパイルしてしまえば、Jythonの10倍くらい速く動く気がするけど。

ツールを作る上では、気軽にソースを変更したい。
なので、JRubyを。
JythonのPython2系の文字コード関連で悩まされたので、
JRubyにその辺の問題がないのであれば、早く使ってみたい。

同じタグの記事
同じカテゴリの記事

KotlinでJAXB

KotlinでJAXB

Kotlinで作成しているツールでXMLの読み書きをしたい。
ネットで調べても、Kotlinで便利なXML Parserが見つからなかった。
しかし、KotlinはJavaなのだから、Javaで便利なJAXBが使えるのではないか。

XML出力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import javax.xml.bind.JAXB
import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement

class Person(
    @XmlElement(name = "Name") var name: String,
    @XmlElement(name = "Age") var age: Int) {

    constructor() : this("no name", -1)
}

@XmlRootElement(name="ClassRoom")
class Root(name: String, age: Int) {
   
    var teacher = Person()

    @XmlElementWrapper(name="StudentList")
    @XmlElement(name="Student")
    val persons = mutableListOf<Person>()

    init {
        teacher.name = name
        teacher.age = age
    }

    constructor() : this("no teacher", -1)
}

fun main(args: Array<String>) {
    val r = Root("Ishii", 40)
    r.persons.add(Person("Student 01", 20))
    r.persons.add(Person("Student 02", 21))
    r.persons.add(Person("Student 03", 22))
    r.persons.add(Person("Student 04", 23))
    r.persons.add(Person("Student 05", 24))

    JAXB.marshal(r, java.io.File("work_out.xml"))

    println("OK!")
}

クラスの定義が容易なKotlinなので、XMLのデータクラス作成が楽。

XML入力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fun main(args: Array<String>) {
    val xml = """
        <?xml version="
1.0" encoding="UTF-8" standalone="yes"?>
        <ClassRoom>
            <teacher>
                <age>50</age>
                <name>Tokita</name>
            </teacher>
            <StudentList>
                <Student>
                    <age>30</age>
                    <name>Student 301</name>
                </Student>
                <Student>
                    <age>31</age>
                    <name>Student 302</name>
                </Student>
            </StudentList>
        </ClassRoom>
    "
"".trimIndent()
    xml.reader().use {
        val r2 = JAXB.unmarshal(it, Root::class.java)
        println("Teacher name[${r2.teacher.name}] age[${r2.teacher.age}]")
        println("Students")
        r2.persons.forEach {
            println("  Student name[${it.name}] age[${it.age}]")
        }
    }
}

読み込みも簡単。

まとめ

JAXBで読み書きしやすいXMLは、ある程度限られているので、
これで万能とは言えないが、シンプルなXMLはこれで十分対応可能なようだ。

ちなみに、Java 8 + Kotlin 1.2.31で実行。
Java 9だと、Jigsawの関係で、JAXBを使うために–add-modulesする必要がある。

1
2
$ kotlinc -Xadd-modules=java.xml.bind KotlinJaxb.kt -d KotlinJaxb.jar -include-runtime
$ java --add-modules java.xml.bind -jar KotlinJaxb.jar

kotlincの引数に-Xadd-modulesなんて言うのがあるとは。

同じタグの記事
同じカテゴリの記事

Kotlin 1.2.31がいつの間にかリリースされていた

Kotlin 1.2.31がいつの間にかリリースされていた。

どんな新機能があるのかはわからないが、
java9で発生していた、ウォーニングメッセージやjava10でコンパイルできない問題が回避されている。

Kotlin 1.2.21 java9でのウォーニングメッセージ

1
2
3
4
5
6
7
8
9
10
11
12
13
$ kotlinc -version
info: kotlinc-jvm 1.2.21 (JRE 9.0.4+11)
$ kotlinc Test.kt
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.intellij.util.text.StringFactory to constructor java.lang.String(char[],boolean)
WARNING: Please consider reporting this to the maintainers of com.intellij.util.text.StringFactory
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
$ kotlin TestKt
java.class.version:53.0
java.runtime.version:9.0.4+11
java.version:9.0.4
java.vm.version:9.0.4+11

コンパイル時にウォーニングが表示されるだけで、コンパイルできるし、実行もできる。

Kotlin 1.2.21 java10ではコンパイルできない

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ kotlinc -version
info: kotlinc-jvm 1.2.21 (JRE 10+46)
exception: java.lang.ArrayIndexOutOfBoundsException: 446
        at org.jetbrains.org.objectweb.asm.ClassReader.readUnsignedShort(ClassReader.java:2464)
        at org.jetbrains.org.objectweb.asm.ClassReader.readUTF8(ClassReader.java:2525)
        at org.jetbrains.org.objectweb.asm.ClassReader.readModule(ClassReader.java:761)
        at org.jetbrains.org.objectweb.asm.ClassReader.accept(ClassReader.java:646)
        at org.jetbrains.org.objectweb.asm.ClassReader.accept(ClassReader.java:507)
    ...
$ kotlinc Test.kt
exception: java.lang.ArrayIndexOutOfBoundsException: 446
        at org.jetbrains.org.objectweb.asm.ClassReader.readUnsignedShort(ClassReader.java:2464)
        at org.jetbrains.org.objectweb.asm.ClassReader.readUTF8(ClassReader.java:2525)
        at org.jetbrains.org.objectweb.asm.ClassReader.readModule(ClassReader.java:761)
        at org.jetbrains.org.objectweb.asm.ClassReader.accept(ClassReader.java:646)
        at org.jetbrains.org.objectweb.asm.ClassReader.accept(ClassReader.java:507)
    ...

kotlincのバージョンを見ることも、コンパイルすることもできない。

しかし、コンパイルしておけば、実行はできる。

1
2
3
4
5
$ kotlin TestKt
java.class.version:54.0
java.runtime.version:10+46
java.version:10
java.vm.version:10+46

Kotlin 1.2.31 java9、java10どちらも問題なし。

1
2
3
4
5
6
7
8
$ kotlinc -version
info: kotlinc-jvm 1.2.31 (JRE 9.0.4+11)
$ kotlinc Test.kt
$ kotlin TestKt
java.class.version:53.0
java.runtime.version:9.0.4+11
java.version:9.0.4
java.vm.version:9.0.4+11
1
2
3
4
5
6
7
8
$ kotlinc -version
info: kotlinc-jvm 1.2.31 (JRE 10+46)
$ kotlinc Test.kt
$ kotlin TestKt
java.class.version:54.0
java.runtime.version:10+46
java.version:10
java.vm.version:10+46

とりあえず、Kotlinのバージョンを上げることにする。

同じタグの記事
同じカテゴリの記事

Kotlinを使ってみた

Kotlinを使ってみた

Javaを簡潔に記述できるKotlin。

KotlinでHello, World!下記のソースをHello.ktとして保存する。

1
2
3
fun main(args: Array<String>) {
    println("Hello, Kotlin!")
}

コンパイル、および実行。

1
2
3
$ kotlinc -include-runtime -d Hello.jar Hello.kt
$ java -jar Hello.jar
Hello, Kotlin!

javaコマンドで実行できてしまう。素晴らしい。

Kotlinの特徴

  • 行末のセミコロン不要。
  • javaクラスを自由に扱える。
  • コンパイルするとjavaクラスが作成される。
  • -dオプションで実行形式jarを作成することもできる。
  • 拡張子をktsにすれば、Kotlinスクリプトを記述でき、コンパイルなしに実行できる。
  • List、Mapが拡張され、lambda式で簡単にアクセスできる。

jythonの上を行きそう。

Kotlincでコンパイル

1
$ kotlinc -cp %CLASSPATH% -include-runtime -d Hello.jar Hello.kt

Hello.ktをコンパイルし、Hello.jarが作成される。
-include-runtimeを指定しているので、Kotlinのランタイムクラスがjarファイルに含まれる。
ランタイムは1MB程なので含めてよいと思う。
-include-runtimeを指定しなかった場合は、別途kotlin-runtime.jarを配布する必要がある。

kotlinc,kotlinコマンドの-cpは、jarの*指定はできなかった。それぞれのjarを個別に指定する必要がある。
例えば、Oracle.jdbcとCommonsLoggingを利用しているのであれば下記のように指定する必要がある。

1
2
$ CLASSPATH=/mylib/ojdbc6.jar;/mylib/commons-logging.jar
$ kotlinc -cp %CLASSPATH% -include-runtime -d Hello.jar Hello.kt

外部jarに依存しているのであれば、成果物のjarにランタイムを含めずに、上記の場合なら/mylibにKotlinのランタイムを追加すれば良いと思う。javaコマンドはクラスパスの*指定に対応しているので、下記のように実行できる。

1
$ java -cp /mylib/*:Hello.jar HelloKt

コンパイルしたクラスの実行

Kotlinコンパイル環境があるの場合

1
$ kotlin -cp %CLASSPATH%:Hello.jar HelloKt

HelloKtは実行クラス名。Hello.ktはHelloKtクラスとしてコンパイルされている。
kotlinコマンドでは、Kotlinのランタイムが自動的に参照されるので、コンパイル時にランタイムを含める必要はない。

javaの実行環境のみの場合

コンパイル時にkotlinランタイムを含めた場合

外部jarを参照していないのであれば

1
$ java -jar Hello.jar

外部jarを参照しているのであれば、

1
$ java -cp %CLASSPATH%:Hello.jar HelloKt

コンパイル時にkotlinランタイムを含めなかった場合

1
java -cp %CLASSPATH%:Hello.jar:kotlin-runtime.jar HelloKt

jaraの引数に-jarを指定すると、全てのクラスパスは無視されるので、
作成したjarもクラスパスに含めて、メインメソッドのあるクラスを指定する。
Hello.ktはHelloKtクラスとしてコンパイルされる。
Hello.ktにpackage指定されいている場合は、そのパッケージに所属した形でコンパイルされる。

下記のkotlinをHello2.ktとして保存。

1
2
3
4
package my.first
fun main(args: Array<String>) {
  print("Hello.")
}
1
2
3
$ kotlinc -include-runtime -d h.jar Hello2.kt
$ java -cp ./h.jar my.first.Hello2Kt
Hello.

Kotlinスクリプト

kotlinをコンパイルせずに、スクリプトとして実行できる。Kotlinスクリプトの拡張子はktsにしなければならない。

1
kotlinc -script Hello.kts

考察:jythonスクリプトをkotlinに置き換えるべきか

jythonもkotlinもJavaで実装されている。
Javaで実装されているため、Windows、Linux、macを問わず開発/実行できる。
Javaの豊富なライブラリを利用できる点はどちらも同じ。

では、jythonをkotlinに置き換えるべきなのだろうか。

jythonは、pythonのバージョンが2.7で止まっている。
python2.7なので、文字列の扱いがデリケートでる。
ここがとても煩わしい。jythonで色々なタイプの文字が出て来る場合は、
文字列結合も、jythonではなく、java.lang.StrinBuilderやjava.text.MessageFormatを使ったりしている。
kotlinでは、ここはあまり気にしなくて良さそうだ。

しかし、jythonの有利な点として、ファイルI/Oやディレクトリ操作、FTP、メール送信などがpythonとして簡単に記述することができる。
kotlinでも、色々kotlin拡張モジュールがあったり、強力なラムダ式を利用すれば、そこそこ簡単に実装できるようにも思えるが、pythonほどお手軽ではない。

pythonに慣れているために、こう考えてしまうのか。
もう少しkotlinでツールを作ってみよう。

同じタグの記事
同じカテゴリの記事