Java
melangの各noteで他言語と比較した、Javaの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
型注釈を書く場面が比較的多い。
int age = 20;int age = 20;
String name = "Alice";ローカル変数の型推論(`var`)はJava 10以降で使えるが、基本的には型を明示して書くスタイル。
型注釈の位置
型が変数名より前に来るprefix方式。推論はローカル変数の`var`に限定。
int age = 20;int age = 20;
var name = "Alice"; // Java 10以降、ローカル変数のみ推論できる
String greet(String name) { // 引数・戻り値は常にprefixで明示
return "Hello, " + name;
}`Type name`という前置構文が基本で、フィールド・引数・戻り値すべてに一貫して適用される。`var`はローカル変数宣言にのみ使え、引数や戻り値の型推論は言語仕様として存在しない。
型変換
キャスト構文と、失敗時に例外を投げるAPIを併用する。
int y = (int) x;long big = 3_000_000_000L;
int a = (int) big; // 黙って切り捨てられる
int b = Math.toIntExact(big); // 範囲外ならArithmeticException
int n = Integer.parseInt("42");
int m = Integer.parseInt("abc"); // NumberFormatException
Number num = 10;
long l = num.longValue(); // メソッド形式の変換もある基本はキャスト構文で、縮小変換は黙って値を切り捨てる。範囲外を検出したい場合は`Math.toIntExact`のような別のAPIを選ぶ必要がある。文字列のパースは`parseInt`が検査例外ではない`NumberFormatException`を投げるため、失敗可能性がシグネチャに現れない。
暗黙の型変換
拡大変換は暗黙、縮小変換は明示が必要。
int x = 10;int x = 10;
long y = x; // 暗黙にlongへ拡大変換される
long a = 10;
int b = 20;
long c = a + b; // bがlongへ暗黙変換されてから加算される
short s = (short) x; // 縮小変換にはキャストが必要intからlong、floatからdoubleのような「情報を失わない」拡大変換(widening)は暗黙に行われる。逆に縮小変換(narrowing)は明示的なキャストを要求する。二項演算子のオペランドも、大きい方の型に暗黙に合わせられる(binary numeric promotion)。
Enum / Sum Type
enumはクラス的な機能を持つが、ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。はsealed + recordで表現する。
sealed interface Result permits Success, Failure {}sealed interface Result permits Success, Failure {}
record Success(User user) implements Result {}
record Failure(String message) implements Result {}
String describe(Result r) {
return switch (r) {
case Success s -> "ok: " + s.user();
case Failure f -> "error: " + f.message();
}; // sealedを網羅しないとコンパイルエラー
}`enum`はクラスとして状態・メソッドを持てるが、variantごとに異なるフィールド構成を持つADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。ではない。ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。用途は`sealed interface` + `record`で表現し、switch expressionと組み合わせれば網羅性を検査できる。
値の不変性
`final`は参照の再代入を防ぐだけで、参照先の変更は防がない。
final T x = ...; / record User(String name) {}final List<String> tags = new ArrayList<>();
// tags = new ArrayList<>(); // コンパイルエラー: 再代入
tags.add("admin"); // OK: 参照先は変更できる
record User(String name) {} // フィールドはfinal(浅い不変)
var fixed = List.of("a", "b"); // 不変コレクション
// fixed.add("c"); // 実行時にUnsupportedOperationException`final`は束縛(変数・フィールド)を固定するだけで、参照先のオブジェクトは変更できる。`record`や`List.of`で不変にできるが、選ぶのは書き手である。不変コレクションの違反は、コンパイル時ではなく実行時に例外として現れる。
名前付き引数
名前付き引数はなく、オプションが多い場合はBuilderパターンで代替する。
void foo(int width, int height)void foo(int width, int height) {}
foo(10, 20); // 引数名は呼び出し側に現れない
new PersonBuilder().width(10).height(20).build(); // Builderで意味を明示言語機能としての名前付き引数はない。引数が増えた場合はBuilderパターンでメソッド名に意味を持たせることが多い。
関数の引数
すべて値渡し。オブジェクトは参照の値が渡され、中身は共有される。
void f(int n, StringBuilder sb)void f(int n, StringBuilder sb) {
n = 1; // 呼び出し側には影響しない
sb.append("a"); // 呼び出し側から見える(同じオブジェクト)
}引数はすべて値渡しだが、オブジェクトの場合は参照の値がコピーされるため、中身を書き換えると呼び出し側から見える。「値渡しなのに参照のように見える」という誤解を招きやすい。渡し方を選ぶ構文はなく、呼び出し側にも印はない。melangの渡し方はこれに近いが、オブジェクトが不変なので、共有された中身を書き換えられることはない。
if 文/式
`if`はstatement。値の分岐には三項演算子`?:`やswitch式を使う。
if (cond) { ... } else { ... } / cond ? a : bint x = cond ? 1 : 2; // 三項演算子は式で、elseに相当する側が必須
if (cond) {
process();
} else {
handle();
}`if`はstatementで、値を返せない。値の分岐は三項演算子(両側が必須)かswitch式で書く。「手続きは`if`、値は別の構文」と分かれているが、手続きの`else`は自由に使える。
I/O の明示
checked exceptionの`throws`が、I/Oの可能性を間接的に示す。
String readFile(String path) throws IOExceptionString readFile(String path) throws IOException {
return Files.readString(Path.of(path));
}
int square(int x) throws IOException {
readFile("x"); // 呼ぶなら自分もthrowsを宣言する必要がある
return x * x;
}`IOException`はchecked exceptionのため、呼び出し元はcatchするか自身の`throws`に伝播させる必要があり、`io`の伝播規則に近い構造を持つ。ただし目的は例外処理でありeffectの分類ではなく、unchecked例外を投げるI/O API(一部のNIOなど)は対象外になる。
言語名
地名・飲料名に由来する固有名詞で、衝突は少ないが商標管理が絡む。
インドネシアの地名兼コーヒーの俗称に由来する固有名詞で、一般的なプログラミング用語との衝突は少ない。一方で`Java`は当初Sun Microsystems、現在はOracleの商標であり、言語名が単なる識別子ではなく法的に管理された商標である点が、コミュニティ主導のOSS言語とは異なる性質を持つ。