2009-06-24 9 views
27

Esto no es una cuestión de lo que es boxeo y unboxing, es más bien ¿por qué do idiomas como Java y C# necesitan eso?¿Por qué algunos idiomas necesitan Boxeo y Unboxing?

Estoy muy familiarizado con C++, STL y Boost.

En C++ que podría escribir algo como esto muy fácilmente,

std::vector<double> dummy; 

tengo algo de experiencia con Java, pero yo estaba muy sorprendido porque tenía que escribir algo como esto,

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

Mi pregunta, ¿por qué debería ser un objeto ?, ¿qué tan difícil es técnicamente incluir tipos primitivos cuando se habla de genéricos?

+0

Viniendo de C++ a Java, quedé completamente completamente conmocionado cuando descubrí este hecho ... – GuLearn

Respuesta

46

¿Qué tan difícil es técnicamente incluir tipos primitivos cuando hablamos de genéricos?

En el caso de Java, es por la forma en que funcionan los genéricos. En Java, los genéricos son un truco en tiempo de compilación, que le impide poner un objeto Image en un ArrayList<String>. Sin embargo, los genéricos de Java se implementan con borrado de tipo: la información de tipo genérico se pierde durante el tiempo de ejecución. Esto fue por razones de compatibilidad, porque los genéricos se agregaron bastante tarde en la vida de Java. Esto significa que, en tiempo de ejecución, un ArrayList<String> es efectivamente un ArrayList<Object> (o mejor: solo ArrayList que espera y devuelve Object en todos sus métodos) que se lanza automáticamente al String cuando recupera un valor.

Pero desde int no deriva de Object, no se puede poner en un ArrayList que espera (en tiempo de ejecución) Object y no se puede emitir un Object a int tampoco. Esto significa que la primitiva int debe estar envuelta en un tipo que hereda de Object, como Integer.

C# por ejemplo, funciona de manera diferente. Los genéricos en C# también se aplican durante el tiempo de ejecución y no se requiere el uso de un boxeo con un List<int>. El boxeo en C# solo ocurre cuando intenta almacenar un tipo de valor como int en una variable de tipo de referencia como object. Desde int en C# hereda de Object en C#, escribiendo object obj = 2 es perfectamente válido, sin embargo aparece dentro del int, que se realiza automáticamente por el compilador (ningún tipo Integer referencia se expone al usuario o cualquier cosa).

+0

con la esperanza de que mi pregunta sea notada me atrevo a preguntar: ¿por qué java no implementó la primitiva genérica a través de autoboxing? Quiero decir en caso de que la lista haya compilado automáticamente en Integer ... – Dedyshka

11

El boxeo y el desembalaje son una necesidad nacida de la forma en que los lenguajes (como C# y Java) implementan sus estrategias de asignación de memoria.

Ciertos tipos se asignan en la pila y otros en el montón. Para tratar un tipo asignado a la pila como un tipo asignado a un montón, se requiere el uso de boxeo para mover el tipo asignado a la pila al montón. Unboxing es los procesos inversos.

en C# tipos de pila asignados son llamados tipos de valor (por ejemplo System.Int32 y System.DateTime) y tipos de montón asignados son llamados tipos de referencia (por ejemplo System.Stream y System.String).

En algunos casos, es ventajoso poder tratar un tipo de valor como un tipo de referencia (la reflexión es un ejemplo) pero en la mayoría de los casos, es mejor evitar el boxeo y el desempaquetado.

+4

Cuidado con los tipos de valores y la asignación basada en la pila. Eric Lippert tuvo dos excelentes publicaciones en el blog sobre ese tema: http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx y http: // blogs .msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx – Joey

+2

AFAIK, C# no utiliza el auto-boxing en contenedores genéricos a menos que especifique algo como Lista . Los genéricos de C# funcionan de manera similar a C++ para los tipos de valores. http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx –

2

Creo que esto también se debe a que las primitivas no heredan de Object. Supongamos que tiene un método que quiere poder aceptar cualquier cosa como parámetro, p. Ej.

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

Es posible que tenga que pasar un simple valor primitivo a ese método, como:

printer.print(5); 

Usted sería capaz de hacerlo sin el boxeo/unboxing, porque 5 es un primitivo y no es una Objeto. Podría sobrecargar el método de impresión para cada tipo primitivo para habilitar dicha funcionalidad, pero es un problema.

1

En Java y C# (a diferencia de C++) todo amplía Object, por lo que las clases de colección como ArrayList pueden contener Object o cualquiera de sus descendientes (básicamente cualquier cosa).

Por motivos de rendimiento, sin embargo, las primitivas en java, o los tipos de valores en C#, recibieron un estado especial. Ellos no son un objeto No puede hacer algo como (en Java):

7.toString() 

Aunque toString es un método en Object. Con el fin de unir este guiño al rendimiento, se crearon objetos equivalentes. AutoBoxing elimina el código repetitivo de tener que poner una primitiva en su clase contenedora y volver a sacarla, haciendo que el código sea más legible.

La diferencia entre los tipos de valores y los objetos en C# es más gris.Ver here sobre cómo son diferentes.

+0

En C#, las primitivas se implementan como estructuras (int por ejemplo se define en Int32 en el espacio de nombres del sistema) que tienen métodos para 7.ToString() funcionará bien. Y aunque todo en C# se deriva de Object, este no es el caso de Java. – JulianR

+3

@JulianR: Más específicamente, todos los tipos de valores CLR heredan de System.ValueType que a su vez hereda de System.Object. System.ValueType anula muchos de los métodos virtuales de System.Object, evitando así el uso de box cuando se invocan esos métodos. Los únicos métodos que causan el boxeo para un tipo de valor son Object.GetType y Object.MemberwiseClone. –

2

Sólo puedo decirle para Java por lo que no admite tipos de primitve en los genéricos.

Primero fue el problema de que la cuestión de apoyar esto cada vez llevado a la discusión si java debe tener incluso los tipos primitivos. Lo cual, por supuesto, dificultó la discusión de la pregunta real.

En segundo lugar la principal razón de no incluirla era que querían compatibilidad binaria por lo que sería ejecutar sin modificar en una máquina virtual no tiene conocimiento de los genéricos. Esta razón de compatibilidad con la migración/compatibilidad con la migración también explica por qué ahora la API de colecciones admite genéricos y se mantuvo igual y no hay (como en C# cuando introdujeron genéricos) un conjunto completo nuevo de una API de recopilación con reconocimiento genérico.

La compatibilidad se realizó utilizando ersure (información de los parámetros de tipo genérico eliminado en tiempo de compilación), que es también la razón por la que recibe tantas advertencias elenco sin marcar en Java.

Se podría añadir todavía los genéricos reificadas pero no es tan fácil. Solo agregar el tipo de información agregar tiempo de ejecución en lugar de eliminarlo no funcionará, ya que rompe la compatibilidad binaria fuente & (no puede seguir utilizando tipos sin procesar y no puede llamar al código compilado existente porque no tienen los métodos correspondientes)

El otro enfoque es el que eligió C#: véase más arriba

Y autoboxing/unboxing automatizado no contó con el apoyo para este caso de uso debido a los costos autoboxing demasiado.

Java theory and practice: Generics gotchas

1

Cada objeto no cadena no-array almacenado en el montón contiene una cabecera de 8 ó 16 bytes (tamaños para 32/sistemas de 64 bits), seguido por el contenido de ese del público objeto y campos privados Las matrices y las cadenas tienen el encabezado anterior, más algunos bytes más que definen la longitud de la matriz y el tamaño de cada elemento (y posiblemente el número de dimensiones, la longitud de cada dimensión extra, etc.), seguidos por todos los campos de la primera elemento, luego todos los campos del segundo, etc. Dada una referencia a un objeto, el sistema puede examinar fácilmente el encabezado y determinar de qué tipo es.

Las ubicaciones de almacenamiento de tipo de referencia tienen un valor de cuatro u ocho bytes que identifica de manera única un objeto almacenado en el montón. En las implementaciones actuales, ese valor es un puntero, pero es más fácil (y semánticamente equivalente) pensarlo como un "ID de objeto".

Las ubicaciones de almacenamiento de valor mantienen el contenido de los campos del tipo de valor, pero no tienen ningún encabezado asociado. Si el código declara una variable de tipo Int32, no es necesario que almacene información con ese Int32 diciendo de qué se trata. El hecho de que esa ubicación contenga un Int32 se almacena efectivamente como parte del programa, por lo que no tiene que almacenarse en la ubicación misma. Esto representa un gran ahorro si, por ejemplo, uno tiene un millón de objetos, cada uno de los cuales tiene un campo del tipo Int32. Cada uno de los objetos que contienen el Int32 tiene un encabezado que identifica la clase que puede operarlo. Dado que una copia de ese código de clase puede operar en cualquiera de las millones de instancias, tener el hecho de que el campo es Int32 ser parte del código es mucho más eficiente que tener el almacenamiento para cada uno de esos campos, incluir información sobre lo que es .

El boxeo es necesario cuando se realiza una solicitud para pasar el contenido de una ubicación de almacenamiento de tipo de valor a un código que no sabe esperar ese tipo de valor particular. El código que espera objetos de tipo desconocido puede aceptar una referencia a un objeto almacenado en el montón. Como cada objeto almacenado en el montón tiene un encabezado que identifica qué tipo de objeto es, el código puede usar ese encabezado cada vez que sea necesario usar un objeto de una forma que requiera conocer su tipo.

Tenga en cuenta que en .net, es posible declarar lo que se denominan clases y métodos genéricos. Cada una de esas declaraciones genera automáticamente una familia de clases o métodos que son idénticos, excepto el tipo de objeto sobre el que esperan actuar. Si uno pasa un Int32 a una rutina DoSomething<T>(T param), eso generará automáticamente una versión de la rutina en la cual cada instancia del tipo T se reemplaza efectivamente por Int32. Esa versión de la rutina sabrá que cada ubicación de almacenamiento declarada como tipo T contiene un Int32, así como en el caso donde una rutina estaba codificada para usar una ubicación de almacenamiento Int32, no será necesario almacenar información de tipo con aquellos ubicaciones mismas.

Cuestiones relacionadas