3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# vs Java:20年の歩みと思想の違いを技術的に整理する

3
Posted at

C# vs Java:20年の歩みと思想の違いを技術的に整理する

はじめに

JavaエンジニアがC#(ASP.NET Core)を学ぶと、「似ているようで根本的に違う」と感じる瞬間がいくつかある。async/awaityield returnIAsyncEnumerable、タプル——これらをJavaで実現しようとすると、ライブラリが必要だったり、冗長な実装が必要だったりする。

本記事では、C#とJavaの設計思想の違いがどこから来るのかを、両言語の歴史的な歩みと具体的なコードで整理する。


1. 出発点の違い

Java C#
登場 1995年(Sun Microsystems) 2000年(Microsoft)
設計者 James Gosling Anders Hejlsberg
前身 Oak(組込み向け言語) Delphi / J++
コア思想 Write Once, Run Anywhere 開発者生産性の最大化
実行環境 JVM CLR(Common Language Runtime)

HejlsbergはTurboPascal・Delphiの設計者でもあり、C#は「Javaの反省を踏まえて設計し直した言語」という側面が強い。


2. Javaの機能歩み(Java 1.0 〜 Java 25)

Java 1.0〜1.4(1995〜2002):基礎の確立

  • オブジェクト指向、ガベージコレクション、例外処理
  • java.util(Collections Framework)
  • スレッド(Thread / Runnable
  • JDBC(データベース接続の抽象化)

この時代はC#が存在しなかった。Javaが「エンタープライズの標準」として普及。


Java 5(2004):近代化の第一歩

// ジェネリクス
List<String> names = new ArrayList<>();

// 拡張for文
for (String name : names) { ... }

// アノテーション
@Override
public String toString() { ... }

// enum
enum Day { MONDAY, TUESDAY, WEDNESDAY }

// オートボクシング
Integer x = 42; // int → Integer 自動変換

C#との比較: C# 2.0(2005)でジェネリクス導入。ほぼ同時期だが、C#のジェネリクスはランタイムレベルで実装されており、JavaのType Erasure(実行時に型情報が消える)との設計上の違いがある。


Java 8(2014):最大の転換点

// ラムダ式
Runnable r = () -> System.out.println("Hello");

// Stream API(java.util.stream)
List<String> result = names.stream()
    .filter(n -> n.startsWith("A"))
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// Optional
Optional<String> opt = Optional.ofNullable(getValue());
opt.ifPresent(System.out::println);

// インターフェースのdefaultメソッド
interface Greeter {
    default void greet() { System.out.println("Hello"); }
}

C#との比較: C#はLINQ(C# 3.0、2007年)で7年先行。JavaのStreamはLINQに近いが、collect(Collectors.toList()) の冗長さは今も残る。C#では .ToList()


Java 9〜11(2017〜2018):モジュールと型推論

// var(Java 10〜)
var list = new ArrayList<String>(); // 型推論

// モジュールシステム(Java 9)
module com.example.app {
    requires com.example.core;
    exports com.example.api;
}

// String新メソッド(Java 11)
"  hello  ".strip(); // trim()のUnicode対応版
"".isBlank();
"a\nb\nc".lines().count(); // 3

Java 11がLTSとして広く採用される。


Java 14〜16(2020〜2021):表現力の向上

// Switch式(Java 14)
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
};

// yield(Switch式の中だけで使う)
int result = switch (x) {
    case 1 -> 10;
    default -> {
        int v = x * 2;
        yield v; // C#の yield return とは別物!
    }
};

// Record(Java 16):イミュータブルなデータクラス
record Point(int x, int y) {}
// equals, hashCode, toString, getter が自動生成

// テキストブロック(Java 15)
String json = """
        {
            "name": "田中",
            "age": 25
        }
        """;

// instanceof パターンマッチング(Java 16)
if (obj instanceof String s) {
    System.out.println(s.length()); // キャスト不要
}

Java 17(2021):LTS、封印クラス

// sealed class:継承できるクラスを制限
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}

// パターンマッチングとの組み合わせ(Java 21で完成)
double area = switch (shape) {
    case Circle c -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.width() * r.height();
};

Java 21(2023):Virtual Threads で革命

// Virtual Threads:OSスレッドを消費しない軽量スレッド
// ブロッキングI/OをしてもOSスレッドをブロックしない
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        executor.submit(() -> {
            // ブロッキングでDBアクセスしても高スループット
            var data = database.query("SELECT ...");
            process(data);
        });
    }
}

これはC#のasync/awaitとは逆のアプローチ

  • C#:「非同期で書く」→ コンパイラがステートマシンに変換
  • Java:「同期で書く」→ ランタイムが軽量スレッドで高スループットを実現

Java 25(2025):LTS最新版

  • パターンマッチングのさらなる強化(プリミティブ型パターン)
  • Structured Concurrency(構造化並行性)正式化
  • Scoped Values正式化
  • Project Valhalla(値オブジェクト)が近づく

3. C#の機能歩みと対比

C# 3.0(2007):LINQ — Javaより7年早く関数型

// LINQ:クエリ構文
var result = from n in names
             where n.StartsWith("A")
             select n.ToUpper();

// メソッド構文(Javaのstream APIに近い)
var result = names
    .Where(n => n.StartsWith("A"))
    .Select(n => n.ToUpper())
    .ToList(); // Java: .collect(Collectors.toList())

C# 5.0(2012):async/await — Javaとの最大の分岐点

// C#:言語レベルで非同期を抽象化
public async Task<string> FetchDataAsync()
{
    var response = await httpClient.GetStringAsync(url);
    return response;
}
// Java:ライブラリ(CompletableFuture)で対応
public CompletableFuture<String> fetchDataAsync() {
    return CompletableFuture.supplyAsync(() -> {
        // ここでは同期的なHTTPコールが必要
        return httpClient.get(url); // ブロッキング
    });
}

C#のawaitはコンパイラがステートマシンに変換する言語機能。Javaのコールバックチェーンより遥かに自然に書ける。


C# 8.0(2019):IAsyncEnumerable + yield return

// 非同期ストリーム:言語レベルで実装
public async IAsyncEnumerable<int> ReadChunkedAsync()
{
    int index = 0;
    while (index < 100)
    {
        int[] chunk = await GetNextChunkAsync(index);
        if (chunk.Length == 0) yield break;
        foreach (var item in chunk)
            yield return item; // 状態を保持したまま一時停止
        index++;
    }
}

// 呼び出し側
await foreach (var item in ReadChunkedAsync())
{
    Console.WriteLine(item);
}
// Javaで同等のことをする場合:Project Reactorが必要
Flux<Integer> readChunked() {
    return Flux.generate(
        () -> 0,
        (index, sink) -> {
            int[] chunk = getNextChunk(index);
            if (chunk.length == 0) sink.complete();
            else for (int item : chunk) sink.next(item);
            return index + 1;
        }
    );
}

C# タプル vs Java Record

// C#:匿名タプル(名前不要)
(bool isValid, string message) Validate(string input)
    => (true, "OK");

var result = Validate("test");
Console.WriteLine(result.isValid);  // true
Console.WriteLine(result.message);  // OK
// Java:Recordは「名前が必須」
record ValidationResult(boolean isValid, String message) {}

ValidationResult validate(String input) {
    return new ValidationResult(true, "OK");
}
// 一時的な複数戻り値のためにクラス定義が必要

4. DI(依存性の注入)の比較

Spring Boot vs ASP.NET Core

// Spring Boot:アノテーションスキャンで自動検出
@Service
public class OrderService {
    @Autowired
    private OrderRepository repository;
}

@Repository
public class OrderRepositoryImpl implements OrderRepository { ... }
// ASP.NET Core:Program.cs に明示的に登録
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<OrderService>();

// コンストラクタインジェクション(アノテーション不要)
public class OrderService
{
    private readonly IOrderRepository _repository;
    public OrderService(IOrderRepository repository)
        => _repository = repository;
}

思想の違い:

  • Spring:「コンテナが全部見つけてくれる」(暗黙的・魔法感あり)
  • ASP.NET Core:「何が登録されているか一目でわかる」(明示的・透明性)

5. ORM の比較

Java C#
フルORM JPA / Hibernate Entity Framework Core
マイクロORM MyBatis Dapper
マイグレーション Flyway / Liquibase EF Core Migrations
LINQ相当 JPQL / Criteria API LINQ to Entities
// EF Core:LINQでSQLなしにクエリ
var orders = await context.Orders
    .Where(o => o.Status == "Active")
    .Include(o => o.Items)
    .ToListAsync();
// JPA:JPQLで文字列クエリ
List<Order> orders = em.createQuery(
    "SELECT o FROM Order o WHERE o.status = :status", Order.class)
    .setParameter("status", "Active")
    .getResultList();

6. 非同期モデルの設計思想対比

C# の思想:「複雑さを言語が隠す」
  async/await → コンパイラがステートマシン生成
  yield return → コンパイラがIEnumerator生成
  IAsyncEnumerable → 両方を組み合わせる
  開発者は「同期的に見えるコード」を書く

Java の思想:
  〜Java 20:「ライブラリで解決(Reactor、RxJava)」
  Java 21〜:「ランタイムで解決(Virtual Threads)」
  Reactor  → push型の非同期ストリーム
  VirtualThreads → 同期的に書いて高スループット

7. テストフレームワークの比較

機能 Java C#
単体テスト JUnit 5 xUnit / NUnit / MSTest
モック Mockito Moq
アサーション AssertJ FluentAssertions
Spring統合 @SpringBootTest WebApplicationFactory
// JUnit 5 + Mockito
@Test
void shouldReturnOrder() {
    when(repository.findById(1L)).thenReturn(Optional.of(order));
    var result = service.getOrder(1L);
    assertThat(result).isPresent();
}
// xUnit + Moq
[Fact]
public async Task ShouldReturnOrder()
{
    _mockRepo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(order);
    var result = await _service.GetOrderAsync(1);
    Assert.NotNull(result);
}

8. まとめ:思想の違いの本質

┌─────────────────────────────────────────────┐
│                   Java                      │
│  「安定性」「後方互換」が最優先              │
│  重要機能 → まずライブラリで解決            │
│  標準化は慎重に、10年越しもある             │
│  Java 21〜 Virtual Threadsで逆転の発想      │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│                   C#                        │
│  「開発者生産性」が最優先                   │
│  重要機能 → 言語・コンパイラに組み込む      │
│  async/await、yield、LINQ全部言語機能       │
│  .NETと密に統合、一貫したAPI                │
└─────────────────────────────────────────────┘

どちらを選ぶか

ユースケース 推奨
エンタープライズJava資産の継続 Java + Spring
新規Webアプリ(Azure利用) C# + ASP.NET Core
マイクロサービス(コンテナ) どちらも可
高スループットAPI C#(gRPC、SignalR) または Java 21 Virtual Threads
関数型プログラミング重視 Kotlin(JVM)または F#(.NET)

参考リンク

3
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?