「モダン Java の書き方」とは何か
ここでいう「モダン Java」は、
Java 8 以降で手に入った機能や考え方を前提にした書き方のことだと思ってください。
昔ながらの「for 文だらけ・null だらけ・setter だらけ」の Java から、
ラムダ、Stream、Optional、イミュータブル、record、var などを使いながら、
読みやすく、安全で、意図が伝わるコードにしていく。
その方向性を、初心者向けに具体的なコードでかみ砕いていきます。
var と型推論:読みやすさを壊さない範囲で使う
var は「何でも省略していい魔法」ではない
Java 10 以降では、ローカル変数に var が使えます。
var name = "Alice";
var list = new ArrayList<String>();
Java型が右辺から明らかで、読む人が迷わないなら var はとても便利です。
ただし、何でもかんでも var にすると、逆に読みづらくなります。
var x = service.process(data); // x って何?
Javaモダンな書き方では、「型が明らかなところだけ var を使う」がちょうどいいです。
var users = List.of(new User("Alice"), new User("Bob"));
for (var user : users) {
System.out.println(user.name());
}
Java右辺や文脈から「users は List<User> だな」「user は User だな」とすぐ分かる。
こういう場所では、var はノイズを減らしてくれます。
record で「データだけのクラス」を簡潔に書く
getter・setter・equals・hashCode 地獄からの解放
「値をまとめるだけのクラス」を書くとき、昔はこうでした。
public class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
// equals, hashCode, toString を IDE で自動生成…
}
Javaモダン Java では、record を使ってこう書けます。
public record User(String name, int age) {}
Javaこれだけで、コンストラクタ、アクセサ(name() / age())、equals、hashCode、toString が自動生成されます。
しかもイミュータブルです。
「ただのデータキャリア」は、まず record を検討する。
これがモダン Java の感覚にかなり近いです。
Stream とラムダ:ループを「意図」で書く
「どうやるか」より「何をしたいか」を書く
例えば、「名前のリストから、長さ 3 以上のものだけを大文字にして結合する」という処理を考えます。
昔ながらの書き方はこうなります。
List<String> names = List.of("a", "bob", "carol");
StringBuilder sb = new StringBuilder();
for (String name : names) {
if (name.length() >= 3) {
sb.append(name.toUpperCase());
}
}
String result = sb.toString();
Javaモダンな書き方では、Stream を使って「意図」をそのまま並べます。
List<String> names = List.of("a", "bob", "carol");
String result = names.stream()
.filter(name -> name.length() >= 3)
.map(String::toUpperCase)
.collect(Collectors.joining());
Javafilter で「条件」、map で「変換」、collect で「集約」。
「何をしたいか」が縦に並ぶので、慣れるとこちらの方が読みやすくなります。
ただし、Stream が複雑になりすぎると逆に読みにくくなるので、
「2〜3 ステップで書ける処理」に向いている、と覚えておくとバランスが取りやすいです。
Optional と null:null 前提の世界から抜け出す
「null が来るかも」を型で表現する
昔の Java では、「値がないかもしれない」を null で表現していました。
User findUser(String id) {
// 見つからなければ null を返す
}
Java呼び出し側は、毎回こう書く必要があります。
User user = findUser(id);
if (user != null) {
System.out.println(user.getName());
}
Javaモダン Java では、「ないかもしれない」を Optional で表現します。
Optional<User> findUser(String id) {
...
}
Java呼び出し側は、Optional として扱います。
findUser(id)
.ifPresent(user -> System.out.println(user.name()));
Javaあるいは、デフォルト値を決めることもできます。
User user = findUser(id)
.orElseGet(() -> new User("guest", 0));
Java大事なのは、「null かもしれない」をコメントではなく型で表現すること。
これがモダン Java の設計の感覚です。
try-with-resources と例外:後片付きを自動化する
finally でのクローズ地獄からの卒業
昔は、リソースを扱うときにこう書いていました。
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
System.out.println(line);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// ログだけ出すなど
}
}
}
Javaモダン Java では、try-with-resources を使ってこう書けます。
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
}
Javaスコープを抜けるときに自動で close() してくれます。
「開いたら閉じる」というお約束を、構文レベルで保証してくれる。
ファイル、ソケット、DB コネクションなど、「閉じ忘れるとまずいもの」は、
モダン Java では基本的に try-with-resources で扱います。
switch 式・パターンマッチング:分岐を「表」として書く
switch を「式」として使う
従来の switch は文(statement)でしたが、
モダン Java では「値を返す switch 式」が使えます。
String label = switch (status) {
case 200 -> "OK";
case 404 -> "NOT_FOUND";
case 500 -> "ERROR";
default -> "UNKNOWN";
};
Java昔ながらの書き方と比べると、かなりスッキリします。
String label;
switch (status) {
case 200:
label = "OK";
break;
case 404:
label = "NOT_FOUND";
break;
...
}
Java「入力に対して出力を対応付ける」という処理は、
switch 式で書くと「表」のように見えて、意図が伝わりやすくなります。
パターンマッチング(イメージだけ)
最近の Java では、instanceof とパターンマッチングを組み合わせて、
型チェックとキャストをまとめて書けるようになっています。
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
Java昔はこうでした。
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
Java細かい話に見えますが、「型チェック+キャスト」のお決まりパターンを短く書けるのは、
日常的なコードのノイズを減らすうえでかなり効きます。
モダン Java の「考え方」の軸
イミュータブルを基本にする
モダン Java では、「できるだけイミュータブル」を基本にします。
record、private final フィールド、セッターなし、withXxx メソッドなど。
状態がコロコロ変わるオブジェクトより、
「一度作ったら変わらないオブジェクト」の方が、
マルチスレッドでも安全で、テストもしやすく、読みやすい。
null より Optional、配列よりコレクション
「ないかもしれない」は null ではなく Optional。
「固定長の配列」より「List や Set」。
こうした選択は、単なる好みではなく、
「意図を型で表現する」「安全なデフォルトを選ぶ」という設計の姿勢です。
命令型から「宣言的」に一歩寄せる
for 文と if 文で「どうやるか」を細かく書くより、
Stream やメソッド名で「何をしたいか」を表現する。
これは、モダン Java が「関数型のエッセンス」を取り込んできた結果でもあります。
全部を Stream で書く必要はありませんが、
「ここは宣言的に書いた方が意図が伝わるな」という場所では、
積極的にラムダや Stream を使うと、コードの質が一段上がります。
まとめ:モダン Java の書き方を自分の言葉で説明するなら
あなたの言葉で整理すると、こうなります。
「モダン Java の書き方とは、
record や var、Stream、Optional、try-with-resources、switch 式などを使いながら、
『意図が伝わる』『安全』『変更に強い』コードを書くスタイル。
データだけのクラスは record で簡潔に、
null かもしれない値は Optional で型として表現し、
ループや条件分岐は Stream や switch 式で“何をしたいか”をそのまま書く。
イミュータブルを基本にしつつ、リソースの後片付きは構文に任せる。
つまり、
“昔の Java の書き方をそのまま続ける”のではなく、
言語が進化して手に入った道具をちゃんと使って、
読みやすくて壊れにくいコードにしていくこと。」
