Java vs Kotlin: 7つの違いとコード例、比較表
JavaとKotlinの違いを比較しながら、Javaにおける課題とKotlinが提供する解決策について解説します。
1. 冗長なコード
Javaではシンプルなモデルクラスを作成するだけでも多くのボイラープレートが必要です。Kotlinはdata class
によって、ボイラープレートなしでequals
、hashCode
、toString
などが自動生成されます。
コード例
Java:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public boolean equals(Object o) { /* 省略 */ }
@Override
public int hashCode() { /* 省略 */ }
@Override
public String toString() { /* 省略 */ }
}
Kotlin:
data class User(val name: String, val age: Int)
2. Null安全の欠如
JavaではNullPointerException
が多発しやすく、開発者が手動でnullチェックを行う必要があります。KotlinではNullable型を区別するため、nullチェックの煩わしさが解消されます。
コード例
Java:
public String getUserName(User user) {
if (user == null) {
return "Unknown";
}
return user.getName();
}
Kotlin:
fun getUserName(user: User?): String {
return user?.name ?: "Unknown"
}
3. ラムダ式と関数型プログラミングの使いにくさ
Java 8からラムダ式が導入されましたが、Kotlinと比べると関数型プログラミングのサポートは不十分です。Kotlinはmap
やfilter
など豊富なメソッドを提供し、コレクション操作も簡潔に行えます。
コード例
Java:
List<String> names = users.stream()
.filter(user -> user.getAge() > 18)
.map(User::getName)
.collect(Collectors.toList());
Kotlin:
val names = users.filter { it.age > 18 }.map { it.name }
4. 古いバージョンとの互換性問題
Javaは後方互換性が重視されるため、企業によってはJava 8などの古いバージョンを使用し続けることが多く、最新機能にすぐアクセスできません。Kotlinは新しい言語であり、モダンな機能を取り入れやすいです。
JavaとKotlinのモダンな機能を、各言語バージョンで導入された特徴とともに比較します。Javaは後方互換性を重視しているため、新しい機能が登場するまでに時間がかかる傾向があり、企業によっては古いバージョンが使われ続けています。一方、Kotlinはモダンな機能を初期から多く取り入れており、アップデートも比較的早く行われています。
比較: JavaとKotlinのモダンな機能
機能 | Kotlin | Java |
---|---|---|
ヒアドキュメント | Kotlin 1.0からサポート | Java 13(Text Blocks)で導入 |
Record型 | Kotlinのdata class で代替 |
Java 16で導入 |
Pattern Matching | Kotlin 1.0からwhen を利用 |
Java 17以降で段階的に導入中 |
デフォルト引数 | Kotlin 1.0からサポート | Javaでは未サポート、オーバーロードで代替 |
シングルトン | Kotlin 1.0からobject でサポート |
Javaは標準ライブラリでサポートされておらず、通常はenum を利用 |
1. ヒアドキュメント
JavaではJava 13からText Blocks
としてヒアドキュメントが導入されましたが、Kotlinでは初期バージョンから利用可能です。
コード例: ヒアドキュメント
Java 13以降:
public class Example {
public static void main(String[] args) {
String textBlock = """
This is a text block.
It allows multiple lines.
No need for newline characters.
""";
System.out.println(textBlock);
}
}
Kotlin:
val textBlock = """
This is a text block.
It allows multiple lines.
No need for newline characters.
"""
println(textBlock)
2. Record型
Javaでは、シンプルなデータキャリアクラスを簡潔に定義するためにRecord
がJava 16で導入されました。一方、Kotlinは当初からdata class
を提供しており、より短い構文でデータキャリアクラスを定義できます。
コード例: Record型
Java 16以降:
public record User(String name, int age) {}
Kotlin:
data class User(val name: String, val age: Int)
3. Pattern Matching
JavaではJava 17からパターンマッチングが段階的に導入されていますが、Kotlinでは初期からwhen
構文で簡単にパターンマッチングが可能です。
コード例: Pattern Matching
Java 17以降:
Object obj = "Hello";
switch (obj) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer: " + i);
default -> System.out.println("Unknown type");
}
Kotlin:
val obj: Any = "Hello"
when (obj) {
is String -> println("String: $obj")
is Int -> println("Integer: $obj")
else -> println("Unknown type")
}
4. デフォルト引数
Javaではデフォルト引数がサポートされていないため、代わりにメソッドオーバーロードで対応する必要があります。Kotlinは初期からデフォルト引数をサポートしており、メソッド定義が簡潔になります。
コード例: デフォルト引数
Java:
public class User {
private String name;
private int age;
public User(String name) {
this(name, 0);
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
Kotlin:
data class User(val name: String, val age: Int = 0)
5. シングルトン
Kotlinはobject
キーワードを使ってシングルトンオブジェクトを簡単に定義できますが、Javaにはシングルトンの標準サポートがないため、通常はenum
を使用するか、明示的な実装が必要です。
コード例: シングルトン
Java:
public enum Singleton {
INSTANCE;
}
Kotlin:
object Singleton
5. チェック例外
Javaのチェック例外は、例外処理のために多くのtry-catch
ブロックが必要で、コードが冗長になります。Kotlinにはチェック例外がなく、開発者の判断でエラーハンドリングが行えます。
コード例
Java:
public void readFile(String path) {
try {
BufferedReader reader = new BufferedReader(new FileReader(path));
// ファイル処理
} catch (IOException e) {
e.printStackTrace();
}
}
Kotlin:
fun readFile(path: String) {
val reader = BufferedReader(FileReader(path))
// ファイル処理
}
6. 記述量の多いアクセス修飾子の使用
Javaでは、クラスやメソッドにpublic
、private
などのアクセス修飾子を明示的に記述する必要があり、特にプロジェクトが大きくなるほど冗長に感じます。Kotlinはデフォルトでpublic
扱いにするなど、アクセス制御がシンプルです。
コード例
Java:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
private void calculateAge() { /* 年齢計算 */ }
}
Kotlin:
class User(val name: String, val age: Int) {
private fun calculateAge() { /* 年齢計算 */ }
}
7. Nullチェックの記述が多い
Javaではnull安全が標準でサポートされていないため、nullチェックが必須となり、コードの可読性を低下させます。Kotlinは、Nullable型とNon-Nullable型の区別が明確なため、NullPointerExceptionを防ぎやすい設計になっています。
Kotlinは、Nullable型とNon-Nullable型が明確に区別されているため、NullPointerException(NPE)を防ぎやすい設計になっています。そもそもNon-Nullable型として宣言された変数には、null
を設定できないため、コンパイルエラーが発生します。これにより、開発段階でnullの割り当てミスを防ぐことができます。
Non-Nullable型の例
Kotlinでは、変数をString
型として宣言すると、その変数にはnull
を代入できません。これにより、意図しないNullPointerException
が防げます。
val name: String = "John"
// name = null // エラー: Non-Nullable型の変数にnullを代入できません
上記の例でname
はNon-Nullable型として宣言されているため、nullの代入を試みるとコンパイルエラーが発生します。このように、Kotlinではnullableかどうかを明確に区別できるため、実行時にNPEが発生しにくくなっています。
Nullable型の例
Nullable型の変数を使用する場合は、?
を付けて明示的にNullableとして宣言する必要があります。これにより、変数がnullの可能性があることが明確になります。
val name: String? = "John"
val emptyName: String? = null // Nullable型にはnullを代入可能
このように、String?
のように?
を付けてNullable型として宣言することで、null
が代入できる変数となります。このNullable型とNon-Nullable型の区別により、KotlinはNPEを防ぎやすい設計となっています。
Kotlinのこうした設計により、変数宣言時点でNullableかNon-Nullableかを明確にでき、予期しないnullの割り当てを防ぐことが可能です。これによって、コードの安全性が高まり、実行時エラーも減少します。
比較表
特徴 | Javaの特徴 | Kotlinの特徴 |
---|---|---|
冗長なコード | Getter/Setterやequals 等のコードが多い |
data class でシンプルな定義 |
Null安全の欠如 |
NullPointerException が多発 |
Nullable型とNon-Nullable型を区別 |
ラムダ式と関数型の制限 | ラムダ式が限定的、コレクション操作が冗長 | 豊富な関数型サポートで、シンプルなコレクション操作 |
古いバージョンの互換性問題 | 最新機能の普及に時間がかかる | モダンな機能を取り入れやすい |
チェック例外 | try-catchで冗長なコードになる | チェック例外がないため、シンプルなエラーハンドリング |
記述量の多いアクセス修飾子 | メソッドやフィールドにpublic やprivate が必須 |
デフォルトpublic で簡潔なアクセス制御 |
Nullチェックの煩わしさ | 開発者が手動でnullチェックを行う必要がある | 言語レベルでのnull安全サポート |