2012-07-24 11 views
9

Wenn die Typprüfung während der Kompilierung durchgeführt werden kann, wird die Typumwandlung nach meinem Verständnis während der Kompilierung durchgeführt und verursacht keinen Laufzeitaufwand.Jede Art Casting von javac getan?

Zum Beispiel

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

Ist der Typ Gießen während der Kompilierung oder während der Laufzeit getan?

Und gibt es eine allgemeine Regel zu entscheiden, ob ein Typ Casting von Javac Compiler oder von der VM getan wird?

+0

Gute Frage ... aber ist es wichtig zu kodieren? Oder fragen Sie nur nach Ihrer eigenen Neugier? – Tenner

+3

@Tenner spielt es eine Rolle? –

+0

In diesem Beispiel erwarte ich, dass das Typcasting während der Kompilierungszeit ausgeführt wird und nicht während der Laufzeit. Sie sollten o jedoch nicht für die return-Anweisung in ein Child-Objekt umwandeln, da Java aufgrund des Objektdiagramms weiß, dass es vom Typ Kind ist. – Jay

Antwort

2

Eigentlich gibt es drei Möglichkeiten in diesem Fall:

  1. Der javac Compiler die Optimierung durchführen kann.
  2. Der JIT-Compiler konnte die Optimierung durchführen.
  3. Der systemeigene Code vom JIT-Compiler kann Code enthalten, um eine Laufzeittypüberprüfung durchzuführen.

Ich erwarte, dass es Option 1. oder 2. ist, aber das könnte plattformspezifisch sein.


In der Tat ist auf meinem System der Bytecode nicht optimiert. Wenn eine Optimierung stattfinden soll, wird es der JIT-Compiler sein, um es zu tun. (Dies passt zu dem, was ich gehört habe ... dass die meisten Java-Bytecode-Compiler vor der Erzeugung von Bytecodes wenig Optimierung durchführen.

)
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

Wie hast du das Stephen – developer

+0

Mit 'javap -c' –

+0

@ StephenC Ich sehe die gleichen Ergebnisse. Wenn ich 'getChild() 'als private Methode einstelle, scheint die Optimierung zu erfolgen. Ich habe keinen 'getChild()' Block mehr im javap -c Log (siehe meine Antwort). Ich verstehe nicht wirklich, warum es einen Unterschied macht:/ –

1

Wenn ein laufendes Programm versucht, eine Objektreferenz auf einen anderen Typ zu übertragen, muss die virtuelle Maschine prüfen, ob der Typ, in den umgewandelt wird, die tatsächliche Klasse des referenzierten Objekts oder eines seiner Obertypen ist. Es muss die gleiche Art von Prüfung durchführen, wenn ein Programm eine Operation ausführt.

In beiden Fällen muss die virtuelle Maschine in die Klassendaten des referenzierten Objekts schauen. Wenn ein Programm eine Instanzmethode aufruft, muss die virtuelle Maschine eine dynamische Bindung ausführen: Sie muss die aufzurufende Methode nicht auf der Basis des Referenztyps, sondern auf der Klasse des Objekts auswählen. Zu diesem Zweck muss es erneut Zugriff auf die Klassendaten haben, die nur einen Verweis auf das Objekt enthalten.

Edit:

Java-Compiler ist nicht verantwortlich für die Überprüfung, ob das Casting richtig ist oder nicht, genau wie einige der Bindungen nur zur Laufzeit auftreten. Die Java Virtual Machine prüft zur Laufzeit, ob das eigentliche Referenzobjekt ein legitimes Objekt des neuen Typs ist. Wenn nicht, wird eine Laufzeitausnahme auftreten: ClassCastException.

+1

'javac' könnte diesen Code ohne Downcasts kompiliert haben, so dass die Laufzeitumgebung niemals wissen würde, dass ein' Parent'-Typ involviert ist. –

0

Ich denke, es ist in beiden Phasen getan. Zur Kompilierungszeit wird der Compiler Sie zwingen, die richtigen Umwandlungen durchzuführen, um sicherzustellen, dass Sie die Typen nicht verwechselt haben, wie in jedem strongly typed language.

Wenn Sie jedoch einen Object werfen Sie haben, sagen wir, als Parameter in String (die für Objekte funktionieren, die tatsächlich instanceofString) die JVM noch dafür sorgen, dass die implementierende Klasse von Object erstreckt wirklich oder ist String und Sie erhalten eine ClassCastException, wenn es nicht ist.

0

Für die Umwandlungen, die keinen Test zur Laufzeit benötigen, kann es möglich sein, dass der Compiler einige Optimierungen machen zur Laufzeit zu vermeiden Gießen.

Ich empfehle, die JLS Chapter 5. Conversions and Promotions zu lesen, um mehr über die Art der Konvertierungen zu erfahren, die einen Test zur Laufzeit benötigen.

Beispiel 5.0-1. Conversions zur Compile-Zeit und Laufzeit

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:

Solche Umwandlungen erfordern einen Test zur Laufzeit, um herauszufinden, ob der Ist-Referenzwert ist ein legitimer Wert der neuen Art. Wenn nicht, wird eine ClassCastException ausgelöst.

5.1.8. Unboxing Conversion; Die Konvertierung wird zur Laufzeit fortgesetzt.

Siehe auch: 5.5.3. Checked Casts at Run-time

Es ist nicht so einfach zu bestimmen, wann die Umwandlung zum Beispiel geschehen:

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(); 
    } 
} 

Das Ergebnis gegeben durch javap -c Main ist:

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 

} 

Wenn Sie das ändern Methodendeklaration zu public static Child getChild() das Ergebnis ist:

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 

} 

Sie sehen, dass nur die Änderung des Accessors, kann viel auf die möglichen Optimierungen auswirken.

+0

Ich überprüfte. Die 'private static getChild()' existiert noch in der Klasse. Aus irgendeinem Grund (oder Bug), dass javap es nicht ausgibt. Und der Bytecode ist auch kein Unterschied. – qinsoon

0

Als ich

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

und dekompilierten, dass Code javap -c Test auf Java 7 (Windows 7 64-Bit) verwenden es gab mir dieses Ergebnis

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 
} 

So ist es, dass die Compiler Nähte nicht Methode optimieren getChildVersion1 zu sein wie getChildVersion2 so neben der Art der Überprüfung während der Kompilierung Zeit gibt es auch zur Laufzeit überprüft (9: checkcast #2). Aber als Stephen C mentioned kann es mit der Plattform (OS, Java-Version) verwandt sein.