Допустимо ли иметь класс байт-кода JVM без какого-либо конструктора?
AFAIK, в Java неявные конструкторы всегда генерируются для класса без конструкторов [1], [2].
Но в байт-коде я не смог найти такое ограничение на JVMS.
Так:
допустимо ли в соответствии с JVMS определять класс без конструктора только для использования его статических методов, как в следующем мире jasmin hello?
Есть ли у него какие-либо дальнейшие последствия, кроме невозможности создать его экземпляры? Я не смогу использовать
invokespecial
инициализировать экземпляры, которые рендеритnew
бесполезно в соответствии с https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html (не может использовать неинициализированный объект).
Код Жасмин:
.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
то есть без конструктора:
.method public <init>()V
aload_0
invokenonvirtual java/lang/Object/<init>()V
return
.end method
?
Бег с java Main
дает ожидаемый результат Hello World!
,
Я проверил javap -v
вывод и в отличие от Java, jasmin
не сгенерировал конструктор по умолчанию.
Я также пытался позвонить new Main();
во всяком случае, чтобы увидеть, что происходит с:
public class TestMain {
public static void main(String[] args) {
Main m = new Main();
}
}
и, как и ожидалось, это дает ошибку компиляции cannot find symbol
, Если я добавлю конструктор в Жасмин, то TestMain
работает.
Выход из javap -v
для полноты:
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"
Если кто-то может сгенерировать это с помощью Javac (в частности, нет ACC_INTERFACE
ни ACC_SYNTHETIC
) это было бы хорошим аргументом для обоснованности.
2 ответа
Это законно. JVMS не говорит иначе.
Иногда компилятор Java даже создает такие классы для создания конструкторов доступа для внутренних классов:
class Foo {
{ new Bar(); }
class Bar() {
private Bar() { }
}
}
Чтобы сделать этот закрытый конструктор доступным для внешнего clasd, компилятор Java добавляет частный конструктор пакета во внутренний класс, который в качестве единственного аргумента принимает экземпляр случайно созданного класса без конструктора. Этот экземпляр всегда нулевой, и метод доступа вызывает только конструктор без параметров без использования аргумента. Но поскольку нельзя назвать имена constrors, это единственный способ избежать столкновений с другими конструкторами. Чтобы сохранить файл класса минимальным, конструктор не добавляется.
Примечание: всегда можно создавать экземпляры классов без конструкторов. Это может быть достигнуто, например, путем отказа от десериализации. Если вы используете Jasmin для определения класса без конструктора, который реализует Serializable
Интерфейс, вы можете создать поток байтов вручную, который похож на класс, если он был сериализован. Вы можете затем десериализовать этот класс и получить его экземпляр.
В Java вызов конструктора для выделения объекта - это два отдельных шага. Это даже подтверждается байтовым кодом создания экземпляра. Что-то вроде new Object()
представлен двумя инструкциями
NEW java/lang/Object
INVOKESPECIAL java/lang/Object <init> ()V
первый - это выделение, второй - вызов конструктора. Верификатор JVM всегда проверяет, что конструктор вызывается перед использованием экземпляра, но теоретически JVM вполне способна отключить оба, как доказано десериализацией (или внутренними вызовами в ВМ, если сериализация не является опцией).
Вы уже ответили на вопрос сами: класс без конструктора является абсолютно допустимым в соответствии с JVMS. Вы не можете написать такой класс на чистом Java, но он может быть создан с использованием генерации байт-кода.
Подумайте об интерфейсах: они также являются классами без конструктора с точки зрения JVM. И они также могут иметь статические члены (вы можете даже вызывать интерфейсы main
метод из командной строки).