2015-04-06 8 views
13

AFAIK, w niejawnych konstruktorach języka Java generowane są zawsze dla klasy bez konstruktorów [1], [2].Czy prawidłowe jest posiadanie klasy kodu bajtowego JVM bez żadnego konstruktora?

Ale w bajtodzie nie mogłem znaleźć takiego ograniczenia na JVMS.

Więc:

  • jest ważne zgodnie z JVMs zdefiniowanie klasy bez konstruktora tylko wykorzystać swoje metody statyczne, jak w poniższym Jasmin cześć świata?

  • ma to dalsze konsekwencje, oprócz tego, że nie można go utworzyć? Nie będę mógł używać invokespecial do inicjowania wystąpień, co czyni bezużytecznym new zgodnie z https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.4 (nie można użyć niezainicjowanego obiektu).

kod Jasmin:

.class public Main 
.super java/lang/Object 
.method public static main([Ljava/lang/String;)V 
    .limit stack 2 
    getstatic java/lang/System/out Ljava/io/PrintStream; 
    ldc "Hello World!" 
    invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 
    return 
.end method 

czyli bez konstruktora:

.method public <init>()V 
    aload_0 
    invokenonvirtual java/lang/Object/<init>()V 
    return 
.end method 

?

Praca z java Main daje oczekiwane wyjście Hello World!.

Sprawdziłem dane wyjściowe javap -v iw przeciwieństwie do Java, jasmin nie wygenerowałem domyślnego konstruktora.

Próbowałem również zadzwonić new Main(); w każdym razie, aby zobaczyć, co dzieje się z:

public class TestMain { 
    public static void main(String[] args) { 
     Main m = new Main(); 
    } 
} 

i zgodnie z oczekiwaniami daje błąd kompilacji cannot find symbol. Jeśli dodaję konstruktora do jasminu, działa TestMain.

Wyjście javap -v dla kompletności:

public class Main 
    minor version: 0 
    major version: 46 
    flags: ACC_PUBLIC, ACC_SUPER 
Constant pool: 
    #1 = Utf8    Main.j 
    #2 = Class    #17   // Main 
    #3 = NameAndType  #21:#23  // out:Ljava/io/PrintStream; 
    #4 = Utf8    ([Ljava/lang/String;)V 
    #5 = Utf8    java/lang/Object 
    #6 = Class    #5    // java/lang/Object 
    #7 = Utf8    Hello World! 
    #8 = Class    #16   // java/io/PrintStream 
    #9 = String    #7    // Hello World! 
    #10 = Class    #19   // java/lang/System 
    #11 = Utf8    Code 
    #12 = Utf8    main 
    #13 = Fieldref   #10.#3   // java/lang/System.out:Ljava/io/PrintStream; 
    #14 = Utf8    SourceFile 
    #15 = NameAndType  #18:#22  // println:(Ljava/lang/String;)V 
    #16 = Utf8    java/io/PrintStream 
    #17 = Utf8    Main 
    #18 = Utf8    println 
    #19 = Utf8    java/lang/System 
    #20 = Methodref   #8.#15   // java/io/PrintStream.println:(Ljava/lang/String;)V 
    #21 = Utf8    out 
    #22 = Utf8    (Ljava/lang/String;)V 
    #23 = Utf8    Ljava/io/PrintStream; 
{ 
    public static void main(java.lang.String[]); 
    descriptor: ([Ljava/lang/String;)V 
    flags: ACC_PUBLIC, ACC_STATIC 
    Code: 
     stack=2, locals=1, args_size=1 
     0: getstatic  #13     // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: ldc   #9     // String Hello World! 
     5: invokevirtual #20     // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
     8: return 
} 
SourceFile: "Main.j" 

Jeśli ktoś może generować że z javac (w szczególności nie ACC_INTERFACE ani ACC_SYNTHETIC), że byłby to dobry argument za ważności.

+1

Czy próbowałeś napisać obiekt Java, który wywołuje 'new Main();'? – RealSkeptic

+0

@RealSkeptic po prostu zrobił i dostał 'error: can not find symbol' zgodnie z oczekiwaniami. Następnie, jeśli dodaję konstruktor do Jasmin, działa 'new Main()'. –

+0

Cóż, więc. Twoje pytanie jest mniej lub bardziej filozoficzne. Czy to jest poprawna Java? Nie. Czy to prawda Jasmin? Tak. Czy można go użyć z programu Java? Tak. Czy działa tak, jak ludzie oczekują, że klasa Java będzie działać? Nie do końca. Czym jest "Ważny"? – RealSkeptic

Odpowiedz

8

Jest legalny. JVMS nie mówi inaczej.

Zdarza się, że kompilator Javy ma nawet utworzyć takich klas w celu stworzenia dostępowe konstruktorów dla klas wewnętrznych:

class Foo { 
    { new Bar(); } 
    class Bar() { 
    private Bar() { } 
    } 
} 

Aby to prywatny konstruktor dostępny do zewnętrznej clasd, kompilator Javy dodaje pakiet-prywatny konstruktor klasy wewnętrznej, która pobiera instancję losowo utworzonej klasy bez konstruktora jako swojego jedynego argumentu. Ta instancja jest zawsze pusta, a accessor wywołuje konstruktor bez parametrów bez użycia argumentu. Ale ponieważ constrors nie można nazwać, jest to jedyny sposób uniknięcia kolizji z innymi konstruktorami. Aby zachować minimalny plik klasy, nie dodaje się żadnego konstruktora.

Na marginesie: Zawsze można utworzyć wystąpienia klas bez konstruktorów. Można to osiągnąć przez, na przykład, absrystykę deserializacji. Jeśli użyjesz Jasmin do zdefiniowania klasy bez konstruktora implementującego interfejs Serializable, możesz ręcznie utworzyć strumień bajtów podobny do klasy, jeśli był serializowany. Możesz deserializować tę klasę i odbierać jej instancję.

W języku Java wywołanie konstruktora alokacji obiektu składa się z dwóch oddzielnych kroków. Jest to nawet uwidocznione przez kod bajtowy tworzenia instancji. Coś jak new Object() jest reprezentowana przez dwóch instuctions

NEW java/lang/Object 
INVOKESPECIAL java/lang/Object <init>()V 

pierwszy jest alokacja, drugie wywołanie konstruktora. Weryfikator JVM zawsze sprawdza, czy konstruktor jest wywoływany przed użyciem instancji, ale teoretycznie maszyna JVM jest doskonale zdolna do odłączenia obu, jak udowodniono przez deserializację (lub wewnętrzne wywołania do maszyny wirtualnej, jeśli serializacja nie jest opcją).

+0

W przypadku innych nowinek takich jak ja generuje to trzy klasy według http://stackoverflow.com/a/2654699/895245. Trzecia klasa, zwana 'Foo $ 1.class', nie ma konstruktora i nie jest interfejsem. Ale ma [ACC_SYNTHETIC] (https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1) i [EnclosingMethod] (https: // docs. oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.7) zestaw atrybutów w przeciwieństwie do minimalnego wyjścia Jasmin, co czyni ten argument tak dobrym, jak apangin, który ustawia "ACC_INTERFACE". –

+0

Twoje pytanie dotyczyło zajęć. Interfejsy i typy adnotacji nie mają konstruktorów przez jls. Możliwe jest jednak zdefiniowanie konstruktorów nawet w interfejsach przy użyciu narzędzia do generowania kodu, takiego jak ASM, ponieważ są to tylko metody o nazwie na poziomie kodu bajtowego. Powyższy przykład generuje trzy klasy: Foo i Foo $ Bar to dwie oddzielne klasy. –

+0

Co mam na myśli, argument "javac generuje to" jest ważny tylko wtedy, gdy można wygenerować ten sam kod bajtowy co fragment Jasmin. Interfejsy mają ACC_INTERFACE, a to ma ACC_SYNTHETIC, a kod jaśminowy nie. Ale prawda, można to uznać za bliżej, ponieważ generuje klasy. –

6

Sam już odpowiedziałeś na to pytanie: klasa bez konstruktora jest absolutnie ważna zgodnie z JVMS. Nie możesz napisać takiej klasy w czystej Jawie, ale można ją skonstruować za pomocą generowania kodu bajtowego.

Pomyśl o interfejsach: są to również klasy bez konstruktora z punktu widzenia JVM. Mogą też mieć statycznych członków (można nawet wywołać metodę interfejsu main z wiersza poleceń).

+0

Interfejsy są uzasadnionym argumentem, chociaż mają na nich [ACC_INTERFACE] (https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1), które mogą uczynić je ważnymi tylko w takim przypadku. –

Powiązane problemy