C#とJavaは近い親戚です。どちらも静的型付けでオブジェクト指向、ガベージコレクションを持つCスタイルの構文の言語で、どちらも仮想マシンのためのバイトコードにコンパイルされ、どちらも大規模なバックエンドシステムに使われています。C#は2002年に、Javaに対するMicrosoftの答えという面もあってリリースされ、20年以上の間に2つの言語は互いに機能を借用してきました(ラムダ、ジェネリック、レコード、パターンマッチング)。一方を知っている開発者は、もう一方をほぼすぐに読めます。
日々の開発で重要になる違いは、もっと小さく具体的なものです。このページではコードとともにそれらを見ていきます。C#のスニペットは実行でき、比較のためのJavaは静的なブロックで示します。
ひと目でわかる比較
| C# | Java | |
|---|---|---|
| 開発元 | Microsoft(2002年) | Sun Microsystems(1995年)、現在はOracle |
| ランタイム | CLR(.NET)、ILをネイティブコードにコンパイル | JVM、バイトコードをネイティブコードにコンパイル |
| プラットフォーム | Windows、Linux、macOS | Windows、Linux、macOS |
| プロパティ | 組み込み:public int Age { get; set; } | getterとsetterのメソッド |
| ユーザー定義の値型 | struct | まだない(Project Valhalla) |
| ジェネリック | 実体化:型が実行時にも存在する | 型消去:コンパイル後に型が取り除かれる |
| コレクションの問い合わせ | LINQ | Streams API |
| 非同期 | async / await | 仮想スレッド(Java 21)、CompletableFuture |
| チェック例外 | なし | あり |
| 演算子のオーバーロード | あり | なし |
| null安全 | null許容参照型(C# 8) | Optional<T>、アノテーション |
| 主な用途 | Unityのゲーム、ASP.NET Core、Windowsのデスクトップ、Azure | 企業のバックエンド、Android、ビッグデータ |
構文:ほとんど同じ
クラス、メソッド、if、for、while、switch、try/catch はほとんど同じように読めます。目に見える違いは、命名規則といくつかのキーワードです。C#のメソッドはPascalCase(ToUpper、WriteLine)、JavaのメソッドはcamelCase(toUpperCase、println)です。C#は string と bool、Javaは String と boolean と書きます。継承はC#では class Dog : Animal、Javaでは class Dog extends Animal です。
// Java
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Ana");
names.add("Ben");
for (String n : names) {
System.out.println(n.toUpperCase());
}
}
}
出力:
ANA
BEN
C#は == で文字列を値として比較するので、Javaの典型的な初心者のバグの1つがなくなります。Javaでは、2つの String オブジェクトに対する == は参照を比較するので、実行時に作られた(入力から読んだ、連結した)2つの等しい文字列が等しくないと判定されることがあります。
getterとsetterの代わりにプロパティ
Javaのクラスは、通常IDEやLombokが生成する getName() と setName() のメソッドを通して状態を公開します。C#にはプロパティがあります。フィールドのように読めて、コードを実行するメンバーです。
// Java
public class Product {
private double price;
public double getPrice() { return price; }
public void setPrice(double price) {
if (price < 0) throw new IllegalArgumentException("negative");
this.price = price;
}
}
出力:
Mug: 9.50
オブジェクト初期化子 new Product { Name = ..., Price = ... } は、構築の直後にプロパティを設定します。Javaのレコード(Java 16)は不変のデータの場合をカバーします。C#にもレコード(C# 9)があり、それ以外のすべてにはプロパティがあります。
値型:構造体
Javaでは、ユーザー定義の型はすべてヒープに格納される参照型で、値なのは8つのプリミティブ型(int、double など)だけです。C#では struct で独自の値型を定義できます。構造体は代入時にコピーされ、配列や他のオブジェクトの中に、別個のヒープ割り当てなしで直接置けます。
出力:
a = (1, 2)
b = (99, 2)
1000
これは、何百万もの小さなオブジェクトがガベージコレクターに負担をかけるゲームや数値計算のコードで重要になります。構造体を参照してください。
ジェネリック:実体化と型消去
Javaはコンパイル時にジェネリックの型引数を消去します。実行時には List<Integer> は単なる List で、List<int> は許されません(ボックス化された Integer オブジェクトになります)。C#は実行時にも型引数を保持するので、List<int> は生のintを格納し、コードは T が何かを尋ねられます。
出力:
3 items of type Int32
1 items of type String
List`1
typeof(T) と new T() が動くのは、ランタイムが T を知っているからです。Javaではどちらもコンパイルエラーになり、一般的な回避策は Class<T> オブジェクトを渡して回ることです。
LINQとストリーム
どちらの言語にも、コレクションを絞り込んだり変換したりする流れるようなAPIがあります。JavaはStreams API(Java 8)、C#はLINQ(C# 3)で、LINQにはクエリ構文もあり、Entity Frameworkを通してデータベースにも使えます。
// Java
List<String> expensive = products.stream()
.filter(p -> p.getPrice() > 20)
.sorted(Comparator.comparing(Product::getPrice))
.map(Product::getName)
.collect(Collectors.toList());
出力:
Lamp, Chair
Total: 126
操作は1対1で対応し(filter は Where、map は Select、collect(toList()) は ToList())、C#版では stream() もコレクターも不要です。new { Name = ..., Price = ... } のオブジェクトは匿名型で、Javaには直接対応するものがありません。
非同期と並行処理
C#は2012年に async/await を導入しました。async メソッドは Task を返し、await はスレッドをブロックせずにメソッドを中断します。後にJavaScript、Python、Rustも同じキーワードを採用しました。Javaは別の道を選び、CompletableFuture の連鎖と、Java 21以降は普通のブロッキングするコードを安価にする仮想スレッドを提供しています。
// C#
async Task<string> LoadAsync(HttpClient http, string url)
{
string body = await http.GetStringAsync(url);
return body.Substring(0, 100);
}
// Java 21 with virtual threads: plain blocking code on a cheap thread
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> client.send(request, BodyHandlers.ofString()));
}
どちらの方法も、多数の同時リクエストに対応できます。C#は非同期性をメソッドのシグネチャに見える形で示し、Javaはランタイムの中に隠します。
例外、演算子、その他の小さな違い
- チェック例外。 Javaは、特定の例外をメソッドに宣言するかキャッチするよう強制します(
throws IOException)。C#にはチェック例外がまったくありません。 - 演算子のオーバーロード。 C#では型が
+、==、<などを定義できるので、decimalやDateTimeの演算が自然に読めます。Javaではできません(BigDecimal.add)。 - 既定で仮想。 Javaのメソッドは
finalを付けない限りオーバーライドできます。C#のメソッドはオーバーライド可能にするにはvirtualを付ける必要があり、オーバーライドする側はoverrideと書かなければなりません。 - 拡張メソッド。 C#は既存の型にメソッドを追加できます(
"text".IsPalindrome())。Javaではできません。 - null許容値型。 C#の
int?はnullになりうるintです。Javaはボックス化されたIntegerを使います。 - 符号なしの型。 C#には
uint、ulong、ushort、符号なしのbyteがあります。Javaのbyteは符号付きで、16ビットのcharを除いて符号なしの整数型はありません。
エコシステム、求人、どちらを学ぶか
オープンソースのサーバーの世界では、Javaのエコシステムのほうが大きいです。Spring、Kafka、Spark、Hadoop、Elasticsearch、そしてほとんどのApacheのプロジェクトはJavaです。AndroidのアプリはKotlinかJavaで書かれます。C#はより統合されたスタックを持っています。1つのベンダーが、言語、ランタイム、Webフレームワーク(ASP.NET Core)、ORM(Entity Framework)、主要なIDE(Visual Studio)を保守しており、Unityによってゲーム開発の中心にあります。
どちらの言語も求人市場は大きく、両者のバランスは地域や業界によって異なります。C#は、MicrosoftやAzureで成り立つ企業、金融や医療のソフトウェア、ゲームスタジオで強いです。
Unityでゲームを作りたい、Windowsのデスクトップアプリを作りたい、.NETの会社で働きたいならC#を選びます。Android、ビッグデータ、バックエンドがJVMの上にある会社を目指すならJavaを選びます。どちらを学んでも、もう一方に必要な概念、つまりクラス、インターフェース、ジェネリック、コレクション、例外、ガベージコレクションを持つランタイムが身に付きます。
よくある質問
C#はJavaに似ていますか?
とても似ています。どちらも静的型付けでオブジェクト指向、ガベージコレクションを持つCスタイルの構文の言語で、仮想マシンのためのバイトコードにコンパイルされます。Javaの開発者は初日からC#を読めます。違いは細部にあります。C#にはプロパティ、ユーザー定義の値型、実体化されたジェネリック、LINQ、async/await があり、Javaにはチェック例外と、より大きなオープンソースのサーバー向けエコシステムがあります。
C#とJavaのどちらを先に学ぶべきですか?
何を作りたいかで選びます。Unityのゲーム、Windowsのデスクトップアプリ、MicrosoftやAzureで成り立つ会社ならC#。Android(Kotlinと並んで)、大規模な企業のバックエンド、KafkaやSparkのようなビッグデータのツールならJavaです。2つの言語は十分に近いので、後で乗り換えるのにかかるのは数か月ではなく数週間です。
C#はJavaより速いですか?
同じ性能のクラスにあります。どちらもネイティブコードにJITコンパイルし、世代別のガベージコレクターを使います。C#は、構造体、Span<T>、ボックス化なしの値型に対するジェネリックによって低レベルの制御がしやすく、割り当ての多いコードで役立ちます。一般的なWebの処理では、言語よりフレームワークやデータベースのほうがはるかに重要です。
C#にはJavaのようなチェック例外がありますか?
ありません。C#では、宣言やキャッチが必須の例外型はなく、メソッドのシグネチャは何を投げるかを列挙しません。設計者は、チェック例外が空のcatchブロックと壊れやすいシグネチャにつながると判断しました。代わりに /// <exception> のXMLコメントで例外を文書化します。
C#とJavaでは求人が多いのはどちらですか?
どちらも求人市場は大きく、どちらの求人が多いかは国や業界によって異なるので、働きたい場所の求人を確認してください。C#はMicrosoftを使う企業、金融、医療、ゲームスタジオで強く、Javaは大手銀行、通信、ECのバックエンド、Androidで強いです。