Las características de código de bytes no están disponibles en el lenguaje Java


146

¿Existen actualmente (Java 6) cosas que puede hacer en el código de bytes de Java que no puede hacer desde el lenguaje Java?

Sé que ambos están completos en Turing, así que lea "puede hacer" como "puede hacerlo significativamente más rápido / mejor, o simplemente de una manera diferente".

Estoy pensando en códigos de bytes adicionales como invokedynamic, que no se pueden generar usando Java, excepto que uno específico es para una versión futura.


3
Definir "cosas". Al final, el lenguaje Java y el bytecode de Java están completos en Turing ...
Michael Borgwardt

2
Es la verdadera pregunta; ¿Hay alguna ventaja en la programación en código de bytes, por ejemplo, usando Jasmin, en lugar de Java?
Peter Lawrey

2
Como rolen el ensamblador, que no puedes escribir en C ++.
Martijn Courteaux

1
Es un compilador de optimización muy pobre que no puede compilar (x<<n)|(x>>(32-n))a una rolinstrucción.
Random832

Respuestas:


62

Hasta donde sé, no hay características principales en los códigos de bytes compatibles con Java 6 a las que no se pueda acceder también desde el código fuente de Java. La razón principal de esto es obviamente que el bytecode de Java fue diseñado con el lenguaje Java en mente.

Sin embargo, hay algunas características que no son producidas por los compiladores modernos de Java:

  • La ACC_SUPERbandera :

    Este es un indicador que se puede establecer en una clase y especifica cómo invokespecialse maneja un caso de esquina específico del código de bytes para esta clase. Está configurado por todos los compiladores de Java modernos (donde "moderno" es> = Java 1.1, si no recuerdo mal) y solo los compiladores de Java antiguos producían archivos de clase donde esto no estaba configurado. Este indicador existe solo por razones de compatibilidad con versiones anteriores. Tenga en cuenta que a partir de Java 7u51, ACC_SUPER se ignora por completo por razones de seguridad.

  • Los jsr/ retbytecodes.

    Estos bytecodes se usaron para implementar subrutinas (principalmente para implementar finallybloques). Ya no se producen desde Java 6 . La razón de su desaprobación es que complican mucho la verificación estática sin una gran ganancia (es decir, el código que usa casi siempre se puede volver a implementar con saltos normales con muy poca sobrecarga).

  • Tener dos métodos en una clase que solo difieren en el tipo de retorno.

    La especificación del lenguaje Java no permite dos métodos en la misma clase cuando difieren solo en su tipo de retorno (es decir, el mismo nombre, la misma lista de argumentos, ...). Sin embargo, la especificación JVM no tiene esa restricción, por lo que un archivo de clase puede contener dos de estos métodos, simplemente no hay forma de producir dicho archivo de clase utilizando el compilador Java normal. Hay un buen ejemplo / explicación en esta respuesta .


55
Podría agregar otra respuesta, pero también podríamos hacer de la suya la respuesta canónica. Es posible que desee mencionar que la firma de un método en bytecode incluye el tipo de retorno . Es decir, puede tener dos métodos con exactamente los mismos tipos de parámetros, pero diferentes tipos de retorno. Vea esta discusión: stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter

8
Puede tener nombres de clase, método y campo con casi cualquier carácter. Trabajé en un proyecto donde los "campos" tenían espacios y guiones en sus nombres. : P
Peter Lawrey

3
@ Peter: Hablando de los caracteres del sistema de archivos, me encontré con un ofuscador que había cambiado el nombre de una clase a aotra Adentro del archivo JAR. Me tomó alrededor de media hora descomprimir en una máquina Windows antes de darme cuenta de dónde estaban las clases faltantes. :)
Adam Paynter

3
@JoachimSauer: parafraseó JVM especificaciones, página 75: nombres de las clases, métodos, campos y variables locales puede contener cualquier carácter excepto '.', ';', '[', o '/'. Los nombres de los métodos son los mismos, pero tampoco pueden contener '<'o '>'. (Con las notables excepciones de <init>y, <clinit>por ejemplo, y los constructores estáticos). Debo señalar que si sigue estrictamente la especificación, los nombres de clase en realidad están mucho más restringidos, pero las restricciones no se aplican.
leviathanbadger

3
@JoachimSauer: también, una adición indocumentada mía: el lenguaje java incluye "throws ex1, ex2, ..., exn"como parte de las firmas de métodos; No puede agregar cláusulas de excepción a los métodos anulados. PERO, a la JVM no podría importarle menos. Por lo tanto, solo los finalmétodos están realmente garantizados por la JVM para estar libres de excepciones, aparte de RuntimeExceptionsy Errors, por supuesto. Demasiado para el manejo de excepciones comprobadas: D
leviathanbadger

401

Después de trabajar con código de bytes de Java durante bastante tiempo e investigar un poco más sobre este asunto, aquí hay un resumen de mis hallazgos:

Ejecute código en un constructor antes de llamar a un súper constructor o constructor auxiliar

En el lenguaje de programación Java (JPL), la primera declaración de un constructor debe ser una invocación de un superconstructor u otro constructor de la misma clase. Esto no es cierto para el código de bytes de Java (JBC). Dentro del código de bytes, es absolutamente legítimo ejecutar cualquier código antes de un constructor, siempre que:

  • Se llama a otro constructor compatible en algún momento después de este bloque de código.
  • Esta llamada no está dentro de una declaración condicional.
  • Antes de esta llamada al constructor, no se lee ningún campo de la instancia construida y no se invoca ninguno de sus métodos. Esto implica el siguiente artículo.

Establecer campos de instancia antes de llamar a un súper constructor o constructor auxiliar

Como se mencionó anteriormente, es perfectamente legal establecer un valor de campo de una instancia antes de llamar a otro constructor. Incluso existe un truco heredado que le permite explotar esta "característica" en las versiones de Java anteriores a la 6:

class Foo {
  public String s;
  public Foo() {
    System.out.println(s);
  }
}

class Bar extends Foo {
  public Bar() {
    this(s = "Hello World!");
  }
  private Bar(String helper) {
    super();
  }
}

De esta forma, se podría establecer un campo antes de que se invoque el súper constructor, lo que sin embargo ya no es posible. En JBC, este comportamiento aún se puede implementar.

Ramifica una llamada de súper constructor

En Java, no es posible definir una llamada de constructor como

class Foo {
  Foo() { }
  Foo(Void v) { }
}

class Bar() {
  if(System.currentTimeMillis() % 2 == 0) {
    super();
  } else {
    super(null);
  }
}

Sin embargo, hasta Java 7u23, el verificador de HotSpot VM no realizó esta comprobación, por lo que fue posible. Esto fue utilizado por varias herramientas de generación de código como una especie de pirateo, pero ya no es legal implementar una clase como esta.

Este último fue simplemente un error en esta versión del compilador. En las versiones más recientes del compilador, esto es nuevamente posible.

Definir una clase sin ningún constructor.

El compilador de Java siempre implementará al menos un constructor para cualquier clase. En el código de bytes de Java, esto no es obligatorio. Esto permite la creación de clases que no se pueden construir incluso cuando se usa la reflexión. Sin embargo, el uso sun.misc.Unsafeaún permite la creación de tales instancias.

Definir métodos con firma idéntica pero con diferente tipo de retorno

En el JPL, un método se identifica como único por su nombre y sus tipos de parámetros sin formato. En JBC, el tipo de retorno sin procesar también se considera.

Defina campos que no difieran por nombre sino solo por tipo

Un archivo de clase puede contener varios campos del mismo nombre siempre que declaren un tipo de campo diferente. La JVM siempre se refiere a un campo como una tupla de nombre y tipo.

Lanza excepciones marcadas no declaradas sin atraparlas

El tiempo de ejecución de Java y el código de bytes de Java no conocen el concepto de excepciones comprobadas. Es solo el compilador de Java que verifica que las excepciones marcadas siempre se detectan o declaran si se lanzan.

Use la invocación de métodos dinámicos fuera de las expresiones lambda

La llamada invocación del método dinámico se puede usar para cualquier cosa, no solo para las expresiones lambda de Java. El uso de esta característica permite, por ejemplo, cambiar la lógica de ejecución en tiempo de ejecución. Muchos lenguajes de programación dinámicos que se reducen a JBC mejoraron su rendimiento al usar esta instrucción. En el código de bytes de Java, también podría emular expresiones lambda en Java 7 donde el compilador aún no permitía el uso de la invocación de métodos dinámicos mientras la JVM ya entendía la instrucción.

Utilice identificadores que normalmente no se consideran legales

¿Alguna vez te ha gustado usar espacios y un salto de línea en el nombre de tu método? Cree su propio JBC y buena suerte para la revisión del código. Los únicos caracteres no válidos para identificadores son ., ;, [y /. Además, los métodos que no tienen nombre <init>o <clinit>que no pueden contener <y >.

Reasignar finalparámetros o la thisreferencia

finallos parámetros no existen en JBC y, en consecuencia, pueden reasignarse. Cualquier parámetro, incluida la thisreferencia, solo se almacena en una matriz simple dentro de la JVM, lo que permite reasignar la thisreferencia en el índice 0dentro de un marco de método único.

Reasignar finalcampos

Siempre que se asigne un campo final dentro de un constructor, es legal reasignar este valor o incluso no asignar un valor en absoluto. Por lo tanto, los siguientes dos constructores son legales:

class Foo {
  final int bar;
  Foo() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

Para los static finalcampos, incluso está permitido reasignar los campos fuera del inicializador de clase.

Trate a los constructores y al inicializador de clase como si fueran métodos

Esto es más una característica conceptual, pero los constructores no reciben un trato diferente dentro de JBC que los métodos normales. Es solo el verificador de JVM lo que asegura que los constructores llamen a otro constructor legal. Aparte de eso, es simplemente una convención de nomenclatura de Java que se debe llamar a los constructores <init>y que se llama al inicializador de clase <clinit>. Además de esta diferencia, la representación de métodos y constructores es idéntica. Como Holger señaló en un comentario, incluso puede definir constructores con tipos de retorno distintos voido un inicializador de clase con argumentos, aunque no es posible llamar a estos métodos.

Crear registros asimétricos * .

Al crear un registro

record Foo(Object bar) { }

javac generará un archivo de clase con un solo campo llamado bar, un método de acceso llamado bar()y un constructor tomando un solo Object. Además, barse agrega un atributo de registro para . Al generar manualmente un registro, es posible crear una forma de constructor diferente, omitir el campo e implementar el descriptor de acceso de manera diferente. Al mismo tiempo, todavía es posible hacer que la API de reflexión crea que la clase representa un registro real.

Llame a cualquier súper método (hasta Java 1.1)

Sin embargo, esto solo es posible para las versiones Java 1 y 1.1. En JBC, los métodos siempre se envían en un tipo de destino explícito. Esto significa que para

class Foo {
  void baz() { System.out.println("Foo"); }
}

class Bar extends Foo {
  @Override
  void baz() { System.out.println("Bar"); }
}

class Qux extends Bar {
  @Override
  void baz() { System.out.println("Qux"); }
}

fue posible implementar Qux#bazpara invocar Foo#bazmientras saltaba Bar#baz. Si bien aún es posible definir una invocación explícita para llamar a otra implementación de súper método que la de la súper clase directa, esto ya no tiene ningún efecto en las versiones de Java posteriores a 1.1. En Java 1.1, este comportamiento se controlaba configurando el ACC_SUPERindicador que permitiría el mismo comportamiento que solo llama a la implementación directa de la superclase.

Definir una llamada no virtual de un método que se declara en la misma clase

En Java, no es posible definir una clase

class Foo {
  void foo() {
    bar();
  }
  void bar() { }
}

class Bar extends Foo {
  @Override void bar() {
    throw new RuntimeException();
  }
}

El código anterior siempre dará lugar a una RuntimeExceptioncuando foose invoca en una instancia de Bar. No es posible definir el Foo::foométodo para invocar su propio bar método que se define en Foo. Como bares un método de instancia no privado, la llamada siempre es virtual. Sin embargo, con el código de bytes, se puede definir la invocación para usar el INVOKESPECIALcódigo de operación que vincula directamente la barllamada al método Foo::fooa Foola versión. Este código de operación se usa normalmente para implementar invocaciones de súper métodos, pero puede reutilizar el código de operación para implementar el comportamiento descrito.

Anotaciones de grano fino

En Java, las anotaciones se aplican de acuerdo con lo @Targetque declaran las anotaciones. Mediante la manipulación de código de bytes, es posible definir anotaciones independientemente de este control. Además, por ejemplo, es posible anotar un tipo de parámetro sin anotar el parámetro, incluso si la @Targetanotación se aplica a ambos elementos.

Definir cualquier atributo para un tipo o sus miembros.

Dentro del lenguaje Java, solo es posible definir anotaciones para campos, métodos o clases. En JBC, básicamente puede incrustar cualquier información en las clases de Java. Sin embargo, para utilizar esta información, ya no puede confiar en el mecanismo de carga de clases de Java, sino que necesita extraer la metainformación usted mismo.

Desbordamiento e implícitamente asignar byte, short, chary booleanlos valores

Los últimos tipos primitivos no se conocen normalmente en JBC, pero solo se definen para tipos de matriz o para descriptores de campo y método. Dentro de las instrucciones del código de bytes, todos los tipos nombrados ocupan el espacio de 32 bits que permite representarlos como int. Oficialmente, sólo los int, float, longy doubleexisten tipos del código de bytes, que todos necesitamos conversión explícita por la regla de verificador de la JVM.

No liberar un monitor

Un synchronizedbloque en realidad está compuesto por dos declaraciones, una para adquirir y otra para liberar un monitor. En JBC, puede adquirir uno sin liberarlo.

Nota : En implementaciones recientes de HotSpot, esto lleva a un IllegalMonitorStateExceptionfinal de un método o a una versión implícita si el método se termina por una excepción en sí misma.

Agregar más de una returndeclaración a un inicializador de tipo

En Java, incluso un inicializador de tipo trivial como

class Foo {
  static {
    return;
  }
}

es ilegal. En el código de bytes, el inicializador de tipo se trata como cualquier otro método, es decir, las declaraciones de retorno se pueden definir en cualquier lugar.

Crea bucles irreducibles

El compilador de Java convierte bucles en sentencias goto en código de bytes Java. Dichas declaraciones pueden usarse para crear bucles irreducibles, lo que el compilador de Java nunca hace.

Define un bloque de captura recursivo

En el código de bytes de Java, puede definir un bloque:

try {
  throw new Exception();
} catch (Exception e) {
  <goto on exception>
  throw Exception();
}

Una declaración similar se crea implícitamente cuando se usa un synchronizedbloque en Java donde cualquier excepción al liberar un monitor vuelve a las instrucciones para liberar este monitor. Normalmente, no debería producirse ninguna excepción en dicha instrucción, pero si así fuera (por ejemplo, en desuso ThreadDeath), el monitor aún se liberaría.

Llamar a cualquier método predeterminado

El compilador de Java requiere que se cumplan varias condiciones para permitir la invocación de un método predeterminado:

  1. El método debe ser el más específico (no debe ser anulado por una interfaz secundaria implementada por ningún tipo, incluidos los supertipos).
  2. El tipo de interfaz del método predeterminado debe ser implementado directamente por la clase que llama al método predeterminado. Sin embargo, si la interfaz Bextiende la interfaz Apero no anula un método A, aún se puede invocar el método.

Para el código de bytes de Java, solo cuenta la segunda condición. Sin embargo, el primero es irrelevante.

Invocar un súper método en una instancia que no sea this

El compilador de Java solo permite invocar un método super (o el predeterminado de la interfaz) en instancias de this. Sin embargo, en el código de bytes, también es posible invocar el súper método en una instancia del mismo tipo similar al siguiente:

class Foo {
  void m(Foo f) {
    f.super.toString(); // calls Object::toString
  }
  public String toString() {
    return "foo";
  }
}

Acceder a miembros sintéticos

En el código de bytes de Java, es posible acceder a miembros sintéticos directamente. Por ejemplo, considere cómo en el siguiente ejemplo Barse accede a la instancia externa de otra instancia:

class Foo {
  class Bar { 
    void bar(Bar bar) {
      Foo foo = bar.Foo.this;
    }
  }
}

Esto es generalmente cierto para cualquier campo sintético, clase o método.

Definir información de tipo genérico fuera de sincronización

Si bien el tiempo de ejecución de Java no procesa tipos genéricos (después de que el compilador de Java aplica la eliminación de tipos), esta información todavía se adjunta a una clase compilada como metainformación y se hace accesible a través de la API de reflexión.

El verificador no verifica la consistencia de estos Stringvalores codificados con metadatos. Por lo tanto, es posible definir información sobre tipos genéricos que no coincida con el borrado. Como consecuencia, las siguientes afirmaciones pueden ser ciertas:

Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());

Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);

Además, la firma se puede definir como no válida de modo que se genere una excepción de tiempo de ejecución. Esta excepción se produce cuando se accede a la información por primera vez, ya que se evalúa perezosamente. (Similar a los valores de anotación con un error).

Agregar metainformación de parámetros solo para ciertos métodos

El compilador de Java permite incrustar el nombre del parámetro y la información del modificador al compilar una clase con el parameterindicador habilitado. Sin embargo, en el formato de archivo de clase Java, esta información se almacena por método, lo que hace posible incrustar solo dicha información de método para ciertos métodos.

Arruine las cosas y bloquea tu JVM

Como ejemplo, en el código de bytes de Java, puede definir invocar cualquier método en cualquier tipo. Por lo general, el verificador se quejará si un tipo no conoce dicho método. Sin embargo, si invoca un método desconocido en una matriz, encontré un error en alguna versión de JVM donde el verificador se perderá esto y su JVM finalizará una vez que se invoque la instrucción. Sin embargo, esto no es una característica, pero técnicamente es algo que no es posible con Java compilado javac . Java tiene algún tipo de doble validación. La primera validación es aplicada por el compilador de Java, la segunda por la JVM cuando se carga una clase. Al omitir el compilador, puede encontrar un punto débil en la validación del verificador. Sin embargo, esta es más una declaración general que una característica.

Anotar el tipo de receptor de un constructor cuando no hay clase externa

Desde Java 8, los métodos no estáticos y los constructores de clases internas pueden declarar un tipo de receptor y anotar estos tipos. Los constructores de clases de nivel superior no pueden anotar su tipo de receptor, ya que la mayoría no declaran uno.

class Foo {
  class Bar {
    Bar(@TypeAnnotation Foo Foo.this) { }
  }
  Foo() { } // Must not declare a receiver type
}

Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()Sin embargo, dado que devuelve una AnnotatedTyperepresentación Foo, es posible incluir anotaciones de tipo para Fooel constructor directamente en el archivo de clase donde la API de reflexión luego lee estas anotaciones.

Usar instrucciones de código de bytes no utilizados / heredados

Como otros lo nombraron, lo incluiré también. Anteriormente, Java hacía uso de subrutinas por las declaraciones JSRy RET. JBC incluso conocía su propio tipo de dirección de retorno para este propósito. Sin embargo, el uso de subrutinas complicaba demasiado el análisis de código estático, por lo que estas instrucciones ya no se utilizan. En cambio, el compilador de Java duplicará el código que compila. Sin embargo, esto básicamente crea una lógica idéntica, por lo que realmente no considero que logre algo diferente. Del mismo modo, podría agregar, por ejemplo, elNOOPinstrucción de código de bytes que tampoco es utilizada por el compilador de Java, pero esto tampoco le permitiría lograr algo nuevo tampoco. Como se señaló en el contexto, estas "instrucciones de características" mencionadas ahora se eliminan del conjunto de códigos de operación legales, lo que las hace aún menos características.


3
Con respecto a los nombres de métodos, puede tener más de un <clinit>método definiendo métodos con el nombre <clinit>pero aceptando parámetros o teniendo un voidtipo sin retorno. Pero estos métodos no son muy útiles, la JVM los ignorará y el código de bytes no podrá invocarlos. El único uso sería confundir a los lectores.
Holger

2
Acabo de descubrir que JVM de Oracle detecta un monitor inédito en la salida del método y arroja un IllegalMonitorStateExceptionmensaje si omitió la monitorexitinstrucción. Y en caso de una salida de método excepcional que no pudo hacer un monitorexit, restablece el monitor en silencio.
Holger

1
@Holger: no lo sabía, sé que esto era posible en JVM anteriores, al menos, JRockit incluso tiene su propio controlador para este tipo de implementación. Actualizaré la entrada.
Rafael Winterhalter

1
Bueno, la especificación JVM no exige tal comportamiento. Lo acabo de descubrir porque intenté crear un bloqueo intrínseco colgante utilizando dicho código de byte no estándar.
Holger

3
Ok, encontré la especificación relevante : “ El bloqueo estructurado es la situación en la que, durante una invocación de método, cada salida en un monitor dado coincide con una entrada anterior en ese monitor. Dado que no hay garantía de que todo el código enviado a la Máquina virtual Java realice un bloqueo estructurado, las implementaciones de la Máquina virtual Java están permitidas pero no se requieren para aplicar las dos reglas siguientes que garantizan el bloqueo estructurado. ... "
Holger

14

Aquí hay algunas características que se pueden hacer en código de bytes de Java pero no en el código fuente de Java:

  • Lanzar una excepción marcada de un método sin declarar que el método la arroja. Las excepciones marcadas y no marcadas son algo que solo es verificado por el compilador de Java, no por la JVM. Debido a esto, por ejemplo, Scala puede lanzar excepciones comprobadas de los métodos sin declararlas. Aunque con los genéricos de Java hay una solución alternativa llamada furtivo .

  • Tener dos métodos en una clase que solo difieren en el tipo de retorno, como ya se mencionó en la respuesta de Joachim : La especificación del lenguaje Java no permite dos métodos en la misma clase cuando difieren solo en su tipo de retorno (es decir, el mismo nombre, la misma lista de argumentos, ...) Sin embargo, la especificación JVM no tiene esa restricción, por lo que un archivo de clase puede contener dos de estos métodos, simplemente no hay forma de producir dicho archivo de clase utilizando el compilador Java normal. Hay un buen ejemplo / explicación en esta respuesta .


44
Tenga en cuenta que no es una manera de hacer la primera cosa en Java. A veces se llama un lanzamiento furtivo .
Joachim Sauer

Ahora que es astuto! : D Gracias por compartir.
Esko Luontola

Creo que también puedes usarlo Thread.stop(Throwable)para un lanzamiento furtivo. Sin embargo, supongo que el que ya está vinculado es más rápido.
Bart van Heukelom el

2
No puede crear una instancia sin llamar a un constructor en Java bytecode. El verificador rechazará cualquier código que intente utilizar una instancia no inicializada. La implementación de deserialización de objetos utiliza ayudantes de código nativo para crear instancias sin llamadas de constructor.
Holger

Para un objeto de extensión Foo de clase, no puede crear una instancia de Foo llamando a un constructor que se declara en Object. El verificador lo rechazaría. Podría crear un constructor de este tipo utilizando ReflectionFactory de Java, pero esta apenas es una función de código de byte, pero Jni la realizó. Su respuesta es incorrecta y Holger es correcta.
Rafael Winterhalter

8
  • GOTOpuede usarse con etiquetas para crear sus propias estructuras de control (que no sean for whileetc.)
  • Puede anular la thisvariable local dentro de un método
  • Combinando ambos, puede crear un código de bytes optimizado para crear una llamada de cola (lo hago en JCompilo )

Como punto relacionado, puede obtener el nombre del parámetro para los métodos si se compila con depuración ( Paranamer hace esto leyendo el código de bytes


¿Cómo funciona overridela variable local?
Michael

2
Anular @Michael es una palabra demasiado fuerte. En el nivel de bytecode, se accede a todas las variables locales mediante un índice numérico y no hay diferencia entre escribir en una variable existente o inicializar una nueva variable (con un alcance disyuntivo), en cualquier caso, es solo una escritura en una variable local. La thisvariable tiene índice cero, pero además de preinicializarse con la thisreferencia al ingresar un método de instancia, es solo una variable local. Por lo tanto, puede escribirle un valor diferente, que puede actuar como terminar this'alcance o cambiar la thisvariable, dependiendo de cómo lo use.
Holger el

¡Veo! Entonces, ¿es realmente eso lo que thisse puede reasignar? Creo que fue solo la anulación de la palabra lo que me hizo preguntarme qué significaba exactamente.
Michael

5

Tal vez la sección 7A de este documento sea ​​de interés, aunque se trata de errores de bytecode en lugar de características de bytecode .


Lectura interesante, pero no parece que uno quiera (ab) usar ninguna de esas cosas.
Bart van Heukelom

4

En lenguaje Java, la primera instrucción en un constructor debe ser una llamada al constructor de superclase. Bytecode no tiene esta limitación, en cambio, la regla es que se debe llamar al constructor de la superclase u otro constructor de la misma clase para el objeto antes de acceder a los miembros. Esto debería permitir más libertad como:

  • Cree una instancia de otro objeto, almacénelo en una variable local (o pila) y páselo como parámetro al constructor de superclase mientras mantiene la referencia en esa variable para otro uso.
  • Llame a otros constructores diferentes en función de una condición. Esto debería ser posible: ¿Cómo llamar a un constructor diferente condicionalmente en Java?

No los he probado, así que corrígeme si me equivoco.


Incluso puede establecer miembros de una instancia antes de llamar a su constructor de superclase. Sin embargo, leer campos o métodos de llamada no es posible antes de eso.
Rafael Winterhalter

3

Algo que puede hacer con el código de bytes, en lugar del código Java simple, es generar código que se pueda cargar y ejecutar sin un compilador. Muchos sistemas tienen JRE en lugar de JDK y si desea generar código dinámicamente, puede ser mejor, si no más fácil, generar código de bytes en lugar de código Java que debe compilarse antes de que pueda usarse.


66
Pero entonces solo estás salteando el compilador, no produciendo algo que no podría ser producido usando el compilador (si estuviera disponible).
Bart van Heukelom

2

Escribí un optimizador de bytecode cuando era un I-Play (fue diseñado para reducir el tamaño del código para aplicaciones J2ME). Una característica que agregué fue la capacidad de usar bytecode en línea (similar al lenguaje ensamblador en línea en C ++). Logré reducir el tamaño de una función que formaba parte de un método de biblioteca utilizando la instrucción DUP, ya que necesito el valor dos veces. También tenía instrucciones de cero bytes (si está llamando a un método que toma un char y desea pasar un int, que sabe que no necesita ser lanzado, agregué int2char (var) para reemplazar char (var) y eliminaría la instrucción i2c para reducir el tamaño del código. También hice que flotara a = 2.3; flotante b = 3.4; flotante c = a + b; y eso se convertiría en un punto fijo (más rápido, y también algunos J2ME no lo hicieron) Soporte de coma flotante).


2

En Java, si intenta anular un método público con un método protegido (o cualquier otra reducción en el acceso), obtiene un error: "intentando asignar privilegios de acceso más débiles". Si lo hace con el código de bytes JVM, el verificador está bien y puede llamar a estos métodos a través de la clase padre como si fueran públicos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.