Traducción de un artículo escrito por Peter Verhas con fecha de abril de 2014. Del traductor: el término " método predeterminado " acaba de aparecer en Java y no estoy seguro de si existe una traducción establecida al ruso para él. Usaré el término "método predeterminado", aunque no creo que sea ideal. Los invito a discutir una traducción más exitosa.
¿Cuál es el método predeterminado?
Ahora, con el lanzamiento de Java 8, puedes agregar nuevos métodos a las interfaces para que la interfaz siga siendo compatible con las clases que la implementan. Esto es muy importante si está desarrollando una biblioteca que utilizan muchos programadores desde Kiev hasta Nueva York. Antes de Java 8, si definía una interfaz en una biblioteca, no podía agregarle métodos sin correr el riesgo de que alguna aplicación que ejecutaba su interfaz se rompiera cuando se actualizaba. Entonces, ¿en Java 8 ya no puedes tener miedo de esto? No, no puedes. Agregar un método predeterminado a una interfaz puede inutilizar algunas clases. Primero veamos las cosas buenas de los métodos predeterminados. En Java 8, el método se puede implementar directamente en la interfaz. (Ahora también se pueden implementar métodos estáticos en una interfaz, pero esa es otra historia). Un método implementado en una interfaz se denomina método predeterminado y se indica mediante la palabra clave default . Si una clase implementa una interfaz, puede implementar los métodos implementados en la interfaz, pero no está obligado a hacerlo. La clase hereda la implementación predeterminada. Es por esto que no es necesario modificar las clases al cambiar la interfaz que implementan.¿Herencia múltiple?
Las cosas se complican más si una clase implementa más de una (digamos, dos) interfaces e implementan el mismo método predeterminado. ¿Qué método heredará la clase? La respuesta es ninguna. En este caso, la clase debe implementar el método en sí (ya sea directamente o heredándolo de otra clase). La situación es similar si solo una interfaz tiene un método predeterminado y en la otra el mismo método es abstracto. Java 8 intenta ser disciplinado y evitar situaciones ambiguas. Si los métodos se declaran en más de una interfaz, la clase no hereda ninguna implementación predeterminada; obtendrá un error de compilación. Sin embargo, es posible que no obtenga un error de compilación si su clase ya está compilada. Java 8 no es lo suficientemente robusto a este respecto. Hay razones para esto, que no quiero entrar en discusión (por ejemplo: la versión de Java ya se lanzó y el tiempo para las discusiones ya pasó y, en general, este no es el lugar para ellas).- Digamos que tienes dos interfaces y una clase implementa ambas.
- Una de las interfaces implementa el método predeterminado m().
- Compilas todas las interfaces y la clase.
- Puedes cambiar una interfaz que no tiene un método m() declarándola como un método abstracto.
- Compila solo la interfaz modificada.
- Comienza la clase.
- cambie la interfaz con el método abstracto m() y agregue una implementación predeterminada.
- Compile la interfaz modificada.
- Clase de ejecución: error.
Código de ejemplo
Para demostrar lo anterior, creé un directorio de prueba para la clase C.java y 3 subdirectorios para las interfaces en los archivos I1.java e I2.java. El directorio raíz de la prueba contiene el código fuente de la clase C.java. El directorio base contiene una versión de las interfaces que son adecuadas para ejecución y compilación: la interfaz I1 tiene un método predeterminado m(); La interfaz I2 aún no tiene ningún método. La clase tiene un métodomain
para que podamos ejecutarlo para probarlo. Comprueba si hay argumentos en la línea de comando, por lo que podemos ejecutarlo fácilmente con o sin llamar al archivo m()
.
~/github/test$ cat C.java
public class C implements I1, I2 {
public static void main(String[] args) {
C c = new C();
if( args.length == 0 ){
c.m();
}
}
}
~/github/test$ cat base/I1.java
public interface I1 {
default void m(){
System.out.println("hello interface 1");
}
}
~/github/test$ cat base/I2.java
public interface I2 {
}
Puede compilar y ejecutar la clase desde la línea de comando.
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
El directorio compatible contiene una versión de la interfaz I2 que declara que el método m() es abstracto y también, por razones técnicas, una copia sin modificaciones de I1.java.
~/github/test$ cat compatible/I2.java
public interface I2 {
void m();
}
Un conjunto de este tipo no se puede utilizar para compilar una clase C:
~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
^
1 error
El mensaje de error es muy preciso. Sin embargo, tenemos C.class de una compilación anterior y, si compilamos las interfaces en el directorio compatible, tendremos dos interfaces que aún se pueden usar para ejecutar la clase:
~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1
El tercer directorio wrong
contiene la versión I2, que también declara el método m()
:
~/github/test$ cat wrong/I2.java
public interface I2 {
default void m(){
System.out.println("hello interface 2");
}
}
Ni siquiera tienes que preocuparte por la compilación. Aunque el método se declara dos veces, la clase aún se puede usar y ejecutar hasta que se llame al método m(). Esto es para lo que necesitamos el argumento de la línea de comando:
~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
at C.m(C.java)
at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$
GO TO FULL VERSION