37

これは、ボックス化とボックス化解除とは何かという問題ではなく、Java や C# などの言語でそれが必要な理由です。

私は C++、STL、Boost に精通しています。

C++では、このようなものを非常に簡単に書くことができました.

std::vector<double> dummy;

私はJavaの経験がありますが、このようなことを書かなければならなかったので、本当に驚きました.

ArrayList<Double> dummy = new ArrayList<Double>();

私の質問は、なぜ Object である必要があるのですか? Generics について話すときにプリミティブ型を含めることが技術的に難しいのは何ですか?

4

6 に答える 6

58

Generics について話すときにプリミティブ型を含めるのが技術的に難しいのは何ですか?

Java の場合は、ジェネリクスの動作方法が原因です。Java では、ジェネリックはコンパイル時のトリックであり、ImageオブジェクトをArrayList<String>. ただし、Java のジェネリックは型消去を使用して実装されています。ジェネリック型の情報は実行時に失われます。これは互換性の理由によるもので、ジェネリックは Java の寿命のかなり遅い時期に追加されたためです。これは、実行時に、値を取得するときに自動的にキャストするArrayList<String>事実上ArrayList<Object>(またはより良い:すべてのメソッドでArrayList期待して返す)ことを意味します。ObjectString

しかし、intは から派生していないため、Object(実行時に) を期待する ArrayList に入れることはObjectできず、どちらかにキャストすることもできませObjectint。これは、プリミティブintを から継承する型にラップする必要があることを意味しObjectますInteger

たとえば、C# は動作が異なります。C# のジェネリックも実行時に適用され、List<int>. C# でのボックス化は、 のようintな参照型変数のような値の型を格納しようとした場合にのみ発生しますobjectintC# は C# から継承されているためObject、書き込みobject obj = 2は完全に有効ですが、コンパイラによって自動的に行われる int がボックス化されます (Integer参照型はユーザーなどに公開されません)。

于 2009-06-24T19:47:40.947 に答える
12

ボックス化とボックス化解除は、言語 (C# や Java など) がメモリ割り当て戦略を実装する方法から生まれた必要性です。

特定の型はスタックに割り当てられ、他の型はヒープに割り当てられます。スタック割り当て型をヒープ割り当て型として扱うには、スタック割り当て型をヒープに移動するためのボックス化が必要です。開梱は逆のプロセスです。

C# では、スタックに割り当てられた型は値型(System.Int32および などSystem.DateTime) と呼ばれ、ヒープに割り当てられた型は参照型 (System.Streamおよび などSystem.String) と呼ばれます。

場合によっては、値の型を参照型のように扱えると有利な場合がありますが (リフレクションはその一例です)、ほとんどの場合、ボックス化とボックス化解除は避けるのが最善です。

于 2009-06-24T19:21:24.150 に答える
2

これも、プリミティブが Object を継承していないためだと思います。パラメータとして何でも受け入れることができるようにしたいメソッドがあるとします。

class Printer {
    public void print(Object o) {
        ...
    }
}

次のような単純なプリミティブ値をそのメソッドに渡す必要がある場合があります。

printer.print(5);

5 はプリミティブであり、オブジェクトではないため、ボックス化/ボックス化解除せずにそれを行うことができます。このような機能を有効にするために、プリミティブ型ごとに print メソッドをオーバーロードできますが、面倒です。

于 2009-06-24T19:29:14.353 に答える
2

Java については、ジェネリックでプリミティブ型をサポートしていない理由を説明することしかできません。

最初に、Java がプリミティブ型を持つべきかどうかについて、毎回これをサポートするという質問が議論に持ち込まれるという問題がありました。もちろん、これは実際の質問の議論を妨げました。

2 つ目の主な理由は、ジェネリックを認識しない VM で変更せずに実行できるように、バイナリの下位互換性が必要だったためです。この下位互換性/移行互換性の理由は、コレクション API がジェネリックをサポートし、同じままであり、(ジェネリックを導入したときの C# のように) ジェネリックを意識したコレクション API の完全な新しいセットがない理由でもあります。

互換性は ersure (コンパイル時に削除されるジェネリック型パラメーター情報) を使用して行われました。これは、Java で非常に多くの未チェックのキャスト警告が表示される理由でもあります。

具体化されたジェネリックを追加することはできますが、それほど簡単ではありません。type info add runtime を削除する代わりに追加するだけでは、ソースとバイナリの互換性が失われるため機能しません (対応するメソッドがないため、生の型を使用し続けることはできず、既存のコンパイル済みコードを呼び出すことはできません)。 )。

もう 1 つのアプローチは、C# が選択したものです。上記を参照してください。

また、オートボクシングのコストが高すぎるため、このユースケースでは自動オートボクシング/アンボクシングはサポートされていません。

Java の理論と実践: ジェネリックの落とし穴

于 2009-06-24T20:09:11.420 に答える
2

ヒープに格納されたすべての非配列非文字列オブジェクトには、8 または 16 バイトのヘッダー (32/64 ビット システムのサイズ) が含まれ、その後にそのオブジェクトのパブリック フィールドとプライベート フィールドの内容が続きます。配列と文字列には、上記のヘッダーに加えて、配列の長さと各要素のサイズ (場合によっては次元数、追加の各次元の長さなど) を定義するバイトがいくつかあり、その後に最初のすべてのフィールドが続きます。オブジェクトへの参照が与えられると、システムはヘッダーを簡単に調べて、それがどのタイプであるかを判断できます。

参照型の格納場所には、ヒープに格納されているオブジェクトを一意に識別する 4 バイトまたは 8 バイトの値が保持されます。現在の実装では、その値はポインターですが、「オブジェクト ID」と考える方が簡単です (意味的に同等です)。

値型の格納場所には、値型のフィールドの内容が保持されますが、関連するヘッダーはありません。コードが type の変数を宣言する場合、それが何であるかInt32を示す情報を保存する必要はありません。Int32その場所が保持するという事実Int32は、プログラムの一部として効果的に保存されるため、場所自体に保存する必要はありません。たとえば、それぞれが type のフィールドを持つ 100 万個のオブジェクトがある場合、これは大きな節約になりますInt32。を保持する各オブジェクトにInt32は、それを操作できるクラスを識別するヘッダーがあります。そのクラス コードの 1 つのコピーは、フィールドがInt32コードの一部であることは、それらのフィールドのすべてのストレージにそれが何であるかに関する情報を含めるよりもはるかに効率的です。

値型の格納場所の内容を、その特定の値型を期待することを知らないコードに渡す要求が行われる場合、ボクシングが必要です。未知の型のオブジェクトを予期するコードは、ヒープに格納されたオブジェクトへの参照を受け入れることができます。ヒープに格納されたすべてのオブジェクトには、そのオブジェクトのタイプを識別するヘッダーがあるため、コードは、そのタイプを知る必要がある方法でオブジェクトを使用する必要がある場合はいつでも、そのヘッダーを使用できます。

.net では、ジェネリック クラスおよびメソッドと呼ばれるものを宣言できることに注意してください。このような各宣言は、動作することが期待されるオブジェクトのタイプを除いて同一のクラスまたはメソッドのファミリを自動的に生成します。Int32ルーチンにを渡すと、型のすべてのインスタンスが効果的に に置き換えられDoSomething<T>(T param)たバージョンのルーチンが自動的に生成されます。そのバージョンのルーチンは、type として宣言されたすべての格納場所が を保持していることを認識します。そのため、ルーチンが格納場所を使用するようにハードコードされている場合と同様に、それらの場所自体に型情報を格納する必要はありません。TInt32TInt32Int32

于 2012-06-09T00:38:49.417 に答える
1

Java および C# では (C++ とは異なり) すべてが Object を拡張するため、ArrayList などのコレクション クラスは Object またはその子孫 (基本的には何でも) を保持できます。

ただし、パフォーマンス上の理由から、Java のプリミティブ、または C# の値型には特別なステータスが与えられました。それらはオブジェクトではありません。次のようなことはできません (Java で):

 7.toString()

toString は Object のメソッドですが。このうなずきをパフォーマンスに橋渡しするために、同等のオブジェクトが作成されました。AutoBoxing は、プリミティブをそのラッパー クラスに配置し、再度取り出すというボイラープレート コードを削除し、コードをより読みやすくします。

C# での値型とオブジェクトの違いは、より曖昧です。それぞれの違いについてはこちらをご覧ください。

于 2009-06-24T19:29:31.653 に答える