¿Qué es la compatibilidad binaria en Java?

6 minutos de lectura

Avatar de usuario de Sam
Sam

Estaba leyendo Java efectivo de Joshua Bloch.

En el artículo 17: “Usar interfaces solo para definir tipos”, encontré la explicación donde no se recomienda usar interfaces para almacenar constantes. Pongo la explicación a continuación.

“Peor aún, representa un compromiso: si en una versión futura se modifica la clase para que ya no necesite usar las constantes, aún debe implementar la interfaz para garantizar la compatibilidad binaria”.

¿Qué significa compatibilidad binaria aquí?

¿Puede alguien guiarme con un ejemplo en Java para mostrar que el código es compatible con binario?

Avatar de usuario de Evgeniy Dorofeev
Evgeniy Dorofeev

En resumen, la compatibilidad binaria significa que cuando cambia su clase, no necesita volver a compilar las clases que la usan. Por ejemplo, eliminó o cambió el nombre de un método público o protegido de esta clase

public class Logger implements Constants {
   public Logger getLogger(String name) {
         return LogManager.getLogger(name);
   }
}

de su biblioteca log-1.jar y lanzó una nueva versión como log-2.jar. Cuando los usuarios de su log-1.jar descarguen la nueva versión, romperán sus aplicaciones cuando intenten usar el método getLogger (nombre de la cadena) faltante.

Y si elimina la interfaz Constants (Ítem 17), esto también romperá la compatibilidad binaria, por la misma razón.

Pero puede eliminar/cambiar el nombre de un miembro privado o de paquete privado de esta clase sin romper la compatibilidad binaria, porque las aplicaciones externas no pueden (o no deberían) usarlo.

  • Lo que me sorprende es que las documentaciones oficiales no pueden explicar sus cosas con este nivel de simplicidad. No estoy seguro de por qué quieren usar un lenguaje similar que los abogados suelen usar en términos y acuerdos.

    – Tarik

    23 de abril de 2014 a las 1:19

  • Richard E. Little tiene un ejemplo muy simple y bonito en su Blog. Es en realidad mostrando el problema de la compatibilidad binaria en lugar de la compatibilidad del código fuente, como se ilustra en esta respuesta (con el método renombrado).

    – Yann-Gaël Guéhéneuc

    9 de mayo de 2014 a las 6:43

  • @Tarik, ¿qué documentación oficial probaste para poder hacer una declaración tan audaz?

    – Holger

    1 de febrero de 2022 a las 11:42

Ciro Santilli Avatar de usuario de OurBigBook.com
Ciro Santilli OurBigBook.com

Para entender mejor el concepto, es interesante ver que la compatibilidad binaria NO implica compatibilidad API, ni viceversa.

Compatible con API pero NO compatible con binario: eliminación estática

Versión 1 de la biblioteca:

public class Lib {
    public static final int i = 1;
}

Codigo del cliente:

public class Main {
    public static void main(String[] args) {
        if ((new Lib()).i != 1) throw null;
    }
}

Compile el código del cliente con la versión 1:

javac Main.java

Reemplace la versión 1 con la versión 2: eliminar static:

public class Lib {
    public final int i = 1;
}

recompilar justo versión 2, no el código del cliente y ejecutar java Main:

javac Lib.java
java Main

Obtenemos:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.i
        at Main.main(Main.java:3)

Esto sucede porque a pesar de que podemos escribir (new Lib()).i en Java para ambos static y métodos miembro, se compila en dos instrucciones de VM diferentes dependiendo de Lib: getstatic o getfield. Este descanso se menciona en JLS 7 13.4.10:

Si un campo que no se declaró privado no se declaró estático y se cambia para que se declare estático, o viceversa, se producirá un error de vinculación, específicamente un IncompatibleClassChangeError, si el campo es utilizado por un binario preexistente que esperaba un campo. del otro tipo.

Tendríamos que recompilar Main con javac Main.java para que funcione con la nueva versión.

Notas:

  • llamando a miembros estáticos desde instancias de clase como (new Lib()).i es de mal estilo, genera una advertencia y nunca debe hacerse
  • este ejemplo es artificial porque no estático final las primitivas son inútiles: use siempre static final para primitivas: atributo estático final privado frente a atributo final privado
  • la reflexión podría usarse para ver la diferencia. Pero la reflexión también puede ver campos privados, lo que obviamente conduce a rupturas que se suponía que no debían contar como rupturas, por lo que no cuenta.

Compatible con binario pero NO compatible con API: fortalecimiento de condición previa nula

Versión 1:

public class Lib {
    /** o can be null */
    public static void method(Object o) {
        if (o != null) o.hashCode();
    }
}

Versión 2:

public class Lib {
    /** o cannot be null */
    public static void method(Object o) {
        o.hashCode();
    }
}

Cliente:

public class Main {
    public static void main(String[] args) {
        Lib.method(null);
    }
}

Esta vez, incluso si se vuelve a compilar Main después de actualizar Libarrojará la segunda invocación, pero no la primera.

Esto se debe a que cambiamos el contrato de method de una manera que Java no puede verificar en tiempo de compilación: antes de que pudiera tomar nulldespues ya no.

Notas:

  • el wiki de Eclipse es una gran fuente para esto: https://wiki.eclipse.org/Evolving_Java-based_API
  • hacer API que acepten null valores es una práctica cuestionable
  • es mucho más fácil hacer un cambio que rompa la compatibilidad API pero no binaria que viceversa, ya que es fácil cambiar la lógica interna de los métodos

Ejemplo de compatibilidad binaria en C

¿Qué es una interfaz binaria de aplicación (ABI)?

  • En mi humilde opinión, el primer ejemplo es incorrecto, porque eliminas static y ahora es incompatibilidad API (es decir, Lib.i en bytecode ya no funciona). Razonar la compatibilidad de API para algún código no depende de si los clientes usan esta API sino del estándar (o especificación) para la compatibilidad de API. Aunque estoy de acuerdo en que la compatibilidad API no implica compatibilidad binaria.

    – gato flotante

    28 de febrero de 2016 a las 14:11

Si en el futuro deseamos cambiar la interfaz que están implementando algunas clases (por ejemplo, agregar algunos métodos nuevos).

Si agregamos métodos abstractos (métodos adicionales), luego las clases (implementando la interfaz) debe implementar el método adicional que crea la restricción de dependencia y los costos generales para realizar lo mismo.

Para superar esto, podemos agregar por defecto métodos en la interfaz.

Esto eliminará la dependencia para implementar los métodos adicionales.

No necesitamos modificar la clase de implementación para incorporar cambios. Esto se llama Compatibilidad Binaria.

Consulte el siguiente ejemplo:

La interfaz que vamos a utilizar.

    //Interface       
    interface SampleInterface
            {
                // abstract method
                public void abstractMethod(int side);

                // default method
                default void defaultMethod() {
                   System.out.println("Default Method Block");
                }

                // static method
                static void staticMethod() {
                    System.out.println("Static Method Block");
                }
            }


//The Class that implements the above interface.

    class SampleClass implements SampleInterface
    {
        /* implementation of abstractMethod abstract method, if not implemented 
        will throw compiler error. */
        public void abstractMethod(int side)
        {System.out.println(side*side);}

        public static void main(String args[])
        {
            SampleClass sc = new SampleClass();
            sc.abstractMethod(4);

            // default method executed
            sc.defaultMethod();

            // Static method executed
            SampleInterface.staticMethod();

        }
    }

Nota: Para obtener información más detallada, consulte métodos predeterminados

  • Los métodos predeterminados y estáticos están disponibles en la versión 8 de Java o más.

    – BIPIN SHARMA

    18 de octubre de 2017 a las 7:19

¿Ha sido útil esta solución?