Java Language
Métodos predeterminados
Buscar..
Introducción
El método predeterminado introducido en Java 8 permite a los desarrolladores agregar nuevos métodos a una interfaz sin romper las implementaciones existentes de esta interfaz. Proporciona flexibilidad para permitir que la interfaz defina una implementación que se utilizará como predeterminada cuando una clase que implementa esa interfaz no puede proporcionar una implementación de ese método.
Sintaxis
- Nombre del método vacío público predeterminado () {/ * cuerpo del método * /}
Observaciones
Métodos predeterminados
- Puede usarse dentro de una interfaz, para introducir un comportamiento sin forzar las subclases existentes para implementarlo.
- Puede ser reemplazado por subclases o por una sub-interfaz.
- No se les permite anular los métodos en la clase java.lang.Object.
- Si una clase que implementa más de una interfaz, hereda los métodos predeterminados con firmas de métodos idénticas de cada una de las interfaces, debe invalidar y proporcionar su propia interfaz como si no fueran métodos predeterminados (como parte de la resolución de la herencia múltiple).
- A pesar de que están destinadas a introducir un comportamiento sin romper las implementaciones existentes, las subclases existentes con un método estático con la misma firma de método que el método por defecto recién introducido todavía se romperán. Sin embargo, esto es cierto incluso en el caso de introducir un método de instancia en una superclase.
Métodos estáticos
- Puede usarse dentro de una interfaz, principalmente destinada a ser utilizada como un método de utilidad para los métodos predeterminados.
- No puede ser reemplazado por subclases o por una sub-interfaz (está oculto para ellos). Sin embargo, como es el caso de los métodos estáticos, incluso ahora, cada clase o interfaz puede tener la suya propia.
- No se les permite anular los métodos de instancia en la clase java.lang.Object (como en la actualidad es el caso de las subclases también).
A continuación se muestra una tabla que resume la interacción entre subclase y superclase.
- | SUPER_CLASS-INSTANCE-METHOD | SUPER_CLASS-STATIC-METHOD |
---|---|---|
SUB_CLASS-INSTANCE-METHOD | anula | genera-compiletime-error |
SUB_CLASS-STATIC-METHOD | genera-compiletime-error | se esconde |
A continuación se muestra una tabla que resume la interacción entre la interfaz y la clase de implementación.
- | INTERFACE-DEFAULT-METHOD | INTERFACE-ESTÁTICO-MÉTODO |
---|---|---|
IMPL_CLASS-INSTANCE-METHOD | anula | se esconde |
IMPL_CLASS-STATIC-METHOD | genera-compiletime-error | se esconde |
Referencias:
- http://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method
- https://docs.oracle.com/javase/tutorial/java/IandI/override.html
Uso básico de los métodos por defecto.
/**
* Interface with default method
*/
public interface Printable {
default void printString() {
System.out.println( "default implementation" );
}
}
/**
* Class which falls back to default implementation of {@link #printString()}
*/
public class WithDefault
implements Printable
{
}
/**
* Custom implementation of {@link #printString()}
*/
public class OverrideDefault
implements Printable {
@Override
public void printString() {
System.out.println( "overridden implementation" );
}
}
Las siguientes afirmaciones
new WithDefault().printString();
new OverrideDefault().printString();
Producirá esta salida:
default implementation
overridden implementation
Acceso a otros métodos de interfaz dentro del método predeterminado
También puede acceder a otros métodos de interfaz desde su método predeterminado.
public interface Summable {
int getA();
int getB();
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {
@Override
public int getA() {
return 1;
}
@Override
public int getB() {
return 2;
}
}
La siguiente declaración imprimirá 3 :
System.out.println(new Sum().calculateSum());
Los métodos predeterminados también podrían usarse junto con los métodos de interfaz estáticos:
public interface Summable {
static int getA() {
return 1;
}
static int getB() {
return 2;
}
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {}
La siguiente declaración también imprimirá 3:
System.out.println(new Sum().calculateSum());
Accediendo a métodos predeterminados sobrescritos desde la implementación de la clase
En las clases, super.foo()
buscará solo en superclases. Si desea llamar a una implementación predeterminada desde una superinterfaz, necesita calificar a super
con el nombre de la interfaz: Fooable.super.foo()
.
public interface Fooable {
default int foo() {return 3;}
}
public class A extends Object implements Fooable {
@Override
public int foo() {
//return super.foo() + 1; //error: no method foo() in java.lang.Object
return Fooable.super.foo() + 1; //okay, returns 4
}
}
¿Por qué usar métodos predeterminados?
La respuesta simple es que le permite evolucionar una interfaz existente sin romper las implementaciones existentes.
Por ejemplo, tienes la interfaz Swim
que publicaste hace 20 años.
public interface Swim {
void backStroke();
}
Hicimos un gran trabajo, nuestra interfaz es muy popular, hay muchas implementaciones en todo el mundo y usted no tiene control sobre su código fuente.
public class FooSwimmer implements Swim {
public void backStroke() {
System.out.println("Do backstroke");
}
}
Después de 20 años, ha decidido agregar una nueva funcionalidad a la interfaz, pero parece que nuestra interfaz está congelada porque romperá las implementaciones existentes.
Afortunadamente, Java 8 introduce una nueva característica llamada método predeterminado.
Ahora podemos agregar un nuevo método a la interfaz Swim
.
public interface Swim {
void backStroke();
default void sideStroke() {
System.out.println("Default sidestroke implementation. Can be overridden");
}
}
Ahora todas las implementaciones existentes de nuestra interfaz pueden seguir funcionando. Pero lo más importante es que pueden implementar el método recién agregado en su propio tiempo.
Una de las razones más importantes de este cambio, y uno de sus usos más importantes, está en el marco de las Colecciones Java. Oracle no pudo agregar un método foreach
a la interfaz existente de Iterable sin romper todo el código existente que implementó Iterable. Al agregar métodos predeterminados, la implementación existente de Iterable heredará la implementación predeterminada.
Clase, clase abstracta y prioridad de método de interfaz
Las implementaciones en clases, incluidas las declaraciones abstractas, tienen prioridad sobre todos los valores predeterminados de la interfaz.
- El método de clase abstracta tiene prioridad sobre el método predeterminado de interfaz .
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
public void backStroke() {
System.out.println("AbstractSwimmer.backStroke");
}
}
public class FooSwimmer extends AbstractSwimmer {
}
La siguiente declaración
new FooSwimmer().backStroke();
Producirá
AbstractSwimmer.backStroke
- El método de clase tiene prioridad sobre el método predeterminado de la interfaz
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
}
public class FooSwimmer extends AbstractSwimmer {
public void backStroke() {
System.out.println("FooSwimmer.backStroke");
}
}
La siguiente declaración
new FooSwimmer().backStroke();
Producirá
FooSwimmer.backStroke
Método predeterminado colisión de herencia múltiple
Considere el siguiente ejemplo:
public interface A {
default void foo() { System.out.println("A.foo"); }
}
public interface B {
default void foo() { System.out.println("B.foo"); }
}
Aquí hay dos interfaces que declaran default
método default
foo
con la misma firma.
Si intentas extend
estas dos interfaces en la nueva interfaz, debes elegir entre dos, porque Java te obliga a resolver esta colisión explícitamente.
Primero , puede declarar el método foo
con la misma firma que el abstract
, lo que anulará el comportamiento A
y B
public interface ABExtendsAbstract extends A, B {
@Override
void foo();
}
Y cuando implement
ABExtendsAbstract
en la class
deberás proporcionar la implementación foo
:
public class ABExtendsAbstractImpl implements ABExtendsAbstract {
@Override
public void foo() { System.out.println("ABImpl.foo"); }
}
O segundo , puede proporcionar una implementación default
completamente nueva. También puede reutilizar el código de los métodos A
y B
foo
accediendo a los métodos predeterminados reemplazados desde la clase de implementación .
public interface ABExtends extends A, B {
@Override
default void foo() { System.out.println("ABExtends.foo"); }
}
Y cuando implement
ABExtends
en la class
not
tendrá que proporcionar la implementación foo
:
public class ABExtendsImpl implements ABExtends {}