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?
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 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 siemprestatic 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 Lib
arrojará 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 null
despues 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
docs.oracle.com/javase/specs/jls/se7/html/jls-13.html
– Ray Chen
20 de febrero de 2013 a las 6:15
referencia indispensable al considerar cambios que podrían romper la compatibilidad binaria: wiki.eclipse.org/Evolving_Java-based_APIs_2
– jordanpg
1 de noviembre de 2017 a las 14:33