web-dev-qa-db-ja.com

なぜJavaはクラスの外に関数定義が存在することを許可しないのですか?

C++とは異なり、Javaでは、クラス内の関数宣言とクラス外の定義だけを持つことはできません。なぜそうなのですか?

Java内の1つのファイルに含まれるクラスは1つだけで、他には何もないことを強調しますか?

22
user52009

C++とJava=の違いは、言語がリンケージの最小単位と見なすものにあります。

Cはアセンブリと共存するように設計されているので、そのユニットはアドレスによって呼び出されるサブルーチンです。 (これは、FORTRANなどのネイティブオブジェクトファイルにコンパイルする他の言語にも当てはまります。)言い換えると、関数foo()を含むオブジェクトファイルには、解決される__foo_と呼ばれるシンボルがあります。リンク時に_0xdeadbeef_のようなアドレスにのみそれだけです。関数が引数を取る場合は、アドレスを呼び出す前に、関数が期待するすべての順序が正しいことを確認するのは呼び出し元の責任です。通常、これはスタックに積み上げることで行われ、コンパイラーが面倒な作業を行い、プロトタイプが一致することを確認します。オブジェクトファイル間でこれをチェックすることはありません。コールリンケージを間違えた場合、コールは予定どおりに発信されず、警告は表示されません。危険にもかかわらず、これにより、多くの手間をかけずに、複数の言語(アセンブリを含む)からコンパイルされたオブジェクトファイルを機能するプログラムにリンクすることが可能になります。

C++は、そのすべての追加の空想にもかかわらず、同じように動作します。コンパイラーは、名前空間、クラス、メソッド/メンバー/などをシューホーンします。クラスの内容を単一の名前にフラット化することにより、この規則に合わせて、クラスを一意にする方法でマングルします。たとえば、Foo::bar(int baz)のようなメソッドは、実行時にオブジェクトファイルと__ZN4Foo4barEi_のようなアドレスに入れられると、_0xBADCAFE_に変換される可能性があります。これはコンパイラに完全に依存しているため、異なるマングリングスキームを持つ2つのオブジェクトをリンクしようとすると、運が悪くなります。これは醜いのですが、_extern "C"_ブロックを使用してマングリングを無効にできるため、他の言語からC++コードに簡単にアクセスできるようになります。 C++はCから浮動関数の概念を継承しました。これは主に、ネイティブオブジェクト形式で許可されているためです。

Javaは、独自のオブジェクトファイル形式である_.class_ファイルを使用して、孤立した世界に住む別の獣です。クラスファイルには その内容に関する豊富な情報 が含まれています。これにより、実行時にネイティブリンケージメカニズムでは夢にも思わなかったクラスの処理を環境で実行できます。その情報はどこかで始まる必要があり、その開始点はclassです。利用可能な情報により、コンパイルされたコードは、C、C++、または他の言語のようにソースコードに記述を含む個別のファイルを必要とせずに、それ自体を記述することができます。これにより、実行時であってもネイティブリンケージの欠如を使用してすべてのタイプセーフティベネフィット言語が提供され、リフレクションを使用してファイルから任意のクラスを釣り出し、何かが一致しない場合に保証されたエラーで使用できるようになります。 。

まだ理解していない場合は、この安全性のすべてにトレードオフが伴います。JavaプログラムにリンクするものはすべてJavaである必要があります。(「リンク」とは、いつでも何かを意味します [〜#〜] jni [〜#〜] を使用して、ネイティブコードに(ネイティブの意味で)リンクできますが、次のような暗黙のコントラクトがありますネイティブサイドを壊すと、両方のピースを所有することになります。

Javaは大きく、Adaが以前の10年間にそうであったように、最初に導入されたとき、利用可能なハードウェアでは特に高速ではありませんでした。 Jim Goslingだけが、クラスJavaの最小リンケージ単位を作成する動機を明確に言えますが、フリーフローターを追加することでランタイムに追加されることで、複雑さが解消されたのではないかと推測する必要があります。

33
Blrfl

Wikipediaによると、答えは、Javaはシンプルでオブジェクト指向になるように設計されています。関数は、それらが定義されているクラスを操作するためのものです。クラスの外では意味がありません。Javaは、純粋なOOPに適合しなかったため、これを許可しないという結論にジャンプします。

Googleをすばやく検索しても、Java言語設計の動機はあまりわかりませんでした。

14
mortalapeman

本当の問題は、C++の方法で物事を続けていくメリットは何であり、ヘッダーファイルの本来の目的は何でしたか?簡単に言えば、多くのクラスが同じタイプを参照する可能性のある大規模なプロジェクトで、ヘッダーファイルスタイルを使用すると、コンパイル時間が短縮されます。これは、コンパイラの性質上、Javaおよび.NETでは必要ありません。

ここでこの回答を参照してください: ヘッダーファイルは実際に良いですか?

11
Jonathan Henson

Javaファイルはクラスを表します。クラスの外にプロシージャがある場合、スコープはどのようになりますか?それはグローバルでしょうか?または、クラスに属していますJavaファイルは?

おそらく、理由のために、他のファイルではなくJavaファイルに配置します。これは、他のクラスよりもそのクラスに関係しているためです。クラス外のプロシージャが実際にそのクラスに関連付けられている場合、それからそれが属するクラスの中に強制的に入れてみませんか?Javaはこれをクラス内の静的メソッドとして扱います。

クラス外のプロシージャが許可された場合、おそらくそれが宣言されたファイルのクラスへの特別なアクセス権がないため、データを変更しないユーティリティ関数に制限されます。

このJava制限の唯一の欠点は、どのクラスにも関連付けられていないグローバルプロシージャが本当にある場合、MyGlobalsクラスを作成してそれらを保持し、そのクラスをインポートすることです。これらの手順を使用する他のすべてのファイル。

実際、Javaインポートメカニズムが機能するためには、この制限が必要です。すべてのAPIを使用できるため、Javaコンパイラは、何をコンパイルし、コンパイル対象、つまり、ファイルの先頭にある明示的なインポート文。グローバルを人工的なクラスにグループ化する必要なしに、Javaコンパイラーにコンパイルするように指示するにはyourグローバルではなくany and allグローバルがクラスパスにありますか?doStuff()があり、他の誰かがdoStuff()を持っている名前空間の衝突はどうですか?それは機能しません。 MyClass.doStuff()およびYourClass.doStuff()を指定すると、これらの問題が修正されます。プロシージャを強制的にMyClassの外ではなく内部に移動させると、この制限が明確になるだけで、コードに追加の制限が課されることはありません。

Javaには多くの問題があります-シリアライゼーションには小さないぼがたくさんあるので、実用的ではありません(SerialVersionUIDを考えてみてください)。また、シングルトンやその他の一般的な設計パターンを壊すためにも使用できます。 Objectのclone()メソッドは、deepClone()と浅いClone()に分割され、タイプセーフである必要があります。すべてのAPIクラスは、デフォルト(Scalaでの方法)で不変にすることができます。しかし、すべての手続きがクラスに属さなければならないという制限は良いものです。これは主に、煩わしい制限を課すことなく、言語とコードを単純化して明確にするのに役立ちます。

3
GlenPeterson

答えた人と有権者のほとんどはこの質問を誤解していると思います。それは彼らがC++を知らないことを反映しています。

「定義」と「宣言」は、C++で非常に具体的な意味を持つ単語です。

OPはJavaの動作方法を変更することを意味するものではありません。これは純粋に構文に関する質問です。有効な質問だと思います。

C++では、メンバー関数をdefineする2つの方法があります。

最初の方法はJava方法です。すべてのコードを中括弧の中に置くだけです。

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

2番目の方法:

class Box {
public:  
    // declaration of member function
    void change(int newInt); 
private:
    int _m
}

// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
    this._m = newInt;
}

どちらのプログラムも同じです。関数changeは依然としてメンバー関数です。クラスの外には存在しません。さらに、クラス定義には、Javaと同様に、すべてのメンバー関数と変数の名前と型を含める必要があります。

Jonathan Hensonは、これがC++でヘッダーが機能する方法の成果物であると考えています。ヘッダーファイルに宣言を記述し、別の.cppファイルに実装を含めることができるため、プログラムがODR(1つの定義ルール)に違反しません。ただし、それ以外にもメリットがあります。大きなクラスのインターフェースを一目で確認できます。

Javaでは、抽象クラスまたはインターフェースでこの効果を概算できますが、実装クラスと同じ名前にすることはできないため、扱いにくくなっています。

3
Erik van Velzen

クラスローディングメカニズムのアーティファクトだと思います。各クラスファイルは、ロード可能なオブジェクトのコンテナです。クラスファイルの「外側」の場所はありません。

2
ddyer

C++では、クラスのテキスト全体を、そのメンバーを使用するかインスタンスを生成するすべてのコンパイル単位の一部としてコンパイルする必要があります。コンパイル時間を適切に保つ唯一の方法は、クラス自体のテキストに、可能な限り、実際にコンシューマーにとって必要なものだけを含めることです。 C++メソッドがそれらを含むクラスの外部で記述されることが多いという事実は、コンパイラーがすべてのクラスメソッドのテキストを、クラスが使用されるすべてのコンパイルユニットに対して1回処理する必要があるという事実に動機付けられた、本当に厄介な下劣なハックです非常識なビルド時間。

Javaでは、コンパイルされたクラスファイルには、とりわけ、C++の.hファイルとほぼ同等の情報が含まれています。クラスのコンシューマーは、コンパイラーに.Javaファイルを処理させることなく、そのファイルから必要なすべての情報を抽出できます。 .hファイルに、実装とそこに含まれるクラスのクライアントの両方で利用できる情報が含まれているC++とは異なり、Javaのフローは逆になります。クライアントが使用するファイルはソースファイルではありませんクラスコードのコンパイルで使用されますが、代わりにクラスコードファイルの情報を使用してコンパイラによって生成されます。クライアントが必要とする情報を含むファイルと実装を含むファイルの間でクラスコードを分割する必要がないため、Javaはそのような分割を許可していません。

0
supercat

Javaによく似たC#は、部分メソッドが排他的にプライベートである点を除いて、部分メソッドを使用することでこの種の機能を備えています。

部分的なメソッド: http://msdn.Microsoft.com/en-us/library/6b0scde8.aspx

部分的なクラスとメソッド: http://msdn.Microsoft.com/en-us/library/wa80x488.aspx

Javaが同じことができなかった理由はわかりませんが、おそらく、この機能を言語に追加するためにユーザーベースから認識されたニーズがあるかどうかにかかっています。

C#のほとんどのコード生成ツールは部分クラスを生成するので、開発者は必要に応じて、手動で作成したコードを別のファイルのクラスに簡単に追加できます。