tipos - ¿Pueden los archivos de clase Java usar palabras clave reservadas como nombres?
sintaxis de java (4)
Soy consciente de que Java-el-lenguaje-de-programación-compilable no es lo mismo que Java-the-bytecode-format-for-JVM-ejecución. Hay ejemplos de cosas que son válidas en el formato .class pero no en el código fuente .java, como las clases sin constructor y los métodos sintéticos.
-
Si creamos a mano un archivo .class con una palabra clave reservada del lenguaje Java (por ejemplo,
int
,while
) como clase, método o nombre de campo, ¿la máquina virtual Java lo aceptará para cargar? -
Si la clase está cargada, ¿implica que la única forma de acceder a esta clase o miembro es a través de la reflexión de Java, porque el nombre es sintácticamente ilegal en el lenguaje de programación Java?
- Las palabras clave son conocidas solo por el compilador. El compilador los traduce a un bytecode adecuado. Por lo tanto, no existen durante el tiempo de ejecución del código de bytes compilado y, en consecuencia, no son verificados por la JVM.
- Seguramente, no puede acceder a los miembros de la clase que no se conocen en el momento de la compilación. Pero puede usar la reflexión para ese propósito si está seguro de que dicho miembro de clase existirá en el código compilado (los "creará a mano" allí), porque el compilador no verifica el acceso por reflexión.
Las únicas restricciones sobre los nombres de clase en el nivel de código de bytes son que no pueden contener los caracteres
[
,
.
o
y que tienen como máximo 65535 bytes de longitud.
Entre otras cosas, esto significa que puede usar libremente palabras reservadas, espacios en blanco, caracteres especiales, Unicode o incluso cosas extrañas como líneas nuevas.
Teóricamente, incluso puede usar caracteres nulos en el nombre de una clase, pero como es imposible tener un carácter nulo en el nombre del archivo, no puede incluir un archivo de clase en un jar. Sin embargo, es posible que pueda crear y cargar uno dinámicamente.
Aquí hay un ejemplo de algunas de las cosas que puede hacer (escrito en el ensamblaje de Krakatau):
; Entry point for the jar
.class Main
.super java/lang/Object
.method public static main : ([Ljava/lang/String;)V
.limit stack 10
.limit locals 10
invokestatic int hello ()V
invokestatic "-42" hello ()V
invokestatic "" hello ()V
invokestatic " some whitespace and /t tabs" hello ()V
invokestatic "new/nline" hello ()V
invokestatic ''name with "Quotes" in it'' hello ()V
return
.end method
.end class
.class int
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from int"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class "-42"
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from -42"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
; Even the empty string can be a class name!
.class ""
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from "
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class " some whitespace and /t tabs"
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from some whitespace and /t tabs"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class "new/nline"
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from new/nline"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
.class ''name with "Quotes" in it''
.super java/lang/Object
.method public static hello : ()V
.limit stack 2
.limit locals 0
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello from name with /"Quotes/" in it"
invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
return
.end method
.end class
Salida de ejecución:
Hello from int
Hello from -42
Hello from
Hello from some whitespace and tabs
Hello from new
line
Hello from name with "Quotes" in it
Vea la respuesta de Holger para la cotización exacta de las reglas de la especificación JVM.
Las restricciones sobre los nombres se arreglan en la especificación JVM:
§4.2.1. Clase binaria y nombres de interfaz
Los nombres de clase e interfaz que aparecen en las estructuras de archivos de clase siempre se representan en una forma totalmente calificada conocida como nombres binarios (JLS §13.1). Dichos nombres siempre se representan como estructuras
CONSTANT_Utf8_info
(§4.4.7) y, por lo tanto, pueden extraerse, donde no estén más restringidos, de todo el espacio de código Unicode ...Por razones históricas, la sintaxis de los nombres binarios que aparecen en las estructuras de archivos de clase difiere de la sintaxis de los nombres binarios documentados en JLS §13.1. En esta forma interna, los períodos ASCII (
.
) Que normalmente separan los identificadores que componen el nombre binario son reemplazados por barras diagonales ASCII (/
). Los identificadores mismos deben ser nombres no calificados (§4.2.2).§4.2.2. Nombres no calificados
Los nombres de métodos, campos, variables locales y parámetros formales se almacenan como nombres no calificados. Un nombre no calificado debe contener al menos un punto de código Unicode y no debe contener ninguno de los caracteres ASCII
.
;
[
/
(es decir, punto o punto y coma o corchete izquierdo o barra inclinada).Los nombres de los métodos están más restringidos de modo que, con la excepción de los nombres de métodos especiales
<init>
y<clinit>
(§2.9), no deben contener los caracteres ASCII<
o>
(es decir, paréntesis angular izquierdo o paréntesis angular derecho) .
Entonces, la respuesta es que solo hay unos pocos caracteres que no puede usar en el nivel binario.
Primero,
/
es el separador de paquetes.
Entonces
y
[
no se puede usar porque tienen un significado especial en
firmas de campo
y
firmas de métodos
que pueden contener nombres de tipo.
En estas firmas,
[
inicia un tipo de matriz y
;
marca el final de un nombre de tipo de referencia.
No hay una razón clara por qué
.
está prohibido.
No se usa dentro de la JVM y solo tiene un significado dentro de
las firmas genéricas,
pero
si
está usando firmas genéricas, los nombres de los tipos se restringen aún más al no permitir que contengan
<
,
>
, así como estos caracteres tienen un significado especial dentro de firmas genéricas también.
En consecuencia, violar la especificación mediante el uso
.
dentro de los identificadores no tiene impacto en la función principal de la JVM.
Hay ofuscadores haciéndolo.
El código resultante funciona, pero puede encontrar problemas con Reflection al solicitar firmas de tipo genérico.
Además, la conversión de nombres binarios a nombre de origen reemplazando todos
/
s con
.
s será irreversible si el nombre binario contiene
.
s.
Puede ser interesante que haya una propuesta para admitir todos los identificadores posibles dentro de la sintaxis de Java (ver punto 3, "identificadores exóticos"), pero no llegó al Java 7 final. Y parece que nadie está actualmente haciendo un nuevo intento de traerlo.
Existe la limitación técnica adicional de que los nombres no pueden tener una representación UTF-8 modificada de más de 65535 bytes porque el número de bytes se almacena como un valor corto sin signo.
Sí, puedes usar palabras reservadas. Las palabras son solo para el compilador. No aparecen en el código de bytes generado.
Un ejemplo de uso de palabras Java reservadas se encuentra en el lenguaje Scala basado en JVM. Scala tiene diferentes construcciones y sintaxis que Java, pero compila en código de bytes Java, para ejecutarse en una JVM.
Esto es legal Scala:
class `class`
Esto define una clase llamada
class
con un constructor sin argumentos.
Ejecutar
javap
(un desensamblador) en el archivo compilado
class.class
muestra
public class class {
public class();
}
Scala puede hacer lo mismo con cualquier otra palabra reservada de Java.
class int
class `while`
class goto
También se pueden usar para nombres de métodos o campos.
Como sospechaba, no podría usar estas clases de Java, excepto para la reflexión. Puede utilizarlos desde un archivo de clase "personalizado" similar, por ejemplo, desde un archivo de clase generado por el compilador Scala.
En resumen, esta es una limitación de javac (el compilador), no de java (el entorno VM / runtime).