¿Cómo escribir código en Java 11, pero apuntar a Java 8 y superior?

8 minutos de lectura

avatar de usuario de neshkeev
Neshkeev

Estoy trabajando en una biblioteca pequeña y, por razones obvias, me gustaría escribir código usando todas las funciones de Java 11 (excepto los módulos, supongo que por ahora), pero me gustaría que la biblioteca sea compatible con Java 8 y superior.

Cuando intento esto:

javac -source 11 -target 1.8 App.java

Recibo el siguiente mensaje:

warning: source release 11 requires target release 11

… y cuando miro el código de bytes veo que la versión de la clase es 0x37 (Java 11):

$ xxd App.class
00000000: cafe babe 0000 0037 ...

Y Java 8 no puede cargarlo:

Exception in thread "main" java.lang.UnsupportedClassVersionError: App has been
    compiled by a more recent version of the Java Runtime (class file version 55.0),
    this version of the Java Runtime only recognizes class file versions up to 52.0
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

¿Cómo proporcionan las personas tal compatibilidad? Estoy abierto a todas las herramientas de compilación.

Para mí, parece fácil transformar el lenguaje de alto nivel (Java) en bajo nivel (código de bytes). Me parece que cuando cambia el lenguaje de alto nivel, el de bajo nivel debe permanecer igual. Por eso pensé que era posible.

ACTUALIZAR

Chicos, no creo que esta respuesta duplique Move to OpenJDK-11 pero compile en Java 8, porque allí el OP pregunta cómo seguir produciendo el código con Java 8 características, pero apunte a Java 11 (que es solo una famosa compatibilidad con versiones anteriores). Mi pregunta es al revés: quiero producir el código en Java 11, pero apuntar a Java 8. Encontré esa pregunta cuando estaba investigando el tema antes de plantear la pregunta. No lo encontré aplicable a mi situación.

La otra pregunta ¿Se puede compilar el código Java 8 para ejecutarse en Java 7 JVM? Se parece a mi pregunta, pero se hizo en 2013 y el código de bytes obviamente cambió entre Java 7 y Java 8.

No pensé que el código de bytes cambiara tanto desde Java 8, por eso hice esta pregunta.

  • Si desea orientar y crear una versión Java 8 de su proyecto, solo puede usar las características y bibliotecas del lenguaje Java 8, no las más nuevas.

    – Nicolás Hirras

    30 de enero de 2019 a las 19:04

  • ¿Qué características del lenguaje Java 11 necesita? Las diferencias entre Java 8 y Java 11 son menores.

    – Zheka Kozlov

    31 de enero de 2019 a las 12:42


  • Creo que la única forma (segura) de hacer esto es cambiar a Kotlin, que puede producir un código de bytes compatible con JVM 1.8. De esta manera, obtiene acceso a funciones de lenguaje moderno pero produce un código de bytes compatible. Las características del lenguaje Java 11 requieren un nuevo formato de código de bytes que no es compatible con JVM 1.8.

    – berezovskyi

    16 abr a las 21:04


Avatar de usuario de Holger
Holger

Si bien la conversión de clases compiladas para JDK 11 a JDK 8 sería teóricamente posible con una herramienta sofisticada, no es trivial. Hay cambios significativos en el nivel binario.

Primero, se introdujo JDK 11 nido tipos, lo que elimina la necesidad de generar métodos de acceso sintéticos al acceder private miembros de clases internas/externas. Por supuesto, dicho acceso fallaría en versiones anteriores.

también introdujo constantes dinámicas, aunque no sé si el lenguaje Java explota esa característica en alguna parte. Esto está pensado principalmente para futuras versiones.

Luego, desde JDK 9, la concatenación de cadenas se compila usando invokedynamic refiriéndose a java.lang.invoke.StringConcatFactory que no está presente en Java 8.

Una característica que podría funcionar, es private métodos en interfaces, introducido en Java 9 como una característica del lenguaje, pero ya manejado en el nivel binario en Java 8.

Java 8 tampoco podría procesar definiciones de módulos, pero supongo que serían ignorados.

Avatar de usuario de Buurman
Buurman

No, no puede compilar el código fuente de Java 11 en binarios de Java 8.

En javac términos, el -source El parámetro no puede ser mayor que el -target parámetro.

Entonces, si desea producir binarios de Java 8, sus fuentes deben estar escritas en Java 8 (o anterior). Si no usa ninguna característica del lenguaje Java 11, sus fuentes básicamente ya están en Java 8, por lo que esto no debería ser un problema demasiado grande.

Tenga en cuenta que aún puede usar un JDK 11 para compilar el código fuente de Java 8 en binarios de Java 8. La versión JDK pueden ser mayor que las versiones de origen y/o de destino.

Nota: la documentación de javac no dice nada sobre el -source parámetro que tiene que ser menor o igual que el -target parámetro. Sin embargo, hay mucha documentación no oficial. Por ejemplo, https://stackoverflow.com/a/9261298/691074

Por lo que puedo decir, tampoco hay un solo contraejemplo de cómo hacer que una situación así funcione.

  • por favor, dame el enlace a los documentos que respalden esta declaración “En javac términos, el -source El parámetro no puede ser mayor que el -targetparámetro.” No encontré nada como esto aquí

    – neshkeev

    9 de mayo de 2019 a las 12:24

  • Tienes razón en que los documentos no mencionan eso. El mejor recurso que he podido encontrar dice que, según los documentos, el compilador debería ser capaz de hacer esto. Sin embargo, en la práctica parece que no lo hará. Agregaré la mejor fuente que he podido encontrar a corto plazo.

    – Buurman

    9 de mayo de 2019 a las 13:54

Hay una herramienta llamada Jabel creado por @bsideup, que le permite hacer esto. Pretende ser un procesador de anotaciones para que pueda conectarse a javac. Sin embargo, no realiza ningún procesamiento de anotación real. En cambio, durante la compilación, piratea las partes internas de javac y le hace creer que las características más nuevas como variablediamante en la creación de clases anónimas e incluso otras más nuevas como bloques de texto y cambiar expresiones están bien para usar en Java 8.

Esta es una solución hacky sin ninguna garantía, así que utilícela con precaución.

Avatar de usuario de GhostCat
gato fantasma

Podría estar equivocado aquí, pero al menos hasta ahora, javac no está destinado a usarse de esa manera.

Aquí hay que adivinar un poco: podría intentar ver si --release 8 --target 8 funciona (sin dar la --source 11 parámetro).

Pero dudo que esto funcione. Creo que no hay soporte en javac para aceptar N funciones de código fuente, y hacer que se compile al revés a versiones de destino anteriores.

Por supuesto, el compilador podría tener conocimiento sobre las transformaciones requeridas para convertir N código fuente en (Nm) código de bytes. Pero eso haría que el compilador fuera mucho más complejo, y cada versión se sumaría a eso. También agregaría un costo dramático a la pruebas esfuerzos Dudo que los mantenedores del compilador estén dispuestos a comprar eso. Realmente no es como si este fuera un caso de uso generalizado.

Entonces el solamente “solución” que conozco: ramificación y doble mantenimiento. Y para mantener las cosas razonables, simplemente mantendría una versión de Java 8 y quizás uno para Java 11.

Aunque no vi nada explícito en el javadoc para javac, creo que solo puede indicar la misma versión para las opciones -source y -target. Las funciones de Java 11 no son compatibles con Java 8, aunque ocurre lo contrario, una versión superior de Java puede ejecutar código compilado en una versión inferior. Por lo tanto, no creo que sea posible compilar código escrito en Java 11 para que se pueda ejecutar en Java 8.

  • Pienso: no es posible con las herramientas que tenemos ahora mismo (lo más probable). Si gasta suficientes recursos, probablemente podría crear un compilador de este tipo. Es solo que esto podría ser muy costoso y no hay mucha gente interesada en esa función.

    – Gato fantasma

    30 de enero de 2019 a las 19:44

  • @GhostCat Hay grandes diferencias en el formato de archivo de clase. P.ej CONSTANTE_Dinámico y concatenación de cadenas indicada. No tengo idea de cómo se pueden traducir a Java 8…

    – Zheka Kozlov

    31 de enero de 2019 a las 12:46

  • Pienso: no es posible con las herramientas que tenemos ahora mismo (lo más probable). Si gasta suficientes recursos, probablemente podría crear un compilador de este tipo. Es solo que esto podría ser muy costoso y no hay mucha gente interesada en esa función.

    – Gato fantasma

    30 de enero de 2019 a las 19:44

  • @GhostCat Hay grandes diferencias en el formato de archivo de clase. P.ej CONSTANTE_Dinámico y concatenación de cadenas indicada. No tengo idea de cómo se pueden traducir a Java 8…

    – Zheka Kozlov

    31 de enero de 2019 a las 12:46

¿Ha sido útil esta solución?