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.

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

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 {}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow