2012-07-24 11 views
9

A mi entender, si se puede hacer una verificación de tipo durante la compilación, la conversión de tipo se realizará durante la compilación y no incurrirá en ninguna sobrecarga de tiempo de ejecución.Cualquier tipo de fundición hecha por javac?

Por ejemplo

public Child getChild() { 
    Parent o = new Child(); 
    return (Child) o; 
} 

¿Es el casting tipo realizado durante la compilación o en tiempo de ejecución?

¿Existe alguna regla general para decidir si un tipo de conversión es realizada por el compilador javac o por la máquina virtual?

+0

Buena pregunta ... pero, ¿es esto importante codificar? ¿O solo preguntas por tu propia curiosidad? – Tenner

+3

@Tenner ¿Importa de alguna manera? –

+0

En este ejemplo, espero que la conversión de tipos se realice durante el tiempo de compilación y no durante el tiempo de ejecución. Sin embargo, no debería necesitar enviar o a un elemento secundario para la declaración de devolución, ya que Java sabe que es de tipo Child en función del gráfico Object. – Jay

Respuesta

2

En realidad, hay tres posibilidades en este caso:

  1. El compilador javac se pudo realizar la optimización.
  2. El compilador JIT podría realizar la optimización.
  3. El código nativo del compilador JIT podría incluir código para hacer una verificación del tipo de tiempo de ejecución.

Espero que sea la opción 1. o 2. pero esto podría ser específico de la plataforma.


De hecho, en mi sistema, el bytecode no está optimizado. Si se produce alguna optimización, estará en el compilador JIT para hacerlo. (Esto concuerda con lo que he escuchado ... que la mayoría de los compiladores de código byte de Java hacen poco por la optimización antes de generar bytecodes.)

Compiled from "Test.java" 
public class Test extends java.lang.Object{ 
public Test(); 
    Code: 
    0: aload_0 
    1: invokespecial #8; //Method java/lang/Object."<init>":()V 
    4: return 

public Child getChild(); 
    Code: 
    0: new #16; //class Child 
    3: dup 
    4: invokespecial #18; //Method Child."<init>":()V 
    7: astore_1 
    8: aload_1 
    9: checkcast #16; //class Child 
    12: areturn 

} 
+0

¿Cómo conseguiste que Stephen – developer

+0

Usando 'javap -c' –

+0

@StephenC Veo los mismos resultados. Si configuro 'getChild()' como un método privado, parece que se produce la optimización. No tengo más bloque 'getChild()' en el registro javap -c (vea mi respuesta). Realmente no entiendo por qué hace la diferencia:/ –

1

Cuando un programa en ejecución intenta convertir una referencia de objeto a otro tipo, la máquina virtual debe verificar si el tipo al que se está convirtiendo es la clase real del objeto al que se hace referencia o uno de sus supertipos. Debe realizar el mismo tipo de comprobación cuando un programa realiza una instancia de operación.

En cualquier caso, la máquina virtual debe examinar los datos de la clase del objeto al que se hace referencia. Cuando un programa invoca un método de instancia, la máquina virtual debe realizar un enlace dinámico: debe elegir el método para invocar basándose no en el tipo de referencia sino en la clase del objeto. Para hacer esto, debe tener acceso una vez más a los datos de la clase, dado solo una referencia al objeto.

Editar:

compilador Java no es responsable de comprobar si el casting es correcta o no, al igual que algunos de los enlaces solamente ocurren en tiempo de ejecución. La máquina virtual Java realiza la comprobación en tiempo de ejecución para averiguar si el objeto de referencia real es un objeto legítimo del nuevo tipo. De lo contrario, habrá una excepción de tiempo de ejecución: ClassCastException.

+1

'javac' podría haber compilado ese código sin downcasts, por lo que el tiempo de ejecución nunca sabría que había un tipo' Parent' involucrado. –

0

Supongo que se hará en ambas etapas. En tiempo de compilación, el compilador lo obligará a hacer los moldes adecuados para asegurarse de que no mezcló los tipos, como en cualquier strongly typed language.

Si embargo lances un Object que tienes, por ejemplo, como un parámetro en String (que va a trabajar para objetos que son en realidad instanceofString) JVM, todavía tiene que asegurarse de que la clase que implementa de Object verdaderamente se extiende o se String y obtendrá un ClassCastException si no lo es.

0

Para las conversiones que no requieren una prueba en tiempo de ejecución, puede ser posible que el compilador de hacer algunas optimizaciones para evitar la proyección en tiempo de ejecución.

Sugiero leer el JLS Chapter 5. Conversions and Promotions para saber más sobre el tipo de conversiones que necesitan una prueba en tiempo de ejecución.

Ejemplo 5.0-1. Las conversiones en tiempo de compilación y en tiempo de ejecución

A conversion from type Object to type Thread requires a run-time check to make sure that the run-time value is actually an instance of class Thread or one of its subclasses; if it is not, an exception is thrown. 

A conversion from type Thread to type Object requires no run-time action; Thread is a subclass of Object, so any reference produced by an expression of type Thread is a valid reference value of type Object. 

A conversion from type int to type long requires run-time sign-extension of a 32-bit integer value to the 64-bit long representation. No information is lost. 

A conversion from type double to type long requires a nontrivial translation from a 64-bit floating-point value to the 64-bit integer representation. Depending on the actual run-time value, information may be lost. 

5.1.6. Narrowing Reference Conversion:

Tales conversiones requieren una prueba en tiempo de ejecución para averiguar si el valor de referencia real es un valor legítimo de la nueva tipo. Si no, se genera una ClassCastException.

5.1.8. Unboxing Conversion; La conversión procede en tiempo de ejecución.

Véase también: 5.5.3. Checked Casts at Run-time

No es tan fácil de determinar cuando la conversión ocurrió por ejemplo:

public class Main { 

    private static class Child extends Parent{ 

     public Child() { 
     } 
    } 

    private static class Parent { 

     public Parent() { 
     } 
    } 

    private static Child getChild() { 
     Parent o = new Child(); 
     return (Child) o; 
    } 


    public static void main(final String[] args) { 
     Child c = getChild(); 
    } 
} 

El resultado dado por javap -c Main es:

public class Main extends java.lang.Object{ 
public Main(); 
Code: 
0: aload_0 
1: invokespecial #1; //Method java/lang/Object."<init>":()V 
4: return 

public static void main(java.lang.String[]); 
Code: 
0: invokestatic #4; //Method getChild:()LMain$Child; 
3: astore_1 
4: return 

} 

Si cambia el declaración de método a public static Child getChild() el resultado es:

public class Main extends java.lang.Object{ 
public Main(); 
Code: 
0: aload_0 
1: invokespecial #1; //Method java/lang/Object."<init>":()V 
4: return 

public static Main$Child getChild(); 
Code: 
0: new #2; //class Main$Child 
3: dup 
4: invokespecial #3; //Method Main$Child."<init>":()V 
7: astore_0 
8: aload_0 
9: checkcast #2; //class Main$Child 
12: areturn 

public static void main(java.lang.String[]); 
Code: 
0: invokestatic #4; //Method getChild:()LMain$Child; 
3: astore_1 
4: return 

} 

Verá que el simple hecho de cambiar el acceso, puede tener un gran impacto en las posibles optimizaciones.

+0

Lo revisé. El 'private static getChild()' todavía existe en la clase. Solo por alguna razón (o error) que javap no lo genera. Y el bytecode tampoco es diferente. – qinsoon

0

Cuando Compilé

public class Test { 
    public Child getChildVersion1() { 
     Parent o = new Child(); 
     return (Child) o; 
    } 
    public Child getChildVersion2() { 
     return new Child(); 
    } 
} 

y descompilar el código usando javap -c Test en Java 7 (Windows 7 64 bits) que me dio este resultado

Compiled from "Test.java" 
public class Test { 
    public Test(); 
    Code: 
     0: aload_0 
     1: invokespecial #1     // Method java/lang/Object."<init>":()V 
     4: return 

    public Child getChildVersion1(); 
    Code: 
     0: new   #2     // class Child 
     3: dup 
     4: invokespecial #3     // Method Child."<init>":()V 
     7: astore_1 
     8: aload_1 
     9: checkcast  #2     // class Child 
     12: areturn 

    public Child getChildVersion2(); 
    Code: 
     0: new   #2     // class Child 
     3: dup 
     4: invokespecial #3     // Method Child."<init>":()V 
     7: areturn 
} 

Por lo tanto, costuras que el compilador no optimizar el método getChildVersion1 para ser como getChildVersion2 así que al lado del tipo de comprobación mientras compilación tiempo también está comprobando en tiempo de ejecución (9: checkcast #2). Pero como Stephen C mentioned puede estar relacionado con la plataforma (sistema operativo, versión Java).

Cuestiones relacionadas