C SharpとJavaの比較
C#とJavaの比較(シーシャープとジャバのひかく)の記事では、プログラミング言語C#とJavaの比較について説明する。 言語オブジェクトの扱いいずれの言語もクラスベースのオブジェクト指向言語であり、その文法はC++に類似しているが、C++との互換性はない。メモリ再利用の手段として、従来の手動で解放する方法ではなくガベージコレクション (GC) を使用する。また、スレッド同期の手段を言語構文に組み込んでいる。 また、いずれの言語もC++のデストラクタに相当する機能を持たないため、ガベージコレクションの管理対象外となるリソース(ファイルやネットワーク接続など)はプログラマが明示的に破棄する処理を記述する必要がある。例外安全に破棄を実行する手段として、 Java、.NETいずれもガベージコレクションの実装形態は細かく規定されていないが、多くの実装でコピーGCとマーク&スイープGCを併用した世代別ガベージコレクションが使われている。.NET Frameworkは当初から世代別GCだったが、組み込み環境向けの.NET Compact Frameworkは世代別GCではなくマーク&スイープGCである[3]。 いずれの言語も強い参照と弱い参照の両方をもつ。Javaでは参照がガベージコレクタによって回収された時に通知を受けるリスナーを登録することができる。これは 実行形態と安全性基本的に実行時コンパイラにより中間表現(.NETの場合は共通中間言語、Javaの場合はJavaバイトコード)を機械語に変換しつつ仮想機械上で実行させる形態をとっており、通常のプログラムコードの実行は実行環境(ランタイム)によってすべて管理される。.NETではこれをマネージコード(マネージドコード)と呼び[4]、C#のような言語をマネージ言語と呼ぶが、Javaについても同様にマネージ言語と呼ぶことがある。いずれもC言語やC++と比べて安全性が高く、例えば C#は、一部の言語設計者から危険であると指摘されるポインタを使用した演算が制限つきながら利用できる。C#はポインタを使用するメソッド、型あるいはコードブロックを 型システムとデータ型Javaには大別して参照型(クラス型)とプリミティブ型(基本型)が存在する。参照型は 一方、C#には大別して参照型、値型、ポインタ型が存在する[6]。このうちポインタ型を除き、あらゆる型はすべて Javaの組み込み型 (built-in type) はプリミティブ型 (primitive data type) と呼ばれる[8]。C#は組み込み型をJavaよりも多く持ち、すべての組み込み型は
C#言語自体にはプリミティブ型という用語は存在しないが、.NET Frameworkの共通言語ランタイム (CLR) では、 いずれの言語もプリミティブ型(もしくは値型)と参照型との間で変換するためにボックス化 (boxing) とボックス化解除 (unboxing) が可能である。これによってプリミティブ型(もしくは値型)は参照型のサブセットとみなすことができる。値型は仮想メソッドテーブルを持たず、したがってそのままでは多態性を利用できないが、C#においては、ボックス化によって値型で多態性を利用することが可能である(例えば C#では、 C#の列挙型は抽象クラス プリミティブ型(あるいは値型)は参照型(クラス)とは異なり、インスタンスはヒープ領域ではなくスタックに置かれる。また、(フィールドとして、あるいはボックス化された状態で)クラスの一部になることも、配列の要素になることも可能である。クラスのインスタンスはメモリ上で間接的に参照される必要があるが、プリミティブ型(あるいは値型)はその必要がない。プログラマの視点からは、C#の値型は軽量なクラスとみなせる。しかし、プリミティブ型(あるいは値型)には前述のように多数の制限がある。値型は通常nullの値をとることができないが、.NET Framework 2.0でnull許容型 ( 型そのものをコード上で表現するメタデータ型(メタクラス)として、Javaでは 配列C#およびJavaの配列は参照型であり、第一級オブジェクトである。C#の配列は共通の基本クラス いずれの言語も配列は共変性 (covariance) を持つ。 System.Console.WriteLine(typeof(int[]).BaseType); // System.Array
object[] objs = new string[3]; // 代入可能。
objs[0] = "test";
objs[0] = 10; // System.ArrayTypeMismatchException
System.out.println(int[].class.getSuperclass()); // java.lang.Object
Object[] objs = new String[3]; // 代入可能。
objs[0] = "test";
objs[0] = 10; // java.lang.ArrayStoreException
なお、Java 1.5で拡張for文がサポートされた。これはC#の C#は真の多次元配列(矩形配列)をサポートするが、Javaはサポートしない。C#およびJavaはともに「配列の配列」をサポートする(C#ではジャグ配列と呼ばれる)。 →詳細は「配列」を参照
内部クラスいずれの言語もネストされた型(入れ子にされた型:クラスや構造体のブロックの中で定義されたクラス・構造体・列挙型・インターフェイス)をサポートする。Javaではインターフェイス内部にクラスや列挙型を定義することもできる。C# 7.xまではインターフェイス内部にクラス・構造体・列挙型・インターフェイスを定義することはできなかったが、C# 8.0以降は定義できるようになった[13]。 Javaでは、ネストされたクラス (nested class) は既定で内部クラス (inner class) となる。内部クラスは外側のクラスのインスタンスを暗黙的にキャプチャすることで、静的メンバ、非静的メンバいずれにもアクセスすることができる。ネストされたクラスが C#では、ネストされたクラス/構造体から外側のクラス/構造体の非静的メンバにアクセスするためには外側のクラス/構造体のインスタンスへの明示的な参照が必要になる。Javaの内部クラスやローカルクラスに相当する機能は存在しない。代わりに、C# 2.0以降では匿名メソッド (anonymous method) が、C# 3.0以降ではラムダ式が、そしてC# 7.0以降ではローカル関数がサポートされ、外側の変数をキャプチャするクロージャとして利用できる。なお、C# 3.0では限定的なローカルクラスとして、匿名型(読み取り専用プロパティのみを持つ、匿名のクラス型)がサポートされる。 ジェネリクスJavaではジェネリクスは型消去 (type erasure) によって実装されている。これによってジェネリック型についての情報は実行時には失われ、リフレクションを通してのみ取得できるようになる。.NET 2.0では、ジェネリック型についての情報は完全に保存される。Javaはプリミティブ型に対するジェネリック型は定義できないが、C#では参照型・値型(プリミティブ型を含む)いずれに対してもジェネリック型を定義できる。Javaはその代わりにボックス化した型を使用することができる( ジェネリクスの型制約C#、Javaともに、ジェネリクスの型制約を持つ。 C#では基底型指定以外に、 // C#
class ConstraintClass { }
class SomeGenericClass<T> where T : ConstraintClass { }
class OtherGenericClass<T> where T : new() { }
class OtherGenericClass<T> where T : notnull { }
class OtherGenericClass<T> where T : unmanaged { }
// Java
class ConstraintClass { }
class SomeGenericClass<T extends ConstraintClass> { }
ジェネリクスの共変性と反変性C#、Javaともに、ジェネリクスの共変性と反変性 (covariance and contravariance) を持つ。ただしC#における共変性・反変性のサポートはバージョン4.0以降である。 C#では、型定義側で共変性 // C#
// System.Func<T, TResult> と同様のデリゲートを定義。
// 型定義側で変性の指定を行う。
public delegate TOut MyFunc<in TIn, out TOut>(TIn arg);
public class GenericsTest {
public static void Main() {
// 利用側では変性の指定を行わない。
MyFunc<object, string> func1 = x => "<" + x + ">";
MyFunc<string, object> func2 = func1;
object ret = func2("test");
System.Console.WriteLine(ret);
System.Console.WriteLine(ret.GetType()); // System.String
}
}
Javaでは、型定義側では変性の指定を行わず、利用側で共変性 // Java
// java.util.function.Function<T, R> と同様のインターフェイスを定義。
// 型定義側では変性の指定を行わない。
@FunctionalInterface
interface MyFunc<TIn, TOut> {
TOut apply(TIn arg);
}
public class GenericsTest {
public static void main(String[] args) {
// 利用側で変性の指定を行う。
MyFunc<? super Object, ? extends String> func1 = x -> "<" + x + ">";
MyFunc<? super String, ? extends Object> func2 = func1;
Object ret = func2.apply("test");
System.out.println(ret);
System.out.println(ret.getClass().getName()); // java.lang.String
// 変性の上下限いずれも指定しないこともできる。
java.util.List<?> list;
}
}
型推論C# 3.0でコンテキストキーワード Java 7で導入されたダイヤモンド演算子 表記法と特殊な仕様Java 1.5で、あるクラスの静的メソッド・静的フィールドを短い名前で使用するために C#は静的クラス(Javaの静的内部クラスとは異なる)の構文をもち、これによってクラスは静的メソッドのみを持つことができるようになる。C# 3.0から、型に静的にメソッドを追加するための拡張メソッドが導入されている( キーワード
イベント処理Javaでは、Observer パターンの記述を容易にするために匿名クラスという糖衣構文が用意されている。匿名クラス構文により、クラス本体の定義とインスタンスの生成を同時に行なうことができ、また定義と生成がひとつの式として扱えるため、インターフェイス実装クラスのインスタンスを一度限りしか生成しない場面、例えば特定のインターフェイス実装(あるいは特定のスーパークラスのメソッドのオーバーライド)を要求するイベントハンドラーの設定時などによく用いられる。Java 8ではラムダ式がサポートされ、匿名クラスの代わりに使うこともできる。Java 7まではC#のデリゲートに相当するものは存在しなかったが、Java 8では類似機能としてメソッド参照がサポートされるようになった。 C#ではデリゲート ( 非同期処理JavaおよびC#はともに標準ライブラリでスレッドをサポートしている。OSのスレッド(カーネルレベルのスレッド)に対する薄い抽象化を提供するクラスとして、Javaには 言語組み込みの同期構文として、Javaには C# 5.0以降は Java 1.5以降は 数値処理数学・科学計算・金融分野のアプリケーション開発に十分対応するための言語仕様が、それぞれに存在する。 Java SE 1.2からJava SE 16までは、IEEE 754に準拠した厳密な浮動小数点計算を強制するための 単精度浮動小数点数 Javaでは、
これらに加え、C#では数値処理アプリケーションのために、コード中の特定領域において整数型の算術演算および変換に対する算術オーバーフローの実行時チェックの有効・無効を制御する 演算子多重定義Javaは言語仕様をシンプルに保つため、また乱用による難読化を防ぐため、ユーザー定義の演算子オーバーロードをサポートしていない。一方でコードの書きやすさおよび直感性は犠牲になっている。 var myList = new java.util.ArrayList<Integer>(java.util.Collections.nCopies(10, 0));
myList.set(4, -100);
var gravityTable = new java.util.HashMap<String, Double>();
gravityTable.put("Mercury", 0.377);
gravityTable.put("Venus", 0.9);
gravityTable.put("Earth", 1.0);
double gravityOnVenus = gravityTable.get("Venus");
var b1 = new java.math.BigInteger(Long.toString(Long.MAX_VALUE));
var b2 = new java.math.BigInteger(Integer.toString(1));
var b3 = b1.add(b2);
C#は表記法の多くの点でJavaよりも多機能である。演算子多重定義やユーザー定義キャストなど、それらの多くはC++プログラマによって既に親しまれているものである。 C#はインデクサ(C++の var myList = new System.Collections.Generic.List<int>(new int[10]);
myList[4] = -100;
var gravityTable = new System.Collections.Generic.Dictionary<string, double>();
gravityTable["Mercury"] = 0.377;
gravityTable["Venus"] = 0.9;
gravityTable["Earth"] = 1.0;
double gravityOnVenus = gravityTable["Venus"];
C#は(論理的一貫性を保つための制限はあるものの)ユーザー定義の演算子オーバーロードをサポートしており、注意深く使用すれば簡潔で可読性の高いコードを記述することができる。 var b1 = new System.Numerics.BigInteger(long.MaxValue);
var b2 = new System.Numerics.BigInteger(1);
var b3 = b1 + b2;
また、C#では明示的メンバ実装 (Explicit Member Implementation) が可能である。これによって、インターフェイスメソッドの実装とクラス自身のメソッドの実装とを分離することができ、また同じシグネチャ(名前および引数の数と型の順序)を持つメソッドが異なるインターフェイスにそれぞれ存在した場合に、インターフェイス名を指定して区別することでそれらの実装を別々に行うことができる。 メソッドC#のメソッドはデフォルトで非仮想(非virtual)であり、仮想メソッドにする必要がある場合は明示的に Javaでは、メソッドを非仮想的にする方法はない。これは、派生クラスが同名の無関係なメソッドを再定義することが不可能であることを意味する。これは基底クラスが別のプログラマによって書かれており、バージョン更新の際に、派生クラスに既に存在していたものと同じシグネチャのメソッドが追加されてしまった場合に問題になる。Javaでは、この場合どちらのプログラマの意図とも異なり、派生クラスのメソッドは暗黙的に基底クラスのオーバーライドになってしまう(基底クラスでfinal宣言されていた場合はコンパイルエラーになってしまう)。 これらのバージョン更新の問題を部分的に解決するため、Java SE 5.0では C#では、派生クラスで仮想メソッドをオーバーライドする際には、明示的にその旨を宣言する必要がある。メソッドが基底クラスのオーバーライドである場合、
→「オーバーライド」も参照
プリプロセッサJavaはC/C++でしばしば問題を引き起こしていたプリプロセッサを採用しなかった。一方でC#は限定的にプリプロセッサディレクティブを使用可能である。 条件付きコンパイルJavaではプリプロセッサがサポートされないため、コンパイル時の分岐は不可能となっている。 一方、C#ではプリプロセッサディレクティブを用いた条件付きコンパイル (条件コンパイル、英: conditional compilation) が実装されている。また、指定されたコンパイル定数が定義されている時のみ呼び出されるよう Java 1.4からは実行時に有効・無効を切り替えられる 名前空間とソースファイルC#の名前空間はC++のそれと類似している。Javaのパッケージとは異なり、名前空間はソースファイルの物理的位置とは無関係である。Javaではパッケージ構造とソースファイルの位置が必ずしも一致している必要はないものの、デフォルトではそのような振る舞いをする。 Javaではアクセスレベルが C#ではそのような制約はなく、クラス名と無関係の1つのソースファイル (*.cs) に任意のクラスを複数記述することができる。C# 2.0からは 例外処理Javaは非チェック例外 (unchecked exception) に加えてチェック済み例外 (checked exception) をサポートする。C#では非チェック例外のみである。チェック済み例外は、プログラマがメソッドから発生し得るものを全て宣言し、捕捉する必要がある。 全てのエラーが処理されることを保証できるため、チェック済み例外は非常に便利だとする者もいる。一方、C#の設計者であるアンダース・ヘルスバーグのように、Javaのチェック済み例外はある程度実験的な仕様であり、小さなプログラムでの例を除いては実装する価値を見出せなかった、とする者もいる[42][43]。一つの批判として、チェック済み例外はプログラマが空のcatchブロックを記述するのを促進し、
低レベルコードC/C++などで書かれたネイティブコード資産の再利用や、オペレーティングシステムあるいはハードウェアへのローレベルなアクセスを可能とするため、JavaおよびC#はともにネイティブ相互運用のための機能を提供している。 Java Native Interface (JNI) ではJavaコード内で非Javaコードを .NETのプラットフォーム呼び出し(Platform Invoke、P/Invoke)はC#からアンマネージコードの呼び出しを可能にする。プログラマはメタデータを通して、メソッドの引数や戻り値がどのように橋渡し(マーシャリング)されるかを完全に制御することができる。このため、既存コードのインターフェイスがC言語形式関数でありさえすれば、余分なレイヤーは必要にならない。P/Invokeは(Win32やPOSIXなどの)手続き型APIにはほぼ完全にアクセスすることができるが、C++クラスライブラリへの直接的なアクセスはきわめて困難である。そのほか、C++/CLI言語を介することで、C#とC/C++間の相互運用を行なうことも可能である。またCOM相互運用により、コード資産を相互に利用することも可能である。 C#は通常の型チェックなどのCLRの安全のための機能を無効にし、ポインタ変数を利用することができる。この時、プログラマはコードを 統合言語クエリC#には、統合言語クエリの為の拡張されたキーワード群が存在し、ソースコード上でSQL構文に似たクエリを直接記述することができる。このキーワード群は構文糖として機能し、決められた拡張メソッド呼び出しに展開される。 // 数列から偶数を抜き出して表示する
var inputs = new[] { 1, 5, 2, 3, 4, 7, 11, 10, 6 };
#if true
// LINQクエリ構文
var results =
from inputValue in inputs
where (inputValue % 2) == 0
select inputValue;
#else
// LINQメソッド構文
var results = inputs.
Where(inputValue => (inputValue % 2) == 0).
Select(inputValue => inputValue);
#endif
foreach (var resultValue in results) {
Console.WriteLine(resultValue);
}
統合言語クエリの拡張メソッド群は、コレクションに対するさまざまな集合演算が実装されており、このような演算をロジックで記述することなく、簡単に操作することができる。 また、ラムダ式から式ツリー(Expressionクラス)のインスタンスを生成することができるため、この機能を使用してデータベースへのクエリ発行・収集を、タイプセーフ性を失うことなく直接的に行うことができる(LINQ to SQL、LINQ to Entitiesなど)。 Javaでは、このようなクエリ構文を直接サポートしない。Java 8ではC#のLINQメソッド構文に近い記述が可能となるStream APIとラムダ式がサポートされた。 命名規則.NETでは、外部に公開される識別子のうち、名前空間、型、メソッド、定数フィールドなどの命名にはパスカルケース(アッパーキャメルケース)が使われ、引数の命名にはキャメルケース(ローワーキャメルケース)が使われる[44]。C#コードの命名規則もこのガイドラインに準ずるが、ローカル定数の命名にはパスカルケースが、ローカル変数の命名にはキャメルケースが使われる[45]。 Javaでは、パッケージの命名には小文字の英数字(アンダースコアは含めるべきではない)、型の命名にはアッパーキャメルケース、メソッドの命名にはローワーキャメルケース、定数フィールドの命名にはアッパースネークケース、変数の命名にはローワーキャメルケースが使われる[46]。 詳細は各言語のガイドラインを参照のこと。これらの命名規則は、後述の姉妹言語と相互運用する際にも重要な要素となるため、特に外部に公開される識別子に関してはガイドラインを遵守する必要がある。 字下げスタイル言語の本質とは無関係だが、公式リファレンスなどのコード例における字下げスタイルとして、C#ではBSD/オールマンスタイル[47]、Javaではサン・マイクロシステムズが1995年(最終改訂は1999年4月20日)に発表した公式のコーディング規約「Code Conventions for the Java Programming Language」に従うことが多い[48]。統合開発環境の自動フォーマッターは通例これらのスタイルに沿っている。 // C#
class SomeClass
{
public static double SomeMethod(double x)
{
if (double.IsNaN(x) || double.IsInfinity(x) || x < 0.0)
{
throw new System.ArgumentException();
}
else
{
return System.Math.Sqrt(x);
}
}
}
// Java
class SomeClass {
public static double someMethod(double x) {
if (Double.isNaN(x) || Double.isInfinite(x) || x < 0.0) {
throw new IllegalArgumentException();
} else {
return Math.sqrt(x);
}
}
}
ただし両言語とも構文規則はフリーフォーマットであり、基本的に空白や改行の位置に左右されない。本記事では構文的および機能的な比較のしやすさと省スペース化の観点から、C#のコード例に関してもJavaと同じ字下げスタイルとしている。 実装JVMとCLRJavaはまったく異なる多くのオペレーティングシステム間で実行できる。またパーソナル・コンピュータに限らず、高度な計算処理や制御を必要とする家電製品や、Blu-ray Discのインタラクティブ技術にもBD-Jとして使用されている。このように数多くのJava仮想マシン (Java VM, JVM) 実装が存在する。 C#および.NETテクノロジー自体はクロスプラットフォーム指向であったものの、当初はWindows専用の実装しか存在しなかった。.NET Frameworkはマイクロソフトによる最初の.NETの実装であり、共通言語ランタイム (CLR) はマイクロソフトによる共通言語基盤 (CLI) の実装である。のちに他のプラットフォーム向けに実装された有名なものとしてMono/Xamarinがあり、多くのオペレーティングシステム向けの.NET開発が可能となった。ただし、マイクロソフトによる実装と比較して未実装部分が多く、利用できるライブラリに大きく制限があった。マイクロソフトによるモバイル/組み込み環境向け実装としては.NET Compact Frameworkがあり、Xbox 360などに搭載されていた。 その後、.NET Frameworkとは別に、マイクロソフトおよび.NET Foundationによるクロスプラットフォームなオープンソースソフトウェア (OSS) としての標準実装である「.NET Core」が公開された。なお、.NET CoreのCLR実装はCoreCLRと呼ばれていた。.NET Coreはバージョン5.0で「.NET」と改称された。.NET Frameworkの機能的なバージョンアップは4.8で終了しているが、Windowsへのバンドルおよびメンテナンスは続けられている。 標準両言語の構文(文法)、プログラミングインターフェイス、バイナリ形式(実行ファイル形式)、実行環境などは様々な機関によって管理されている。 C#はEcmaとISO、JIS[注釈 2]によって定義されている。標準化の対象は言語構文、基本クラスライブラリ、アセンブリ形式、実行環境(共通言語基盤: CLI)など多岐に渡る。下位層フレームワークの上に新しく実装された上位層ライブラリの多くはこの標準には含まれない(Windows Forms、ASP.NET、ADO.NETなど)。 現在のところ、Javaのどの部分も第三者の標準化団体によって標準化されていない。Javaの商標、ソースコードやその他の素材に関してはオラクル(旧サン・マイクロシステムズ)が無制限の独占的な権利を保持しているが、オラクル(サン)はJava Community Process[49] (JCP) と呼ばれるプロセスに参加し、当事者たちがJavaに関連する技術(言語、SDKからAPIに至るまで)に対する変更を専門家団体や諮問会議を通して提案することを許可している。JCP内の規定では、Javaに対する新しい仕様や変更はオラクル(サン)による承認が必要であるとされている。JCPは営利寄与者に対しては会費が必要としているが、非営利寄与者や個人は無料で参加できる。JavaのAPIセットにはいくつかのエディションがあり、標準エディションのJava SE、エンタープライズ向けエディションのJakarta EE(旧Java EE)、モバイル/組み込み環境向けエディションのJava MEが存在する。 姉妹言語C#およびJavaには、それぞれの実行環境を用いて動作する姉妹言語が存在し、それぞれ.NET言語およびJVM言語と呼ばれている。姉妹言語は他の既存言語からの移行のしやすさや、記述能力および生産性の向上、あるいは新たなプログラミングパラダイムを導入するなどの目的で開発されたものであり、異なる言語間でユーザー定義のクラス型などを再利用する相互運用も可能である。 各々の代表的な姉妹言語を列挙する。
C#は登場当初、C#.NETと呼ばれており、VB.NETもまた.NET Frameworkに対応する言語として同時期に登場し、当初はほぼ同等の言語機能を持っていたが、C#と比べてバージョンアップおよび新機能の追加は遅れることが多かった。2023年には、VB.NETには今後新しい言語機能は追加されないことが発表された[50]。 Visual Studio 2005から利用可能になったC++/CLIはC++マネージ拡張の後継言語で、C#やVB.NETほど統合開発環境との親和性は高くないが、マネージコードとネイティブコード両方を混在して記述することができる唯一の言語である。 汎用プログラミング言語ではないが、コマンドラインシェル向けに特化したDSLとして、PowerShellが存在する。もともとは.NET Framework上に実装された「Windows PowerShell」だったが、クロスプラットフォームな.NET Coreに対応したPowerShell Coreとして再実装され、さらにPowerShellと改称された。
脚注注釈出典
関連項目 |
Portal di Ensiklopedia Dunia