2009-06-24 8 views
27

Dies ist keine Frage, was ist Boxen und Unboxing, es ist eher warum tun Sprachen wie Java und C# brauchen das?Warum brauchen einige Sprachen Boxing und Unboxing?

Ich bin sehr vertraut mit C++, STL und Boost.

In C++ ich so etwas wie dies sehr leicht schreiben konnte,

std::vector<double> dummy; 

Ich habe einige Erfahrung mit Java, aber ich war wirklich überrascht, weil ich so etwas zu schreiben hatte,

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

Meine Frage, warum sollte es ein Objekt sein, was ist technisch so schwierig, primitive Typen einzubeziehen, wenn man über Generika spricht?

+0

Kommend von C++ nach Java, war ich völlig vollständig extrem schockiert, als ich diese Tatsache entdeckte ... – GuLearn

Antwort

46

Was ist technisch so schwierig, primitive Typen einzuschließen, wenn man über Generika spricht?

In Java ist es wegen der Art, wie Generika funktionieren. Generics sind in Java ein Kompiliertrick, der verhindert, dass Sie ein Image Objekt in eine ArrayList<String> setzen. Java-Generika werden jedoch mit Typlöschung implementiert: Die generischen Typinformationen gehen während der Laufzeit verloren. Dies geschah aus Kompatibilitätsgründen, da Generika relativ spät in Java hinzugefügt wurden. Das bedeutet, dass Laufzeit ArrayList<String> ein ArrayList<Object> (oder besser: nur ArrayList ist, das Object in all seinen Methoden erwartet und zurückgibt), die automatisch String beim Abrufen eines Werts konvertiert.

Aber da int leitet sich nicht von Object, Sie es nicht in einem Arraylist setzen können, die (zur Laufzeit) erwartet Object und Sie können eine Object zu int entweder nicht gegossen. Dies bedeutet, dass das Primitiv int in einen Typ eingeschlossen werden muss, der von Object wie Integer erbt.

C# zum Beispiel funktioniert anders. Generics in C# werden auch zur Laufzeit erzwungen und es ist kein Boxing mit einer List<int> erforderlich. Boxen in C# passiert nur, wenn Sie versuchen, einen Wert wie int in einer Referenztyp-Variable wie object zu speichern. Da int in C# von Object in C# erbt, ist das Schreiben object obj = 2 vollkommen gültig, jedoch wird das int eingerahmt, was automatisch vom Compiler erledigt wird (kein Integer Referenztyp ist dem Benutzer oder irgendetwas zur Verfügung gestellt).

+0

nur in der Hoffnung, dass meine Frage bemerkt wird, wage ich zu fragen: Warum Java nicht primitiv für generische durch Autoboxing implementiert? Ich meine im Falle der Liste Kompilieren würde zu Integer automatisch kompiliert werden .. – Dedyshka

11

Boxing und Unboxing sind eine Notwendigkeit, die aus der Art entstanden ist, dass Sprachen (wie C# und Java) ihre Speicherzuweisungsstrategien implementieren.

Bestimmte Typen sind auf dem Stapel und andere auf dem Heap zugeordnet. Um einen stack-allocated-Typ als einen Heap-allocated-Typ zu behandeln, ist ein Boxing erforderlich, um den stack-allocated-Typ auf den Heap zu verschieben. Unboxing ist der umgekehrte Prozess.

In C# Stapel zugewiesenen Typen werden Werttypen genannt (z.B. System.Int32 und System.DateTime) und Heap-Typen zugeordnet sind Referenztypen (z.B. System.Stream und System.String) genannt.

In einigen Fällen ist es vorteilhaft, einen Werttyp wie einen Referenztyp behandeln zu können (Reflexion ist ein Beispiel), aber in den meisten Fällen sollten Boxing und Unboxing vermieden werden.

+4

Vorsicht mit Werttypen und stack-based Allocation. Eric Lippert hatte zwei großartige Blog-Beiträge zu diesem Thema: http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx und http: // Blogs .msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx – Joey

+2

AFAIK, C# verwendet das automatische Boxing für generische Container nur dann, wenn Sie etwas angeben Liste . Die Generika von C# funktionieren ähnlich wie C++ für Werttypen. http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx –

2

Ich glaube, das liegt auch daran, dass Primitive nicht von Object erben. Angenommen, Sie haben eine Methode, die als Parameter überhaupt etwas akzeptieren kann, z.

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

Sie benötigen einen einfachen Grundwert zu dieser Methode zu übergeben, wie:

printer.print(5); 

würden Sie in der Lage sein, das zu tun, ohne Boxen/Unboxing, denn 5 ist ein primitiver und ist kein Objekt. Sie könnten die Druckmethode für jeden primitiven Typ überladen, um diese Funktionalität zu aktivieren, aber es ist ein Schmerz.

1

In Java und C# (im Gegensatz zu C++) alles erweitert Object, so können Auflistungsklassen wie ArrayList Object oder eines seiner Nachkommen (im Grunde alles) halten.

Aus Gründen der Performance wurde jedoch primitiven Java-Typen oder Werttypen in C# ein spezieller Status zugewiesen. Sie sind kein Objekt. Sie können etwas wie (in Java) nicht tun:

7.toString() 

Obwohl toString eine Methode für Object ist. Um diese Leistung zu überbrücken, wurden gleichwertige Objekte erstellt. AutoBoxing entfernt den Boilerplate-Code, um ein Primitiv in seine Wrapper-Klasse zu setzen und es wieder herauszunehmen, wodurch der Code lesbarer wird.

Der Unterschied zwischen Werttypen und Objekten in C# ist grau.Siehe here über ihre Unterschiede.

+0

In C# sind Grundelemente als Strukturen implementiert (int ist zum Beispiel in Int32 im System-Namespace definiert), die Methoden haben 7.ToString() wird gut funktionieren. Und während alles in C# von Object abstammt, ist dies bei Java nicht der Fall. – JulianR

+3

@JulianR: Genauer erben alle CLR-Werttypen von System.ValueType, die wiederum von System.Object erbt. System.ValueType überschreibt viele der virtuellen Methoden von System.Object und verhindert so das Boxen, wenn diese Methoden aufgerufen werden. Die einzigen Methoden, die das Boxen für einen Werttyp bewirken, sind Object.GetType und Object.MemberwiseClone. –

2

Ich kann Ihnen nur für Java sagen, warum Primitivtypen in Generika nicht unterstützt werden.

Zuerst gab es das Problem, dass die Frage, um dies immer zu unterstützen, die Diskussion brachte, ob Java überhaupt primitive Typen haben sollte. Was natürlich die Diskussion über die eigentliche Frage behinderte.

Zweitens war der Hauptgrund, es nicht aufzunehmen, dass sie binäre Abwärtskompatibilität wollten, so dass es unmodifiziert auf einer VM laufen würde, die keine Generika kannte. Dieser Rückwärtskompatibilitäts-/Migrationskompatibilitätsgrund ist auch der Grund, warum jetzt das Collections-API Generics unterstützt und gleich geblieben ist und es (wie in C# bei der Einführung von Generics) kein vollständiges neues Set einer generischen aware Collection-API gibt.

Die Kompatibilität wurde mit Hilfe von ersure (generische Typ Parameter Info zur Kompilierzeit entfernt) gemacht, die auch der Grund ist, warum Sie so viele ungeprüfte Cast Warnungen in Java erhalten.

Sie könnten noch bestätigten Generika hinzufügen, aber es ist nicht so einfach. Das Hinzufügen der Laufzeit des Typs info, anstatt sie zu entfernen, funktioniert nicht, da sie die Binärkompatibilität der Quelle & unterbricht (Sie können keine Raw-Typen mehr verwenden und vorhandenen kompilierten Code nicht aufrufen, weil sie nicht über die entsprechenden Methoden verfügen).

Der andere Ansatz ist die eine C# gewählt: siehe oben

und automatisierte Autoboxing/Unboxing nicht für diesen Anwendungsfall unterstützt wurde, weil die Kosten zu viel Autoboxing.

Java theory and practice: Generics gotchas

1

Jedes Nicht-Array nicht-String-Objekt auf dem Heap gespeichert enthält einen 8- oder 16-Byte-Header (Größen für 32/64-Bit-Systeme), gefolgt von den Inhalten des Objekt des öffentlichen und private Felder. Arrays und Strings haben den obigen Header und einige weitere Bytes, die die Länge des Arrays und die Größe jedes Elements (und möglicherweise die Anzahl der Dimensionen, die Länge jeder zusätzlichen Dimension usw.) definieren, gefolgt von allen Feldern des ersten Element, dann alle Felder der Sekunde, usw. Gegeben eine Referenz auf ein Objekt, kann das System den Header leicht untersuchen und bestimmen, um welchen Typ es sich handelt.

Referenzspeicherorte enthalten einen 4- oder 8-Byte-Wert, der ein auf dem Heap gespeichertes Objekt eindeutig identifiziert. In aktuellen Implementierungen ist dieser Wert ein Zeiger, aber es ist einfacher (und semantisch äquivalent), ihn als eine "Objekt-ID" zu betrachten.

Speicherorte vom Typ "Wert" enthalten den Inhalt der Felder des Wertetyps, haben jedoch keinen zugeordneten Header. Wenn Code eine Variable vom Typ Int32 deklariert, müssen Sie keine Informationen mit dieser Int32 speichern, die besagt, was es ist. Die Tatsache, dass dieser Speicherort eine Int32 enthält, wird effektiv als Teil des Programms gespeichert und muss daher nicht am Speicherort selbst gespeichert werden. Dies stellt eine große Ersparnis dar, wenn z. B. eine Million Objekte aufweist, von denen jede ein Feld vom Typ Int32 aufweist. Jedes der Objekte, die das Int32 enthalten, hat einen Header, der die Klasse identifiziert, die es bedienen kann. Da eine Kopie dieses Klassencodes mit jeder der Millionen Instanzen arbeiten kann, ist die Tatsache, dass das Feld ein Teil des Codes ist, viel effizienter, als wenn der Speicher für jedes dieser Felder Informationen darüber enthält, worum es sich handelt .

Boxing ist erforderlich, wenn eine Anforderung zum Übergeben des Inhalts eines Werttypspeicherorts an einen Code gestellt wird, der diesen bestimmten Wertetyp nicht erwartet. Code, der Objekte unbekannten Typs erwartet, kann einen Verweis auf ein Objekt akzeptieren, das auf dem Heap gespeichert ist. Da jedes auf dem Heap gespeicherte Objekt über einen Header verfügt, der angibt, um welchen Objekttyp es sich handelt, kann Code diesen Header immer dann verwenden, wenn ein Objekt so verwendet werden muss, dass sein Typ bekannt sein muss.

Beachten Sie, dass es in. NET möglich ist, sogenannte generische Klassen und Methoden zu deklarieren. Jede solche Deklaration erzeugt automatisch eine Familie von Klassen oder Methoden, die identisch sind mit Ausnahme des Objekttyps, von dem sie zu handeln erwarten. Wenn man eine Int32 an eine Routine DoSomething<T>(T param) übergibt, wird automatisch eine Version der Routine erzeugt, in der jede Instanz des Typs T effektiv durch Int32 ersetzt wird. Diese Version der Routine weiß, dass jeder als Typ T deklarierte Speicherort einen Int32 enthält. Genau wie in dem Fall, in dem eine Routine für die Verwendung eines Int32 Speicherorts fest codiert war, ist es nicht erforderlich, Typinformationen mit diesen zu speichern Orte selbst.