java - quién - ¿Es válido tener una clase de bytecode JVM sin ningún constructor?
tabla de codigo bytecode (2)
Ya has respondido la pregunta tú mismo: una clase sin un constructor es absolutamente válida según JVMS. No se puede escribir una clase de este tipo en Java puro, pero se puede construir utilizando la generación de código byte.
Piensa en las interfaces: también son clases sin un constructor desde el punto de vista de JVM. Y también pueden tener miembros estáticos (incluso puede invocar el método main
la interfaz desde la línea de comandos).
AFAIK, en Java, los constructores implícitos siempre se generan para una clase sin constructores [1] , [2] .
Pero en bytecode no pude encontrar tal restricción en el JVMS .
Asi que:
¿Es válido según el JVMS definir una clase sin constructor solo para usar sus métodos estáticos como en el siguiente jasmin hello world?
¿Tiene otras consecuencias además de no poder crear instancias de esto? No podré usar
invokespecial
para inicializar instancias, lo que hace que inútil seanew
según https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.4 (no puede usar un objeto no inicializado).
Código 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
es decir, sin un constructor:
.method public <init>()V
aload_0
invokenonvirtual java/lang/Object/<init>()V
return
.end method
?
Correr con java Main
da el resultado esperado Hello World!
.
javap -v
salida javap -v
y, a diferencia de Java, jasmin
no generó el constructor predeterminado.
También he intentado llamar a new Main();
de todos modos para ver qué pasa con:
public class TestMain {
public static void main(String[] args) {
Main m = new Main();
}
}
y como se espera, da un error de compilación cannot find symbol
. Si agrego el constructor al TestMain
entonces TestMain
funciona.
Salida de javap -v
para completar:
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"
Si alguien puede generar eso con javac (en particular, sin ACC_INTERFACE
ni ACC_SYNTHETIC
) ese sería un buen argumento para la validez.
Es legal El JVMS no dice lo contrario.
A veces, el compilador Java incluso crea dichas clases para crear constructores de acceso o para clases internas:
class Foo {
{ new Bar(); }
class Bar() {
private Bar() { }
}
}
Para hacer que este constructor privado sea accesible para el campo externo, el compilador de Java agrega un constructor privado de paquete a la clase interna que toma una instancia de la clase sin constructor creada aleatoriamente como su único argumento. Esta instancia siempre es nula y el descriptor de acceso solo invoca el constructor sin parámetros sin usar el argumento. Pero como los constrors no pueden ser nombrados, esta es la única forma de evitar colisiones con otros constructores. Para mantener el archivo de clase mínimo, no se agrega ningún constructor.
En una nota al margen: siempre es posible crear instancias de clases sin constructores. Esto puede lograrse, por ejemplo, absteniéndose de la deserialización. Si utiliza Jasmin para definir una clase sin un constructor que implemente la interfaz Serializable
, puede crear manualmente una secuencia de bytes que se asemeje a la clase si se serializó. Puedes deserializar esta clase y recibir una instancia de la misma.
En Java, una llamada de constructor y una asignación de objeto son dos pasos separados. Esto incluso está expuesto por el código de bytes de crear una instancia. Algo así como el new Object()
está representado por dos instrucciones
NEW java/lang/Object
INVOKESPECIAL java/lang/Object <init> ()V
el primero es la asignación, el segundo es la invocación del constructor. El verificador de la JVM siempre verifica que se llame a un constructor antes de usar la instancia, pero en teoría, la JVM es perfectamente capaz de separar ambas, como lo demuestra la deserialización (o las llamadas internas a la VM, si la serialización no es una opción).