はじめに
RubyからJavaへシフトしたエンジニアです。
これまでは静的型付け言語は敬遠してたのですが、Javaを習得することで興味関心の幅が広がりました。
それで最近、趣味でC言語を学んでみたところJavaとC#が、
「君たち、生き別れた兄弟か?」
というくらい似ていることに気づき、その共通点や違いをまとめようと思いました。
本記事は投稿者の嗜好や見解をもとに作成した記事であり、特定の技術や転職を推奨する記事ではありません。
C#に関しては理解が浅いため、ご指摘あればコメント頂けると幸いです。
関連する言語の特徴
各言語の比較表
言語 | 言語パラダイム | メモリ管理 | 主な実行環境 | 主な用途 |
---|---|---|---|---|
C | 手続き型 | プログラマが手動で管理(malloc/freeなど) | OSに依存(ネイティブコード) | OS、組み込みシステム、ハードウェア制御、高速処理が必要なソフトウェア |
C++ | オブジェクト指向、手続き型 | プログラマが手動で管理(new/delete、ポインタ) | OSに依存(ネイティブコード) | 大規模アプリケーション、ゲーム開発、組み込みシステム、OSの一部、性能重視のソフトウェア |
Java | オブジェクト指向 | ガベージコレクションによる自動管理 | Java仮想マシン (JVM) 上で動作 | Webシステム、Androidアプリ、エンタープライズシステム |
C# | オブジェクト指向 | ガベージコレクションによる自動管理 | .NET Framework/Core (CLR) 上で動作 | Windowsアプリケーション、Webサービス、ゲーム開発 (Unity)、モバイルアプリ |
C言語
- UNIX OSの開発のために作られ、1972年に誕生したプログラミング言語です
- 手続き型言語、ハードウェアに近い低レイヤ制御が得意で、CPUやメモリを直接操作できます
- 実行速度が非常に速く、パフォーマンスが求められる場面で利用されます
- JavaやJavaScript、PHPなど多くのプログラミング言語の基礎になっています
用途:OSや組み込みシステムの開発
UNIX:
1960年代末に開発されたマルチタスク・マルチユーザーのオペレーティングシステム(OS)です。現存する中で最も古いOSであり、LinuxやMacOSなどもこのUNIXを参考に作られた派生OSです。
C++
- 1983年にリリースされ、C言語にオブジェクト指向の機能を追加した言語です
- C言語との高い互換性があり、手続き型とオブジェクト指向の両方が使えます
用途:ゲーム開発、デスクトップアプリケーション、組み込みシステムなど
Java
- 1995年にリリースされたオブジェクト指向の言語です
- OSに依存せず、どんな環境でもソフトを動かすことができて汎用性が高いです
- ガベージコレクション (GC) という仕組みにより、不要になったメモリ領域を自動で解放するため、プログラマはメモリ管理の手間が大幅に軽減されます
用途:大規模なWebシステム、Androidアプリケーション開発、エンタープライズシステム(例:企業や官公庁の業務システム、基幹システム)など
C#
- マイクロソフト社がC++・Javaの良いところを組み合わせて作られた、オブジェクト指向言語です(2000年にリリースされ、実はJavaよりも新しい言語)
- Javaと同様にガベージコレクション (GC) による自動メモリ管理機能を持ちます
- 主に.NET Frameworkや.NET Coreといった実行環境上で動作します
- 近年はWindowsだけでなく、Linux、macOS、iOS、Androidなど、さまざまなプラットフォームに対応できるようになったそうです
- CやC++から派生した名前を持ちますが、互換性はなく、全く別の言語系統とみなされるようです
用途:Windowsアプリケーション開発、Webアプリ開発、ゲーム開発(Unity)、モバイルアプリなど
JavaとC#のコード比較
JavaとC#はどちらもオブジェクト指向言語であり、構文や設計思想に多くの共通点がありますが、細かい点で違いも存在します。様々な観点からコードや言語仕様の違いを比較します。
JavaとC#の比較表
比較項目 | Java | C# |
---|---|---|
発表年 | 1995年 (Sun Microsystems) | 2000年 (Microsoft) |
実行環境 | Java仮想マシン(JVM) | .NET CLR (.NET Framework/Core) |
設計思想 | クラスベースのオブジェクト指向 | オブジェクト指向、関数型、コンポーネント指向 |
メモリ管理 | ガベージコレクション(自動管理) | ガベージコレクション(自動管理)、明示的リソース解放可(IDisposable) |
プロパティ | 明示的なgetter/setterを書く必要あり |
get /set キーワードで簡潔に記述可能 |
非同期処理 | CompletableFutureやFuture APIを使用 |
async /await 構文で強力かつ簡単に非同期処理可能 |
型システム | プリミティブ型とラッパークラスが分かれている | 値型と参照型が統一的に扱われる |
演算子オーバーロード | 非サポート | 複数の演算子オーバーロードをサポート |
ポインター | 非サポート | アンセーフコードモードで使用可能 |
レコード型 | Java 16からrecord キーワードで導入 |
C# 9.0からrecord 型をサポート |
IDEサポート | IntelliJ IDEA, Eclipse, NetBeans | Visual Studio, Visual Studio Code, Rider |
クロスプラットフォーム | 高い | .NET Core/.NET 5以降で対応 |
主な用途 | エンタープライズアプリ、Web、Android | Windowsアプリ、ゲーム開発(Unity)、Webサービス |
言語機能の革新性 | 比較的保守的 | 言語機能が先進的(デリゲート、イベント、LINQなど) |
設計思想・メモリ管理
- 両言語ともガベージコレクションによる自動メモリ管理を持ち、プログラマは直接メモリ解放を意識しなくてよい
- Java: ガベージコレクションによってメモリ管理は自動化されており、明示的なリソース解放はtry-with-resources構文などを用いる
- C#: IDisposableインターフェースとusing構文で明示的にリソースを確実に解放できる
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// ファイル読み込みする処理
} catch (IOException e) {
e.printStackTrace();
}
// ここで br.close() が自動的に呼ばれる。finallyブロックは不要
// ファイル操作を行うための標準クラスであるStreamWriterクラスが、
// IDisposableインターフェースを実装している
using (var stream = new StreamReader("file.txt")) {
// ファイル読み込みする処理
}
// usingブロックを抜けた瞬間に、writer.Dispose() が自動で呼び出される
// -> これにより、内部のファイルハンドルが閉じられ、リソースが解放される。
クラス宣言とプロパティ
- Java: getter/setterを手動で書くか、Lombok(アノテーション)でgetter/setterなどをコンパイル時に自動生成できます
- C#: プロパティ構文で簡潔に記述できます。また、手動でプロパティを書くことも可能
Lombokの場合(Java)
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Person {
private String name;
}
getter/setterを手動で書く場合(Java)
public class Person {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
プロパティ構文の場合(C#)
public class Person {
public string Name { get; set; }
}
手動でプロパティを書く場合(C#)
public class Person
{
// バッキング・フィールド(=一時的に格納するフィールド変数)
private string name;
public string Name
{
get
{
// 値を読み取るロジック(そのままバッキング・フィールドの値を返す)
return name;
}
set
{
// 値を書き込むロジック(バッキング・フィールドに値を代入する)
// "value" は代入される値を示す暗黙のキーワードです
name = value;
}
}
}
型システム
- Java: 基本型(intなど)とラッパークラス(Integerなど)で区別
- C#: 値型と参照型に明確に分けられ、ボクシング/アンボクシング機構*も言語仕様に統合
-
ボクシング(Boxing): C#の値型(プリミティブ型や構造体など)のインスタンスを、それを包含できる参照型(オブジェクト)のインスタンスに自動的に変換する処理
-
アンボクシング(Unboxing): ボクシングによって作成された参照型(オブジェクト)を、元の値型に自動的に逆変換する処理
継承とインターフェイス
- Java: クラスの単一継承。複数インターフェイス実装可
public class MyClass extends BaseClass implements InterfaceA, InterfaceB {
// 実装
}
- C#: 同じく単一継承。structやenumも言語仕様に含まれる
public class MyClass : BaseClass, IInterfaceA, IInterfaceB {
// 実装
}
非同期処理
- Java: CompletableFutureやExecutorServiceで非同期処理
CompletableFuture.runAsync(() -> {
System.out.println("Async Task");
});
- C#: 言語レベルでasync / await構文による非同期対応
public async Task AsyncMethod() {
await Task.Run(() => Console.WriteLine("Async Task"));
}
JavaとC#の主な相違点
いくら似ていると言えど、「似ているからJavaを学んだらC#を学ぶのは簡単だ」という論理が必ず成立するとは限らないとは思います。
文法的な違いなどで局所的には当てはまるでしょうけど、実際は言語仕様や慣習に細かい差異もあると思います。
-
アクセス修飾子の違い
- Javaのprotectedは同じパッケージ内と継承クラスからアクセス可能
- C#ではprotected internalに似た動作になり、アセンブリ(プロジェクト)内でもアクセス可能
C#の方がアクセス修飾子が多彩で使い分けが細かいので混乱しやすい。
-
プロパティとgetter/setterの扱い
- Javaでは明示的にgetter/setterメソッドを書く必要がある
- C#はプロパティ構文(get/set)で簡潔に表現
Java流の「大量のgetXxx(), setXxx()メソッドを書くクセは捨てる」必要がある。
-
コレクションの扱い
- JavaのMapやListはメソッド呼び出し(map.get(key))で要素アクセスする
- C#のDictionaryやListはインデクサ(dictionary[key])でアクセスする
新しい要素追加時やキー重複時の振る舞いも微妙に異なり、この差異に注意。
-
メソッドの仮想性(virtual)
-
C#のメソッドはデフォルト非virtualで、オーバーライドしたいメソッドにはvirtual修飾子が必要
-
Javaはメソッドがデフォルトで仮想(virtual)であり、この点は習慣の違いが大きい
-
-
名前空間とusingディレクティブ
- Javaはパッケージ単位でimportを使う
- C#は名前空間(namespace)を使い、usingディレクティブで対象を限定
さらにusing Console = System.Console;のようなエイリアス指定もあり、柔軟だが初学者は戸惑うことも。
-
参照と値渡しの違い
- C#はrefやoutなどで参照渡しを明示できる
- Javaはプリミティブ型の場合は値渡し、オブジェクト(参照型)の場合は、参照の「値渡し」
-
非同期処理
- C#のasync/awaitは言語仕様として強力かつ自然に非同期を書ける
- Javaは後発でAPIベースのためコードの書き方に違いが大きい
おわりに
C#は、オープンソースを重視するWeb系スタートアップでは、開発スピードやエコシステムの観点からあまり採用されないと思います。
ただ、大手企業の基幹システムや、高頻度な取引を扱う金融システムなど、信頼性重視のエンタープライズではC#も多く採用されているようです。
そういった環境でJavaを習得したエンジニアにとっては、Javaの知識を土台にしてC#を習得することで、スキルセットに厚みができると思いました。
(あとは、ゲームエンジンUnityの主要なスクリプト言語になっており、ゲーム開発などの選択肢も広がりそうで面白そうですね)