¿Cuál es el método predeterminado?
Después del lanzamiento de Java 8, se pueden agregar nuevos métodos a la interfaz, pero la interfaz seguirá siendo compatible con su clase de implementación. Esto es importante porque la biblioteca que desarrolle puede ser utilizada ampliamente por varios desarrolladores. Antes de Java 8, después de que se publicara una interfaz en una biblioteca de clases, si se agregaba un nuevo método a la interfaz, las aplicaciones que implementaban esta interfaz corrían peligro de fallar al usar la nueva versión de la interfaz.
Con Java 8, ¿no existe tal peligro? La respuesta es no.
Agregar métodos predeterminados a una interfaz puede hacer que algunas clases de implementación no estén disponibles.
Primero, veamos los detalles del método predeterminado.
En Java 8, se pueden implementar métodos en interfaces (los métodos estáticos en Java 8 también se pueden implementar en interfaces, pero ese es otro tema). El método implementado en la interfaz se denomina método predeterminado, que se identifica con la palabra clave default como modificador. Cuando una clase implementa una interfaz, puede implementar métodos que ya están implementados en la interfaz, pero esto no es necesario. Esta clase heredará el método predeterminado. Es por eso que cuando cambia la interfaz, no es necesario cambiar la clase de implementación.
¿Qué pasa con las herencias múltiples?
Cuando una clase implementa más de una (como dos) interfaces y estas interfaces tienen el mismo método predeterminado, las cosas se vuelven muy complicadas. ¿Qué método predeterminado hereda la clase? ¡Ni! En este caso, la propia clase (ya sea directamente o una clase situada más arriba en el árbol de herencia) debe implementar el método predeterminado.
Lo mismo ocurre cuando una interfaz implementa el método predeterminado y otra interfaz declara el método predeterminado como abstracto. Java 8 intenta evitar la ambigüedad y mantener el rigor. Si un método se declara en varias interfaces, ninguna de las implementaciones predeterminadas se heredará y obtendrá un error en tiempo de compilación.
Sin embargo, si ha compilado su clase, no habrá errores en tiempo de compilación. En este punto, Java 8 es inconsistente. Tiene sus propias razones, y hay varias razones por las que no quiero explicarlo en detalle ni discutirlo en profundidad aquí (porque: la versión se lanzó, el tiempo de discusión es demasiado largo y esta plataforma nunca ha existido). tal discusión).
1. Suponga que tiene dos interfaces y una clase de implementación.
2. Una de las interfaces implementa un método predeterminado m().
3. Compile la interfaz y la clase de implementación juntas.
4. Modifique la interfaz que no contiene el método m() y declare el método m() como abstracto.
5. Vuelva a compilar la interfaz modificada por separado.
6. Ejecute la clase de implementación.
1. Modifique la interfaz que contiene el método abstracto m() y cree una implementación predeterminada.
2. Compile la interfaz modificada.
3. Ejecutar clase: fallido.
Cuando dos interfaces proporcionan una implementación predeterminada para el mismo método, este método no se puede llamar a menos que la clase de implementación también implemente el método predeterminado (ya sea directamente o mediante una clase de nivel superior en el árbol de herencia).
Código de ejemplo:
Para demostrar el ejemplo anterior, creé un directorio de prueba para C.java, y debajo hay 3 subdirectorios para almacenar I1.java e I2.java. El directorio de prueba contiene el código fuente C.java de clase C. El directorio base contiene la versión de la interfaz que se puede compilar y ejecutar. I1 contiene el método m() con implementación predeterminada y I2 no contiene ningún método.
La clase de implementación contiene el método principal para que podamos ejecutarlo en nuestras pruebas. Verificará si hay parámetros de línea de comando, de modo que podamos realizar pruebas fácilmente llamando a m() y no llamando a m().
Copie el código de código de la siguiente manera:
~/github/test$ gato C.java
la clase pública C implementa I1, I2 {
público estático vacío principal (String [] argumentos) {
Cc = nuevo C();
si(args.longitud == 0){
centímetro();
}
}
}
~/github/test$ gato base/I1.java
interfaz pública I1 {
vacío predeterminado m(){
System.out.println("hola interfaz 1");
}
}
~/github/test$ cat base/I2.java
interfaz pública I2 {
}
Utilice la siguiente línea de comando para compilar y ejecutar:
Copie el código de la siguiente manera:~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hola interfaz 1
El directorio compatible contiene la interfaz I2 con el método abstracto m() y la interfaz I1 sin modificar.
Copie el código de la siguiente manera:~/github/test$ cat compatible/I2.java
interfaz pública I2 {
vacío m();
}
Esto no se puede utilizar para compilar la clase C:
Copie el código de la siguiente manera:~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C no es abstracto y no anula el método abstracto m() en I2
la clase pública C implementa I1, I2 {
^
1 error
El mensaje de error es muy preciso. Debido a que tenemos la clase C. obtenida en la compilación anterior, si compilamos las interfaces en el directorio compatible, aún obtendremos dos interfaces que pueden ejecutar la clase de implementación:
Copie el código de código de la siguiente manera:
~/github/test$ compatible con javac/I*.java
~/github/test$ java -cp .:compatible C
hola interfaz 1
El tercer directorio llamado incorrecto contiene la interfaz I2 que también define el método m():
Copie el código de código de la siguiente manera:
~/github/test$ gato incorrecto/I2.java
interfaz pública I2 {
vacío predeterminado m(){
System.out.println("hola interfaz 2");
}
}
Deberíamos tomarnos la molestia de compilarlo. Aunque el método m() se define dos veces, la clase de implementación aún puede ejecutarse siempre que no llame al método definido varias veces. Sin embargo, siempre que llamemos al método m(), fallará inmediatamente. Estos son los parámetros de la línea de comando que utilizamos:
Copie el código de código de la siguiente manera:
~/github/test$ javac incorrecto/*.java
~/github/test$ java -cp .:C incorrecto
Excepción en el hilo "principal" java.lang.IncompatibleClassChangeError: conflictivo
métodos predeterminados: I1.m I2.m
en Cm(C.java)
en C.principal(C.java:5)
~/github/test$ java -cp .:C x incorrecto
~/github/prueba$
en conclusión
Cuando traslada una biblioteca de clases que agrega una implementación predeterminada a una interfaz al entorno Java 8, generalmente no habrá ningún problema. Al menos eso es lo que pensaron los desarrolladores de la biblioteca de clases Java8 cuando agregaron métodos predeterminados a las clases de colección. Las aplicaciones que utilizan su biblioteca aún dependen de bibliotecas de Java 7 que no tienen métodos predeterminados. Al utilizar y modificar varias bibliotecas de clases diferentes, existe una pequeña posibilidad de que se produzcan conflictos. ¿Cómo se puede evitar esto?
Diseña tu biblioteca de clases como antes. No lo tome a la ligera cuando confíe en el método predeterminado. No utilizar como último recurso. Elija sabiamente los nombres de los métodos para evitar conflictos con otras interfaces. Aprenderemos cómo utilizar esta característica para el desarrollo en programación Java.