kazutomoのブログ

たまーに気が向いたらなんか書きます

第一言語をJavaからKotlinにした日

私が仕事として扱ったことのあるプログラミング言語は以下があります。

この中でも Java は特別で、15年近く愛用してきました。

Javaの優れている点

Java の優れている点として以下があるとおもいます。

信頼性の高いVM

インタープリタ言語の中にはランタイムのバージョンアップで動作が変わってしまったりすることも多く、なんたらenv といったアプリケーションを使って任意のバージョンのランタイムに固定するのが慣例となっていますが、Javaにおいては最新のJVMを入れれば大体なんとかなります。

クロスプラットフォーム動作

私は長らく Windows を開発機として使用し、Linux サーバで動作させる。ということをしてきましたが、Windows で動くけど Linux で動かない。という経験はしたことがなく、動作させるOSによって動作確認をやりなおす。というような手順を踏んだことはありません。
現在では開発機として Mac を使用していますが、その際も状況は変わっていません。

インタープリタ言語の中には高速化のためにネイティブコードをコンパイルして使用するものが多く、Windows でビルドした成果物を Linux に持っていってもアーキテクチャの違いによって動作しない。といったことが起こりえますが、Javaにおいては変なことをしなければそのようなケースはありません。

豊富なライブラリ群

これは今では多くの言語で豊富なライブラリを所有しているので、多くの場合はあまり差とは言えないかもしれません。
しかし、Java は現在も重要なポジションをしめており、多くの場合に優先してSDKやライブラリがリリースされる言語でもあります。

高速動作

Javaで記述されたプログラムは非常に高速に動作します。
また、スレッドを使って作成されたプログラムはマルチコアで動作します。
多くのプログラミング言語のランタイムがシングルコアでしか動作せず、プロセスフォークによってマルチコアに対応するしかない点を考えても、ここはかなり優位点といえます。

Javaのイマイチな点

Javaは多くの優れている点を持っているとはいえ、当然ながらイマイチな点があります。

  • ランタイムがでかい
  • VMがあたたまるまで時間がかかる
  • 言語仕様が堅い
  • NullSafety ではない
  • ストリーム処理がダサい

ランタイムがでかい

これは C/C++golang のようなネイティブコードを出力するプログラミング言語に対してのイマイチな点です。
ランタイムによって多くの恩恵を得ているのでやむを得ない面もあるのですが、省メモリ・省ディスクスペースを要求されるケースではJavaが使用できないことにつながっています。
たとえば、ニンテンドー3DS の OS 開発をした際には C++ で開発しましたが、これはJavaの入り込む余地のない世界といえます。
特にメモリの面は問題として大きく感じており、Hello World を動かすだけでも数MBのフットプリントが必要で、これはおそらく全プログラミング言語の中で最大レベルなのではないでしょうか。

VMがあたたまるまで時間がかかる

私はこの数年間 AWS Lambda を使用したアプリケーション開発を中心に仕事をしていますが、AWS Lambda でも仕様上は Java は使用できるのですが、現実的には使用できない。と感じています。
これは AWS Lambda のコールドスタート時に特に体感します。コールドスタート時にはLambda を実行してから、実際に処理が動き始めるまで約7秒の時間を要します。
それに対して、Python は100ms前後です。この差によって私は開発言語を Python にせざるを得ませんでした。

しかし、多くの場合はプログラムの起動に7秒かかろうと気にならないことのほうが多いでしょうから、これはサーバレスアーキテクチャにおける特殊なニーズといえるかもしれません。

言語仕様が堅い

Java はとにかく堅い言語です。
その堅さは多くの場合にいい結果をもたらします。
たとえば、冗長なまでの型宣言ですが、他人の書いたプログラムを見る際には、どのオブジェクトにどのような値が入っているのかすぐに特定できます。

  1. public function hoge($fuga)
  2. {
  3.   $this->fizz($fuga->piyo);
  4.   return $this->buzz($fuga->piyopiyo);
  5. }

PHP にありがちなこのようなプログラムを見つけたときの絶望感は Java で味わうことはないでしょう。
$fuga は何者なのか、hoge の戻り値は何なのか? Java であればドキュメントがなくてもソースコードによって明示されています。

一方でこの堅さは悪い面にも作用します。

もっとも揶揄されるのが Getter/Setter でしょう。
Java で開発をする上で、

  1. private String hoge;
  2. public String getHoge() {
  3.   return hoge;
  4. }
  5. public void setHoge(String hoge) {
  6.   this.hoge = hoge;
  7. }

このようなコードがあちこちに現れることを許容しなければならないでしょう。

NullSafety ではない

ぬるぽ というスラングが生まれる程度に null にまつわる不具合は多いです。
Java もこれに対して抗おうとこの値はnullかもよ?という Optional 型を導入するなど努力はしています。

しかし、null が設定されうる Optional 型など何の役にも立たちません。

  1. Optional<Hoge> nullValue = null;
  2. if(nullValue.isPresent()) { // ここでぬるぽ
  3.   nullValue.get().fuga();
  4. }

Java が本当にぬるぽ問題に対して本気で取り組む姿勢を見せるのであれば、Optional でない型には null が設定できないようにしなければならなかったのでしょうが、10年前のコードでも動くJavaのバージョンに対する良さが失われる破壊的な変更になりますので、それは避けなければならなかったのでしょう。

ストリーム処理がださい

Java でもコレクションに対するストリーム処理ができるようになりました。

  1. List<String> itemNames = items.stream()
  2.   .map(Item::getName)
  3.   .collect(Collectors.toList());

以前の foreach を使った実装に比べれば100倍ましなのは事実なのですが

.stream() とか .collect() とか所々ダサいです。

前述の Optional型 によって、さらにこのストリームはだささを増します。

  1. Optional<Item> item = items.stream()
  2.   .filter(item -> item.getId() == 1000)
  3.   .first();
  4. if(item.isPresent()) {
  5.   item.get().action()
  6. }

そして Kotlin

Kotlin は JVM で動作し、Java と混ぜて使用できます。
これによって、多くの Java の資産をそのまま受け継いで使用できます。

にもかかわらず、Java のイマイチだった点を改善し、より使いやすい言語仕様にまとめあげています。

Getter/Setter

  1. class Hoge(
  2.   val fuga: Int
  3. )
  4. val hoge = Hoge(1)
  5. print(hoge.fuga)
  6. hoge.fuga = 100 // エラー: fuga は不変(val) 宣言なので書き換えられない

さらに Kotlin ではC#のプロパティメソッドのような機能も使用できます。

  1. class Hoge(
  2.   val fuga: Int
  3. ) {
  4.   val fuga2pow
  5.     get() = Math.pow(fuga.toDouble(), 2.0)
  6. }
  7. val hoge = Hoge(1)
  8. print(hoge.fuga2pow)

.toDouble() のあたりにダサさを感じざるを得ませんが、Kotlin の設計思想として暗黙の型キャストは if(variable is Class) で判定したときにしか作用しないという方針のようなので、ここは諦めるしか無さそうです。

NullSafety

Kotlin ではデフォルトではオブジェクトに null を代入できません。
つまり、プログラム側では null チェックなどせずに安心してオブジェクトを触れます。

逆に null が入っているかもしれないときは Hoge? という型で宣言することで、Hoge型だけど null がはいっているかもよ?と宣言します。

そして、Hoge のメソッドを触るときには hoge?.action() と呼び出せば、hoge がnullでなければ actionメソッドを呼びだす。という実装になり、null チェックもダサくありません。

ストリーム処理

  1. val itemNames = items.map(Item::name)

 

  1. val item = items.filter {
  2.   item -> item.id == 1000
  3. }.firstOrNull()
  4. item?.action()

Java でダサかった部分がかなり改善されて、理想的な形になっています。

ランタイムがでかい / JMがあたたまるまで時間がかかる

このあたりは JVM の問題ですので、JVMベースの Kotlin では解決できません。
しかし、Kotlin は ver1.1 で javascript をランタイムとして動作できるようになりました。
さらに Kotlin/Native というプロジェクトでは Kotlin で書かれたプログラムをネイティブコードにコンパイルする開発が進んでいます。
近い将来、組み込み開発にも Kotlin を使用できるかもしれません。

現在では開発する対象に合わせて開発言語を選定していますが、
Kotlin を使っておけばなんでもOK。となる足音を感じています。

 

すくなくとも、現時点でも Java の代わりに Kotlin を使用する。という面においてはメリットしかありません。
そのため私は第一言語としてのJavaを捨て、Kotlin に乗り換えることを決めました。

 

Kotlinスタートブック -新しいAndroidプログラミング

Kotlinスタートブック -新しいAndroidプログラミング