3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト

「Javaって古くない?」「もっとモダンな言語がいいのでは?」

新人エンジニアの方から、こういった声をよく聞きます。気持ちはわかります。SNSでは新しい言語やフレームワークの話題が飛び交っていますし、Javaのコードは一見すると長くて堅苦しく見えるかもしれません。

でも、実際にJavaで仕事をしてみると「この言語、よく考えられているな」と感じる場面がたくさんあります。

この記事では、Javaをこれから学ぶ方・学び始めたばかりの方に向けて、Javaの良さを正面から、具体的なコード例とともに伝えます。


1. 型安全:「動かしてみたらエラー」を減らせる

Javaは静的型付け言語です。変数や引数、戻り値にはすべて型があり、型の不整合はコンパイル時に検出されます。

import java.util.ArrayList;
import java.util.List;

public class TypeSafeExample {
    public static void main(String[] args) {
        // コンパイル時に型の不整合を検出
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");

        // names.add(123); // コンパイルエラー!String型のリストにintは入れられない

        for (String name : names) {
            System.out.println(name.toUpperCase());
        }
    }
}
ALICE
BOB

動的型付け言語では「実行してみたら型が違ってエラー」ということが起きがちですが、Javaではコンパイルの段階で防げます。プロジェクトの規模が大きくなるほど、この恩恵は大きくなります。

IDEの補完・リファクタリング機能が強力に働くのも、型情報があるからこそです。


2. ボイラープレートの削減:recordで「書かなくていいコード」を書かない

「Javaはコードが長い」という印象を持っている方もいるかもしれません。かつては事実でした。しかし、Java 16以降のrecordによって状況は大きく変わっています。

従来の書き方(Java 15以前)

イミュータブルなデータクラスを定義するには、コンストラクタ、getter、equals()hashCode()toString() をすべて手書きする必要がありました。

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 name() { return name; }
    public int age() { return age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User user)) return false;
        return age == user.age && name.equals(user.name);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "User[name=" + name + ", age=" + age + "]";
    }
}

recordなら1行

public class RecordExample {

    // record:たった1行でイミュータブルなデータクラスを定義
    record User(String name, int age) {}

    public static void main(String[] args) {
        User user1 = new User("田中太郎", 25);
        User user2 = new User("田中太郎", 25);

        // toString() が自動生成される
        System.out.println(user1);

        // equals() / hashCode() も自動生成される
        System.out.println("同一内容か: " + user1.equals(user2));

        // アクセサメソッドも自動生成
        System.out.println("名前: " + user1.name());
        System.out.println("年齢: " + user1.age());
    }
}
User[name=田中太郎, age=25]
同一内容か: true
名前: 田中太郎
年齢: 25

たった1行の定義で、コンストラクタ・アクセサ・equals()hashCode()toString() がすべて自動生成されます。「Javaは冗長」という時代は終わりつつあります。


3. sealed + パターンマッチング:型で「漏れ」を防ぐ設計

Java 17で正式導入されたsealed class/interfaceと、Java 21で完成したパターンマッチングの組み合わせは、Javaの表現力を大きく引き上げました。

public class SealedExample {

    // sealed:許可されたサブクラスだけが継承できる
    sealed interface Shape permits Circle, Rectangle, Triangle {}

    record Circle(double radius) implements Shape {}
    record Rectangle(double width, double height) implements Shape {}
    record Triangle(double base, double height) implements Shape {}

    // パターンマッチングで網羅的に処理(Java 21)
    static double area(Shape shape) {
        return switch (shape) {
            case Circle c    -> Math.PI * c.radius() * c.radius();
            case Rectangle r -> r.width() * r.height();
            case Triangle t  -> 0.5 * t.base() * t.height();
        };
    }

    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rect = new Rectangle(4.0, 6.0);
        Shape tri = new Triangle(3.0, 8.0);

        System.out.printf("円の面積: %.2f%n", area(circle));
        System.out.printf("長方形の面積: %.2f%n", area(rect));
        System.out.printf("三角形の面積: %.2f%n", area(tri));
    }
}
円の面積: 78.54
長方形の面積: 24.00
三角形の面積: 12.00

ここで注目すべきは、switch文にdefaultケースがないことです。Shapeはsealed interfaceなので、コンパイラが「すべてのサブタイプを網羅しているか」を検証してくれます。もし将来 Pentagon を追加したら、この switch文はコンパイルエラーになり、対応漏れを確実に検知できます。

「実行時に初めて気づくバグ」ではなく、「コンパイル時に防ぐ仕組み」。これがJavaらしい設計思想です。


4. Stream API:データ処理を宣言的に書く

Java 8で導入されたStream APIは、コレクションの操作を直感的に書ける仕組みです。

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamExample {

    record Employee(String name, String department, int salary) {}

    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("田中", "開発部", 450_000),
            new Employee("鈴木", "開発部", 520_000),
            new Employee("佐藤", "営業部", 400_000),
            new Employee("高橋", "営業部", 480_000),
            new Employee("伊藤", "開発部", 600_000)
        );

        // 部署ごとの平均給与を計算
        Map<String, Double> avgByDept = employees.stream()
                .collect(Collectors.groupingBy(
                        Employee::department,
                        Collectors.averagingInt(Employee::salary)
                ));

        avgByDept.forEach((dept, avg) ->
                System.out.printf("%s の平均給与: %.0f円%n", dept, avg));

        System.out.println("---");

        // 給与50万円以上の社員名を取得
        List<String> highEarners = employees.stream()
                .filter(e -> e.salary() >= 500_000)
                .map(Employee::name)
                .toList();

        System.out.println("給与50万円以上: " + highEarners);
    }
}
開発部 の平均給与: 523333円
営業部 の平均給与: 440000円
---
給与50万円以上: [鈴木, 伊藤]

注意: avgByDeptHashMap を内部的に使用するため、部署の出力順序は実行環境によって異なる場合があります。順序を保証したい場合は LinkedHashMap を指定できます。

「何をしたいか(What)」を書けば、「どうやるか(How)」はStream APIが処理してくれます。forループで添字を管理する必要がなく、フィルタ・変換・集約の意図がコードに直接表れます。


5. Optional:nullとの正しい付き合い方

NullPointerExceptionはJavaプログラマの宿敵とも言われてきました。Java 8で導入されたOptionalは、「値が存在しないかもしれない」ことを型で表現する仕組みです。

import java.util.Optional;

public class NullSafeExample {

    record User(String name, String email) {}

    // Optionalで「値がないかもしれない」ことを型で明示
    static Optional<User> findUserByName(String name) {
        if ("田中".equals(name)) {
            return Optional.of(new User("田中", "tanaka@example.com"));
        }
        return Optional.empty();
    }

    public static void main(String[] args) {
        // 存在する場合
        String email1 = findUserByName("田中")
                .map(User::email)
                .orElse("メールアドレス未登録");
        System.out.println("田中のメール: " + email1);

        // 存在しない場合
        String email2 = findUserByName("山田")
                .map(User::email)
                .orElse("メールアドレス未登録");
        System.out.println("山田のメール: " + email2);
    }
}
田中のメール: tanaka@example.com
山田のメール: メールアドレス未登録

nullチェックのif文を書き忘れることはあっても、Optional を返すメソッドなら「値がないケースを考慮すべき」ということがシグネチャから読み取れます


6. テキストブロック:複数行文字列の可読性

Java 15で正式導入されたテキストブロック""")は、JSONやSQLなどの複数行文字列を見たまま書ける機能です。

public class TextBlockExample {
    public static void main(String[] args) {
        // テキストブロック(Java 15〜):複数行の文字列をそのまま書ける
        String json = """
                {
                    "name": "田中太郎",
                    "age": 25,
                    "skills": ["Java", "Spring Boot", "SQL"]
                }
                """;

        System.out.println(json);

        // SQL文もインデントを保ったまま可読性よく書ける
        String sql = """
                SELECT e.name, d.department_name
                FROM employees e
                JOIN departments d ON e.dept_id = d.id
                WHERE e.salary >= 500000
                ORDER BY e.name
                """;

        System.out.println(sql);
    }
}
{
    "name": "田中太郎",
    "age": 25,
    "skills": ["Java", "Spring Boot", "SQL"]
}

SELECT e.name, d.department_name
FROM employees e
JOIN departments d ON e.dept_id = d.id
WHERE e.salary >= 500000
ORDER BY e.name

+ による文字列連結やエスケープ地獄とは無縁です。


7. 仮想スレッド:大量の同時処理をシンプルに

Java 21で正式導入された仮想スレッド(Virtual Threads / Project Loom)は、Javaの並行処理を根本から変える機能です。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class VirtualThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // 仮想スレッドで10万タスクを同時実行
        long start = System.currentTimeMillis();

        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 100_000).forEach(i -> {
                executor.submit(() -> {
                    // I/O待ちをシミュレート
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    return i;
                });
            });
        } // try-with-resources で自動的にシャットダウン&タスク完了を待機

        long elapsed = System.currentTimeMillis() - start;
        System.out.println("10万タスク完了!");
        System.out.println("実行時間: " + elapsed + "ms");
        System.out.println("使用スレッド: 仮想スレッド(軽量・OS非依存)");
    }
}
10万タスク完了!
実行時間: ***ms(環境によって異なる)
使用スレッド: 仮想スレッド(軽量・OS非依存)

従来のプラットフォームスレッドは1スレッドあたり数百KB〜1MB程度のメモリを消費するため、10万スレッドの同時実行は現実的ではありませんでした。仮想スレッドはJVMが管理する軽量なスレッドで、数十万〜数百万単位でも生成可能です。

重要なのは、既存の同期的なコードをほぼそのまま使えること。非同期プログラミング(コールバック地獄やリアクティブストリーム)に書き換える必要がありません。

補足: 仮想スレッドはI/O待ちの多い処理で真価を発揮します。CPU集約型の処理では、プラットフォームスレッドと比べて速度面の優位はありません。


8. エコシステムの厚み

Javaの強みは言語仕様だけではありません。エコシステムの成熟度と規模が桁違いです。

主要フレームワーク・ライブラリ

分野 名前 概要
Webアプリ Spring Boot Java Webアプリのデファクトスタンダード。Fortune 500企業の90%以上が採用
ビルドツール Maven / Gradle 依存関係管理とビルドの自動化
ORM Hibernate / JPA データベースとオブジェクトのマッピング
テスト JUnit 5 / Mockito 単体テスト・モックの定番
ログ SLF4J / Logback 構造化ログの標準
JSON Jackson / Gson JSONのシリアライズ・デシリアライズ

これらはいずれも長年の実績があり、ドキュメント・書籍・Q&Aサイトの情報量が豊富です。「困ったら検索すれば答えが見つかる」環境は、学習コストの大幅な削減につながります。

Spring Bootの存在感

Spring Bootは単なるフレームワークではなく、事実上のJavaアプリケーション開発プラットフォームです。2026年現在、Spring AIの登場によりLLM連携も容易になり、AI時代のアプリケーション開発にも対応しています。


9. JVM:「一度書けばどこでも動く」は今も健在

Javaのコードはバイトコードにコンパイルされ、JVM(Java Virtual Machine) 上で実行されます。Windows、macOS、Linuxを問わず、JVMさえあれば同じプログラムが動きます。

さらにJVM上では、Kotlin、Scala、Groovyなどの言語も動作します。Javaのエコシステムに投資した知識は、これらの言語に移行しても活きるということです。

GC(ガベージコレクション)の進化

メモリ管理はJVMが自動で行ってくれます。C/C++のように手動でメモリを解放する必要はありません。

さらに、GCの性能も年々進化しています。Java 21以降ではGenerational ZGCがデフォルトで利用可能になり、大規模アプリケーションでも停止時間(pause time)をミリ秒単位に抑えられるようになりました。


10. キャリアとしてのJava

技術的な魅力だけでなく、キャリアの観点からもJavaには大きな価値があります。

求人市場

  • TIOBE Index 2026年3月時点で世界第3〜4位を維持
  • 日本国内のエンジニア求人では、Javaは常にトップクラスの需要
  • 金融、官公庁、EC、物流、医療など基幹系システムでの採用が圧倒的に多い

学習投資の回収効率

Javaを深く学ぶことで得られる知識は、Java以外でも活きます。

  • オブジェクト指向の設計原則(SOLID、デザインパターン)
  • 型システムの理解(ジェネリクス、型安全設計)
  • 並行処理の基礎(スレッド、同期、アトミック操作)
  • JVMの仕組み(GC、クラスローダー、バイトコード)

これらはKotlin、C#、TypeScriptなど他の言語に移っても応用できる普遍的な知識です。

資格制度

Oracle Certified Java Programmer(Java Silver / Gold) という公式認定資格があり、スキルの客観的な証明として使えます。SES企業や大手SIerでの評価指標になっている場合も多いです。


11. 2026年のJava:進化は止まっていない

最新LTS:Java 25(2025年9月リリース)

2026年4月現在、最新のLTS(Long-Term Support)バージョンはJava 25です。主な新機能を紹介します。

機能 JEP 概要
コンパクトソースファイル JEP 512 クラス宣言なしでプログラムを書ける
柔軟なコンストラクタ本体 JEP 513 super() の前にバリデーション処理を記述可能
モジュールインポート宣言 - モジュール単位でのインポートが可能に
プリミティブ型のパターンマッチング - switch文でプリミティブ型にもパターンマッチングを適用

「Hello World」をクラス宣言なしで書けるようになり、入門者のハードルが下がりました。

// Java 25:コンパクトソースファイル(JEP 512)
void main() {
    System.out.println("Hello, Java 25!");
}

注意: この構文はJava 25以降で利用可能です。Java 21環境では従来のpublic static void main(String[] args)を使用してください。

LTSリリースサイクル

Javaは6か月ごとに新バージョンがリリースされ、2年ごとにLTSが提供されます。次のLTSはJava 29(2027年9月予定) です。

バージョン 種別 リリース年月
Java 17 LTS 2021年9月
Java 21 LTS 2023年9月
Java 25 LTS 2025年9月
Java 29 LTS(予定) 2027年9月

「枯れた言語」どころか、半年に一度の新機能投入で着実に進化を続けています。


まとめ

Javaの良さを改めて整理します。

観点 Javaの強み
型安全 コンパイル時にバグを検出。IDEの補完・リファクタリングも強力
モダンな構文 record、sealed class、パターンマッチング、テキストブロック
並行処理 仮想スレッドにより、シンプルなコードで大規模並行処理
エコシステム Spring Boot、Maven/Gradle、JUnit等の成熟した開発基盤
ポータビリティ JVMによるクロスプラットフォーム実行
キャリア 求人数トップクラス、公式認定資格、普遍的なスキルの習得
継続的進化 6か月ごとのリリース、2年ごとのLTS提供

Javaは「古い言語」ではなく、30年の蓄積の上に、今も進化を続けている言語です。

最初は記述量の多さや型の厳格さに戸惑うかもしれません。でもそれは、大規模開発で安全にコードを書くための仕組みです。学べば学ぶほど「こうなっている理由」がわかってきます。

これからJavaを学ぶ皆さん、ぜひ「Javaらしさ」を楽しんでください。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?