私は最近Javaの学習を始めましたが、すべてのJavaクラスを個別のファイルで宣言する必要があるのは非常に奇妙でした。私はC#プログラマーであり、C#はそのような制限を強制しません。
なぜJavaはこれを行うのですか?設計上の考慮事項はありましたか?
編集(いくつかの回答に基づく):
IDEの時代にJavaがこの制限を取り除いていないのはなぜですか?これにより、既存のコードが破損することはありません(または破損しますか?)。
私は最近Javaの学習を始めましたが、すべてのJavaクラスを個別のファイルで宣言する必要があるのは非常に奇妙でした。私はC#プログラマーであり、C#はそのような制限を強制しません。
なぜJavaはこれを行うのですか?設計上の考慮事項はありましたか?
編集(いくつかの回答に基づく):
IDEの時代にJavaがこの制限を取り除いていないのはなぜですか?これにより、既存のコードが破損することはありません(または破損しますか?)。
私は C# ソリューションを使用してこれを実行し (複数のパブリック クラスを含むファイルをすべて削除)、それらを個々のファイルに分割しました。これにより、作業がはるかに簡単になりました。
ファイルに複数の public クラスがある場合、いくつかの問題があります。
ファイルの名前は何ですか?公開クラスの1つ?別名?ソリューションのコード構成やファイルの命名規則が貧弱であるために、人々は十分な問題を抱えています。
また、ファイル/プロジェクト エクスプローラーをブラウズしているときに、物事が非表示にならないのは良いことです。たとえば、1 つのファイルを表示してドリルダウンすると、200 個のクラスがすべてまとめられているとします。1 つのファイルと 1 つのクラスがある場合は、テストをより適切に整理し、ソリューションの構造と複雑さを理解することができます。
Javaはこれを正しく理解したと思います。
Java 言語仕様、第 3 版によると、次のようになります。
この制限は、コンパイル単位ごとにそのような型が最大で 1 つ存在する必要があることを意味します。この制限により、Java プログラミング言語のコンパイラまたは Java 仮想マシンの実装は、パッケージ内の名前付きクラスを簡単に見つけることができます。たとえば、パブリック タイプの wet.sprocket.Toad のソース コードは、ディレクトリ wet/sprocket のファイル Toad.java にあり、対応するオブジェクト コードは、同じディレクトリのファイル Toad.class にあります。
強調は私です。
基本的に、彼らは OS のディレクトリ セパレーターを名前空間のドットに、またはその逆に変換したかったようです。
そうです、それはある種の設計上の考慮事項でした。
Javaで考えるより
:
コンパイル単位 (ファイル) ごとに 1 つのパブリック クラスしか存在できません。各コンパイル単位には、そのパブリック クラスによって表される単一のパブリック インターフェイスが
あるという考え方です。サポートする「友好的な」クラスをいくつでも持つことができます。コンパイル ユニット内に複数の public クラスがある場合、コンパイラはエラー メッセージを表示します。
仕様より(7.2.6)
パッケージがファイル システム (?7.2.1) に保存されている場合、ホスト システムは、型名と次のいずれかが true の場合、拡張子 (.java または .jav など) :
- 型は、型が宣言されているパッケージの他のコンパイル単位のコードによって参照されます。
- この型は public と宣言されています (したがって、他のパッケージのコードからアクセスできる可能性があります)。
- この制限は、コンパイル単位ごとにそのような型が最大で 1 つ存在する必要があることを意味します。
- この制限により、Java プログラミング言語のコンパイラまたは Java 仮想マシンの実装がパッケージ内の名前付きクラスを簡単に見つけることができます。たとえば、パブリック タイプの wet.sprocket.Toad のソース コードは、ディレクトリ wet/sprocket のファイル Toad.java にあり、対応するオブジェクト コードは、同じディレクトリのファイル Toad.class にあります。
要するに、クラスパスにすべてをロードすることなくクラスを見つけることかもしれません。
編集:「選択してもよい」は、その制限に従わない可能性を残しているように思われ、「可能性があります」の意味は、 RFC 2119で説明されている可能性があります(つまり、「オプション」)
ただし、実際には、これは非常に多くのプラットフォームで実施されています非常に多くのツールと IDE に依存しているため、その制限を強制しないことを選択した「ホスト システム」は見当たりません。
「むかしむかしオーク…」より
設計上の理由が分かればほとんどのことがそうであるように、コンパイラーはすべてのコンパイル単位 (.java ファイル) を介して追加のパスを作成し、どのクラスがどこにあるかを把握する必要があり、それによってコンパイルがさらに遅くなります。 .
(ノート:
Oak バージョン 0.2のOak 言語仕様(postcript ドキュメント): Oakは、現在 Java として一般に知られているものの元の名前であり、このマニュアルは、Oak (つまり Java) で利用可能な最も古いマニュアルです。
Java の起源に関する詳細な歴史については、Green ProjectとJava(TM) Technology: An Early History
を参照してください ) 。
これは、Java が開発者の観点から単純さを念頭に置いて作成されたという意味での混乱を避けるためです。「プライマリ」クラスはパブリック クラスであり、同じ名前のファイルとクラスのパッケージで指定されたディレクトリにある場合、(人間が) 簡単に見つけることができます。
Java 言語が開発されたのは 90 年代半ば、IDE によってコード ナビゲーションと検索が簡単になる前の時代であることを思い出してください。
それが、言語設計者がそうすることにした方法です。主な理由は、コンパイラのパススルーを最適化するためだと思います。コンパイラは、パブリック クラスを見つけるためにファイルを推測または解析する必要はありません。これは実際には良いことだと思います。これにより、コード ファイルがはるかに見つけやすくなり、1 つのファイルに多くの情報を入れすぎないようにすることができます。また、Java がコード ファイルをパッケージと同じディレクトリ構造に配置することを強制する方法も気に入っています。これにより、任意のコード ファイルを簡単に見つけることができます。
1 つのファイルに複数の Java トップレベル クラスを含めることは、技術的には合法です。ただし、これは (ほとんどの場合) 悪い習慣と見なされており、これを行うと一部の Java ツールが機能しない場合があります。
JLS は次のように述べています。
パッケージがファイル システム (§7.2.1) に保存される場合、ホスト システムは、型名と次のいずれかに該当する場合、拡張子 (.java または .jav など):
- 型は、型が宣言されているパッケージの他のコンパイル単位のコードによって参照されます。
- この型は public と宣言されています (したがって、他のパッケージのコードからアクセスできる可能性があります)。
JLS テキストでのmayの使用に注意してください。これは、コンパイラがこれを無効として拒否する場合もあれば、そうでない場合もあるということです。ソース コード レベルで移植可能な Java コードをビルドしようとしている場合、これは良い状況ではありません。したがって、1 つのソース ファイル内の複数のクラスが開発プラットフォームで機能する場合でも、これを行うのは悪い習慣です。
私の理解では、この「拒否する許可」は、より広範なプラットフォームでの Java の実装を容易にすることを部分的に意図した設計上の決定です。(逆に) JLS が複数のクラスを含むソース ファイルをサポートするためにすべてのコンパイラを必要とする場合、ファイル システム ベースではないプラットフォームで Java を実装するという概念上の問題が発生します。
実際には、経験豊富な Java 開発者は、これを行うことができることをまったく見逃していません。モジュール化と情報の隠蔽は、パッケージ、クラス アクセス修飾子、および内部またはネストされたクラスを適切に組み合わせて使用することで、より適切に実行できます。
クラスが他の 1 つのクラスによってのみ使用される場合は、プライベート 内部クラスにします。このようにして、ファイルに複数のクラスが含まれます。
クラスが他の複数のクラスで使用されている場合、これらのクラスのどれを同じファイルに入れますか? 3つすべて?すべてのクラスを単一のファイルに入れることになります...
IDE の時代に Java がこの制限を取り除かないのはなぜですか? これにより、既存のコードが壊れることはありません (またはそうなりますか?)。
これで、すべてのコードが統一されました。ソース ファイルを見ると、何が期待できるかがわかります。どのプロジェクトでも同じです。Java がこの規則を削除した場合、作業するすべてのプロジェクトのコード構造を再学習する必要があります。今では一度学習すれば、どこにでも適用できます。IDE にすべてを任せるべきではありません。
これにより、Foobar.class から Foobar.java に移行するためのより単純なヒューリスティックが可能になります。
Foobar が任意の Java ファイルに存在する可能性がある場合、マッピングに問題があります。これは、クラスの定義を見つけるために、最終的にすべての Java ファイルのフル スキャンを実行する必要があることを意味する可能性があります。
個人的には、これが奇妙なルールの 1 つであることがわかりました。これらを組み合わせると、Java アプリケーションは非常に大きくなり、それでも頑丈なままになる可能性があります。
質問に対する答えではありませんが、それでもデータポイントです。
私は個人的な C++ ユーティリティ ライブラリのヘッダーを grep し (ここから自分で入手できます)、実際にクラスを宣言するほとんどすべてのヘッダー ファイル (フリー関数を宣言するだけのものもあります) は、複数のクラスを宣言します。私は自分自身を非常に優れた C++ デザイナーだと考えています (ただし、ライブラリは場所によっては少し行き詰まりがあります - 私はその唯一のユーザーです)。したがって、少なくとも C++ では、同じファイル内の複数のクラスは正常であることをお勧めします。そして良い習慣さえ。