Java経験者のためのKotlin入門

こんにちは、システム事業部の岸本です。
この記事は、Javaは触ったことがあるけれどKotlinは触ったことがない、あるいはKotlinまったく知らない、という人向けの記事になります。

私は現在、Java(フレームワークはSpringBoot)を使ったWebアプリケーションの開発案件に携わっています。
一方、前職時代にKotlin(フレームワークはSpringBoot)を使ったWebアプリケーションの開発にも関わっていたことがあります。
Kotlinを使った開発を経験した後にJavaの案件を経験すると、「Kotlinだったらここもっと楽に実現できるのになー」と思うことが多々ありました。

Javaは現在でも多くの現場で使われている技術であり、そしてまだまだ現役で進化し続けている言語でもあります。
そのため、新規の開発案件でプログラミング言語の候補をJavaとして挙げる企業は多いかと思いますが、Javaを技術選定の候補にしている企業が、Kotlinも候補の1つとして検討されると良いなと思っています。
そこで、ここではJavaをある程度知っている人向けにKotlinを簡単に紹介したいと思います。

Kotlinとは

Kotlin(コトリン)は、JetBrains社が開発したプログラミング言語です。
KotlinはGoogleがAndroid開発の公式言語として採用しています。そのため、知名度は高いですが、Android開発でしか使用しないプログラミング言語だと認識している人も多い印象です。
実際は、KotlinはAndroid以外の開発でも幅広く活躍できる言語です。

Kotlinは一言で説明するなら、Javaをより安全に、かつ簡潔に書くことができる言語です。Javaと互換性があるため、Javaが動作する環境であればKotlinで書いたプログラムも動作させることができます。

Javaを触ったことがある人ならご存知かと思いますが、Javaで書かれたプログラム(Javaファイル)をコンパイルするとクラスファイルと呼ばれる中間コードになります。そしてclassファイルはJVM(Java仮想マシン)上で動作します。
Kotlinで書かれたプログラム(拡張子.ktのファイル)も、コンパイルするとクラスファイルになり、JVM上で動作します。そのため、Javaとの互換性があり、Javaが動作する環境であれば同様にKotlinのプログラムも動作させることができます。Javaで使用可能なライブラリなどのエコシステムは、Kotlinでもそのまま使用することができます。

例えば、JavaのWebアプリケーション開発でよく利用されているフレームワークにSpringBootがありますが、SpringBootもKotlinで開発することが可能です。
SpringBootのプロジェクトを初期化するのによく使用されるspring initializrでも言語としてKotlinを選択することが可能です。(というか、デフォルトの選択肢がKotlinになっています。)

Kotlinによる安全な開発

KotlinはJavaを安全に、簡潔に書くことができる言語です。
そもそもJavaは静的型付け言語であり、コンパイル時に型チェックが行われるため、Pythonなどの動的型付け言語に比べれば比較的安全に開発がしやすい言語です。
そのうえで、Kotlinではより安全に開発ができるようになっています。

nullによるエラーの発生率を下げる

Javaでアプリケーションを開発する際に最も多く遭遇するエラーの一つがNullPointerExceptionでしょう。Javaを学習したり、Javaで開発をした経験がある人はこのエラーを何度も目にしたことがあるでしょう。
Javaで開発を行うときは動作検証しながらエラーに対処していく必要があり、NullPointerExceptionは厄介な問題の一つです。

一方、Kotlinの場合ですが、Kotlinでは型指定をした場合にデフォルトではNull非許容になっていて、コンパイル時点でNullPointerExceptionが発生するリスクをかなり減らすことができるようになっています。

例えば、Javaで以下のようなコードがあったとします。

public class Main {  
  
    public static void main(String[] args) {  
        String str = null;  
        judgeStringLength(str);  // エラーになる  
    }  
  
    public static void judgeStringLength(String str) {  
        if (str.length() > 10) { // NullPointerExceptionが発生する  
            System.out.println("String is longer than 10 characters.");  
        } else {  
            System.out.println("String is 10 characters or shorter.");  
        }  
    }  
}

このコードはコンパイルエラーにはなりませんが、str変数にnullが入っているため、str.lengthNullPointerExceptionが発生します。
この程度のコード量であれば原因がどこにあるのか簡単に見つけることができますが、コード量が増えてくると、どの時点で値がNullになったのかをデバッグするのも時間がかかるようになります。

KotlinではデフォルトでNull非許容なので、同じようなコードを書こうとするとnullを代入している時点でエラーになります。

fun main() {  
    val name: String = null // ここでコンパイルエラー  
    judgeStringLength(name)  
}  
  
fun judgeStringLength(str: String) {  
    if (str.length > 10) { 
	    println("String is longer than 10 characters.")  
	} else {  
	    println("String is 10 characters or shorter.")  
	}
}

Null許容にするには、型の後ろに?を付ける必要があります。
その場合は代入はできますが、nullが許容されている変数で普通にプロパティやメソッドにアクセスしようとするとコンパイルエラーになります。

fun main() {  
    val name: String? = null // ?を付けるとnull許容
    judgeStringLength(name)  
}  

// 引数の型にも?をつける
fun judgeStringLength(str: String?) {  
    if (str.length > 10) { // ここでコンパイルエラー  
        println("String is longer than 10 characters.")  
    } else {  
        println("String is 10 characters or shorter.")  
    }  
}

ここでコンパイルエラーを対処するには2つの方法があります。

1つ目の方法は、nullかどうかを判定して値が確実に入る状態を作ること。
?.演算子を使うと、Null許容のオブジェクトのプロパティやメソッドにもアクセスできるようになり、オブジェクトがnullだった場合は全体がnullとして評価されます。
?:演算子は、左辺がnullだった場合に右辺の値を返す演算子です。
こうすることで、NullPointerExceptionが発生する可能性がなくなります。

val length = str?.length ?: 0 // nullの場合は0を代入  
if (length > 10) {

2つ目の方法は、変数の後ろに!!を付けることです。
こうすると、オブジェクトがnullになっている可能性を無視して強制的にプロパティやメソッドにアクセスできます。つまり、この場合はNullPointerExceptionが発生するリスクは残ることになります。

if (str!!.length > 10) {

まとめると、Kotlinではnullの扱いが以下になります。

  • デフォルトではnull非許容の型になる
  • null許容したい場合は型に?を付ける必要がある
  • null許容しているオブジェクトのメソッドやプロパティに.でそのままアクセスしようとするとコンパイルエラーになる
  • null許容の型は?.?:演算子を使って安全性を担保することでエラーが消える
  • !!を付けることで、NullPointerExceptionのリスクを許容してオブジェクトを扱える

このように、Kotlinではコンパイル時点でnullによるエラーのリスクを最小限に抑えることが可能になっています。!!を付けることで強制的にnull許容されたオブジェクトを扱うことができますが、エクスクラメーションマーク(!)が2つも並んでいると直感的にリスクがあるようにも感じ、心理的にできれば使用したくない思いに駆られます。
このような工夫もあって、KotlinではJavaに比べると安全な開発がしやすくなっています。

簡潔さ

先に紹介したサンプルコードを見ただけでも、KotlinはJavaに比べてすっきり書けることが分かるかと思います。
関数(メソッド)の宣言を見ても、Kotlinだとかんりすっきり書くことができます。

public static void judgeStringLength(String str) {  
	// ...
}
fun judgeStringLength(str: String) {  
}

最代入不可の変数を宣言するとき、Javaでは変数宣言にfinalを付ける必要があります。

final var num = 10;

Kotlinではvalだけで宣言できます。また、kotlinでは文の末尾にセミコロンが不要です。

val num = 10

Kotlinでは他にも様々な文法でJavaよりも簡潔に書けることが多いです。
最近はAIでコード補完してもらったり、AIエージェントにコードを書いてもらうことも増え、昔に比べると言語特有の記述量の多さは気にならなくなった部分もありますが、長期にわたってコードをメンテナンスしていくにあたっては、簡潔に書かれている方がうれしいです。

また、ドメイン駆動設計(DDD)やクリーンアーキテクチャという設計の概念が導入されることも近年増えていますが、このような設計思想はどうしてもファイルの数が多くなりがちで、記述量も多くなる傾向が、体感としてあります。そのような、記述量が増えるような設計方針を採用する場合、記述が簡潔にできる言語を選ぶ方が、開発体験としては好ましいです。

便利な機能

個人的に、Javaでこの機能が使えると便利なのになーと思った機能をいくつか紹介します。
他の言語では当たり前にできるものもありますが、あくまでJavaとの比較という観点で紹介します。

文字列テンプレート

文字列の中に変数の値を埋め込みたい場面は多々あります。
動的型付けの言語では当たり前にサポートされていることが多いですが、Javaではサポートされておらず、文字列を結合しながら実現する必要がありました。
Kotlinでは$変数名を文字列に埋め込むことで、文字列に変数を展開することが可能です。
式を埋め込みたい場合は${}を使用します。

println("Hello, $name! You are $age years old and live in $city.")

関数のデフォルト値と引数の指定

Kotlinでは、関数(メソッド)の引数にデフォルト値を設定することができます。
これも、他の言語ではサポートされていることはありますが、Javaではサポートされていません。

fun greet(name: String = "Guest", age: Int = 0, city: String = "Unknown") {  
    println("Hello, $name! You are $age years old and live in $city.")   
}

関数を呼び出す際は、デフォルト値が設定されている引数は省略することが可能です。
また、関数呼び出し時は、どの引数に値を渡すのかを指定することができます。

fun main() {  
    greet() // デフォルト値を使用  
    greet(name = "Alice") // name のみ指定  
    greet(age = 10) // age のみ指定  
    greet(name = "Bob", age = 25) // name と age を指定  
    greet(name = "Charlie", age = 30, city = "New York") // 全ての引数を指定  
}

このような仕組みは、フィールド(プロパティ)の多いクラスのコンストラクタを定義する際に非常に便利です。
引数の数が多いコンストラクタでは、引数の順番によるミスが生じやすく、可読性も悪くなります。
引数を指定することができるようになると、可読性を高く保てます。
また、オブジェクトの生成に必要となるフィールドのパターンが複数ある場合、Javaではオーバーロードで引数のシグネチャが異なるコンストラクタを複数作成する必要があるのに対し、Kotlinではデフォルト値を設定することで、コンストラクタを複数作成しなくても様々なオブジェクトの生成に対応できるようになります。

拡張関数

Javaではオブジェクトが持つメソッドはそのクラス宣言の中に定義する必要があります。
Kotlinでは、そのクラス定義の外側で、関数を拡張して定義することができます。
これは、自作のクラスに限らず、ライブラリとして用意されているクラスにも可能です。
例えば、以下はStringクラスにlastChar()メソッドを拡張して定義しています。

fun main() {  
   val str = "Kotlin"  
    println(str.lastChar())  // 実行結果:n
}  
  
fun String.lastChar(): Char {  
    return this[this.length - 1]  
}

個人的に、この機能はクラス間の依存関係を疎結合に保つのに役立つ便利な機能だなと感じています。
あるクラスのオブジェクトを別のクラスのオブジェクトに変換したい場合はよくあります。(例えば、DBのテーブルに対応するDTOから、ドメインのエンティティや画面のFormクラスに対応したDTOに変換する場合など)
この時、変換用のメソッドを作りたくなることがありますが、そのメソッドをどのクラスに定義するかはよく悩みます。とくに、クリーンアーキテクチャといった概念を導入している場合、依存の向きは重要なので、どのクラスに対して依存関係が発生するかを考える必要があります。
拡張関数の機能が使えると、変換の処理を行っているクラスで、変換用の関数を拡張関数として定義することで、ソースコード上は依存関係を疎結合に保つことができます。

まずはJavaの学習から

Kotlinは優れた言語ですが、Kotlinに触るのはある程度Javaの知識がある状態が望ましいとも感じています。
Kotlinでの開発をするにはJDKが必要で、プログラムを動作させるにはJVMが必要です。
ビルドツールとしてMavenやGradleなども使用します。
Javaに関してのある程度の知識があれば、Kotlinの学習コストはそれほど高くありませんが、Javaをほとんど知らない状態でKotlinの学習を始めると、学習コストはそれなりに高くなると思います。
逆にJavaの知識をある程度持っていると、Kotlinの便利さをより実感できるかと思います。

Kotlinを触ってみたいけど、Javaに関しての知識がないという方は、まずはJavaの基本的な知識を身に付けた後にKotlinを学習するのがおすすめです。

技術選定は慎重に

現状KotlinはJavaと比較して有利な面が多くありますが、この先ずっとKotlinの方がJavaより優れている保証はありません。Javaは現在でもバージョンアップを重ね、進化し続けている言語です。いつかJavaのバージョンアップによってKotlinと機能的に差異がなくなり、Kotlinを使うメリットがなくなってしまう可能性もゼロとは言い切れません。

とはいえ、この記事を書いている時点ではKotlinを使う利点は大きいと感じています。
技術選定としてJavaが第一候補に挙がる案件に関わっている場合、Kotlinも候補の一つに選んでみてはいかがでしょうか。


システム開発に関するご依頼・お問い合わせはこちらから。

株式会社テクノコア – Committing the Service

株式会社テクノコアは、1999年創業のIT教育・ITサービス企業です。未経験からでも成長できる環境で、新しい挑戦をしませんか?

https://www.techno-core.jp/system-contact

===========

新人研修に関するご依頼・お問い合わせはこちらから。

株式会社テクノコア – Committing the Service

株式会社テクノコアは、1999年創業のIT教育・ITサービス企業です。未経験からでも成長できる環境で、新しい挑戦をしませんか?

https://www.techno-core.jp/contact

============

採用に関するご応募はこちらから。

株式会社テクノコア – Committing the Service

株式会社テクノコアは、1999年創業のIT教育・ITサービス企業です。未経験からでも成長できる環境で、新しい挑戦をしませんか?

https://www.techno-core.jp/recruit