Métodos opcionales en la interfaz Java


120

Según tengo entendido, si implementa una interfaz en java, los métodos especificados en esa interfaz deben ser utilizados por las subclases que implementan dicha interfaz.

He notado que en algunas interfaces, como la interfaz de Colección, hay métodos que se comentan como opcionales, pero ¿qué significa esto exactamente? ¿Me ha confundido un poco, ya que pensé que se requerirían todos los métodos especificados en la interfaz?


¿A qué métodos te refieres? No puedo encontrarlo en JavaDoc o en el código fuente
dcpomero


Respuestas:


232

Parece haber mucha confusión en las respuestas aquí.

El lenguaje Java requiere que cada método en una interfaz sea implementado por cada implementación de esa interfaz. Período. No hay excepciones para esta regla. Decir "Las colecciones son una excepción" sugiere una comprensión muy vaga de lo que realmente está sucediendo aquí.

Es importante darse cuenta de que hay dos niveles de conformidad con una interfaz:

  1. Lo que puede comprobar el lenguaje Java. Esto básicamente se reduce a: ¿hay alguna implementación para cada uno de los métodos?

  2. Realmente cumpliendo el contrato. Es decir, ¿la implementación hace lo que la documentación de la interfaz dice que debería?

    Las interfaces bien escritas incluirán documentación que explique exactamente lo que se espera de las implementaciones. Su compilador no puede verificar esto por usted. Necesita leer los documentos y hacer lo que dicen. Si no hace lo que dice el contrato, tendrá una implementación de la interfaz en lo que respecta al compilador , pero será una implementación defectuosa / no válida.

Al diseñar la API de colecciones, Joshua Bloch decidió que, en lugar de tener interfaces muy precisas para distinguir entre diferentes variantes de colecciones (p. Ej., Legibles, de escritura, de acceso aleatorio, etc.), solo tendría un conjunto de interfaces muy burdas, principalmente Collection, List, Sety Map, a continuación, documentar ciertas operaciones como "opcional". Esto fue para evitar la explosión combinatoria que resultaría de interfaces de grano fino. De las preguntas frecuentes sobre el diseño de API de Java Collections :

Para ilustrar el problema con detalles sangrientos, suponga que desea agregar la noción de modificabilidad a la Jerarquía. Necesita cuatro interfaces nuevas: ModifiableCollection, ModifiableSet, ModifiableList y ModifiableMap. Lo que antes era una jerarquía simple ahora es una jerarquía desordenada. Además, necesita una nueva interfaz de iterador para usar con colecciones no modificables, que no contiene la operación de eliminación. ¿Ahora puede eliminar UnsupportedOperationException? Lamentablemente no.

Considere las matrices. Implementan la mayoría de las operaciones de lista, pero no eliminan ni agregan. Son listas de "tamaño fijo". Si desea capturar esta noción en la jerarquía, debe agregar dos nuevas interfaces: VariableSizeList y VariableSizeMap. No tiene que agregar VariableSizeCollection y VariableSizeSet, porque serían idénticos a ModifiableCollection y ModifiableSet, pero puede optar por agregarlos de todos modos por motivos de coherencia. Además, necesita una nueva variedad de ListIterator que no admita las operaciones de agregar y quitar, para ir junto con List no modificable. Ahora tenemos hasta diez o doce interfaces, más dos nuevas interfaces Iterator, en lugar de las cuatro originales. ¿Terminamos? No.

Tenga en cuenta los registros (como los registros de errores, los registros de auditoría y los diarios para objetos de datos recuperables). Son secuencias naturales de solo adición, que admiten todas las operaciones de lista, excepto para eliminar y establecer (reemplazar). Requieren una nueva interfaz central y un nuevo iterador.

¿Y qué hay de las colecciones inmutables, a diferencia de las inmodificables? (es decir, Colecciones que el cliente no puede cambiar Y que nunca cambiarán por ningún otro motivo). Muchos argumentan que esta es la distinción más importante de todas, porque permite que varios subprocesos accedan a una colección al mismo tiempo sin necesidad de sincronización. Agregar este soporte a la jerarquía de tipos requiere cuatro interfaces más.

Ahora tenemos hasta veinte interfaces y cinco iteradores, y es casi seguro que todavía hay colecciones que surgen en la práctica que no encajan perfectamente en ninguna de las interfaces. Por ejemplo, las vistas de colección devueltas por Map son colecciones naturales de solo eliminación. Además, hay colecciones que rechazarán ciertos elementos en función de su valor, por lo que aún no hemos eliminado las excepciones en tiempo de ejecución.

Cuando todo estuvo dicho y hecho, sentimos que era un compromiso de ingeniería sólido eludir todo el problema al proporcionar un conjunto muy pequeño de interfaces centrales que pueden generar una excepción de tiempo de ejecución.

Cuando los métodos en la API de colecciones se documentan como "operaciones opcionales", no significa que puede dejar la implementación del método fuera de la implementación, ni significa que puede usar un cuerpo de método vacío (por un lado, muchos de necesitan devolver un resultado). Más bien, significa que una opción de implementación válida (una que aún cumple con el contrato) es lanzar un UnsupportedOperationException.

Tenga en cuenta que debido a que UnsupportedOperationExceptiones un RuntimeException, puede lanzarlo desde cualquier implementación de método, en lo que respecta al compilador. Por ejemplo, podría lanzarlo desde una implementación de Collection.size(). Sin embargo, tal implementación violaría el contrato ya que la documentación para Collection.size()no dice que esto esté permitido.

Aparte: el enfoque utilizado por la API de colecciones de Java es algo controvertido (probablemente menos ahora que cuando se introdujo por primera vez, sin embargo). En un mundo perfecto, las interfaces no tendrían operaciones opcionales, y en su lugar se usarían interfaces de grano fino. El problema es que Java no admite tipos estructurales inferidos ni tipos de intersección, por lo que intentar hacer las cosas de la "manera correcta" termina siendo extremadamente difícil de manejar en el caso de las colecciones.


30
+1 para There are no exceptions to this rule. Preguntándose por qué esta respuesta no está marcada como aceptada. Otros son buenos pero has dado más que suficiente.
xyz

9
"El lenguaje Java requiere que cada método en una interfaz sea implementado por cada implementación de esa interfaz. Punto. No hay excepciones a esta regla". Excepto ... cuando los hay. :-) Las interfaces Java 8 pueden especificar una implementación de método predeterminada, Por lo tanto, en Java 8 ... NO es cierto que cada método en una interfaz deba ser IMPLEMENTADO POR cada implementación de la interfaz, al menos no en el sentido en que debe codifique la implementación en la clase conrete.
DaBlick

1
@DaBlick Cuando dije "es implementado por cada implementación" no quise decir que dicha implementación de método debe residir en la fuente de la clase de implementación. Incluso antes de Java 8, se puede heredar una implementación de un método de interfaz, incluso de una clase que no implementa dicha interfaz. por ejemplo: crear Fooque no se implementa Runnablecon el método público void run(). Ahora crea una clase Barque extends Fooy implements Runnablesin sobrepasar run. Todavía implementa el método, aunque indirectamente. Asimismo, la implementación de un método predeterminado sigue siendo una implementación.
Laurence Gonsalves

Disculpas No estaba tratando de ser pedantemáticamente crítico sino de llamar la atención sobre una característica de Java 8 que podría ser relevante para la publicación original. En Java 8, ahora tiene la opción de tener implementaciones que no están codificadas en NINGUNA superclase ni subclase. Esto (en mi humilde opinión) abre un nuevo mundo de patrones de diseño, incluidos algunos que pueden ser apropiados en los casos en que la prohibición de la herencia múltiple podría haber presentado algunos desafíos. Creo que esto generará un nuevo conjunto de patrones de diseño muy útiles. \
DaBlick

3
@AndrewS porque en Java 8 removese le dio una implementación predeterminada. Si no lo implementa, su clase obtiene la implementación predeterminada. Los otros dos métodos que menciona no tienen implementaciones predeterminadas.
Laurence Gonsalves

27

Para compilar una clase de implementación (no abstracta) para una interfaz, se deben implementar todos los métodos.

Sin embargo , si pensamos en un método cuya implementación es una simple excepción lanzada como 'no implementado' (como algunos métodos en la Collectioninterfaz), entonces la Collectioninterfaz es la excepción en este caso, no el caso normal. Por lo general , la clase de implementación debería (y lo hará) implementar todos los métodos.

El "opcional" en la colección significa que la clase de implementación no tiene que 'implementar' (de acuerdo con la terminología anterior), y simplemente arrojará NotSupportedException).

Un buen ejemplo: add()método para colecciones inmutables: el concreto simplemente implementará un método que no hace más que arrojarNotSupportedException

En el caso de Collectionque se haga para evitar árboles de herencia desordenados, eso hará que los programadores se sientan miserables, pero en la mayoría de los casos, este paradigma no se recomienda y debe evitarse si es posible.


Actualizar:

A partir de Java 8, se introdujo un método predeterminado .

Eso significa que una interfaz puede definir un método, incluida su implementación.
Esto se agregó para permitir agregar funcionalidad a las interfaces, sin dejar de admitir la compatibilidad con versiones anteriores de fragmentos de código que no necesitan la nueva funcionalidad.

Tenga en cuenta que el método todavía lo implementan todas las clases que lo declaran, pero utilizando la definición de la interfaz.


En lugar de "no estropear", creo que es más "así es como es".

@pst: Creo que lo que los diseñadores estaban pensando al implementarlo en primer lugar, pero no tengo forma de saberlo con certeza. Creo que cualquier enfoque diferente crearía un lío, pero de nuevo, podría estar equivocado. El punto que estaba tratando de mostrar aquí es: este ejemplo es la excepción, no lo habitual, y aunque a veces puede ser útil, para el caso general, debe evitarse, si es posible.
amit

12
"no tiene que implementarlo (probablemente solo creará un método que arroje ...)". Eso es implementar el método.
Marqués de Lorne

1
Como, lamentablemente, esta ha sido la respuesta aceptada, sugiero que la reescriba. El ' Por lo general , la clase de implementación debería (y lo hará) implementar todos los métodos' es engañoso, como ya señaló EJP .
Alberto

2
El "opcional" en la colección significa que la clase de implementación no tiene que implementarlo. "- esto es simplemente falso. Con" no tiene que implementar "te refieres a otra cosa.
djechlin

19

Una interfaz en Java simplemente declara el contrato para implementar clases. Todos los métodos en esa interfaz deben implementarse, pero las clases de implementación son libres de dejarlos sin implementar, es decir, en blanco. Como ejemplo artificial,

interface Foo {
  void doSomething();
  void doSomethingElse();
}

class MyClass implements Foo {
  public void doSomething() {
     /* All of my code goes here */
  }

  public void doSomethingElse() {
    // I leave this unimplemented
  }
}

Ahora lo he dejado doSomethingElse()sin implementar, dejándolo libre para que lo implementen mis subclases. Eso es opcional.

class SubClass extends MyClass {
    @Override
    public void doSomethingElse() {
      // Here's my implementation. 
    }
}

Sin embargo, si se trata de interfaces de colección, como han dicho otros, son una excepción. Si ciertos métodos no se implementan y los llama, pueden generar UnsupportedOperationExceptionexcepciones.


Podría besarte mi amigo.
Micro

16

Los métodos opcionales en la interfaz Collection significan que la implementación del método puede generar una excepción, pero debe implementarse de todos modos. Como se especifica en los documentos :

Algunas implementaciones de colecciones tienen restricciones sobre los elementos que pueden contener. Por ejemplo, algunas implementaciones prohíben los elementos nulos y algunas tienen restricciones sobre los tipos de sus elementos. Intentar agregar un elemento inelegible arroja una excepción sin marcar, generalmente NullPointerException o ClassCastException. Intentar consultar la presencia de un elemento no elegible puede generar una excepción, o simplemente puede devolver falso; algunas implementaciones exhibirán el comportamiento anterior y algunas exhibirán el segundo. De manera más general, intentar una operación en un elemento no elegible cuya finalización no daría lugar a la inserción de un elemento no elegible en la colección puede generar una excepción o puede tener éxito, a opción de la implementación. Estas excepciones están marcadas como "opcionales".


Nunca entendí realmente lo que los javadocs querían decir con opcional. Creo que querían decir lo que dijiste. Pero la mayoría de los métodos son opcionales según ese estándar new Runnable ( ) { @ Override public void run ( ) { throw new UnsupportedOperationException ( ) ; } }:;
emory

Esto no parece aplicarse a los métodos opcionales , sino que, por ejemplo, add((T)null)puede ser válido en un caso pero no en otro. Es decir, habla de excepciones / comportamientos opcionales y de argumentos ("restricciones sobre elementos" ... "elemento no elegible" ... "excepciones marcadas como opcionales") y no aborda métodos opcionales .

9

Todos los métodos deben implementarse para que el código se compile (aparte de aquellos con defaultimplementaciones en Java 8+), pero la implementación no tiene que hacer nada funcionalmente útil. Específicamente, él:

  • Puede estar en blanco (un método vacío).
  • Puede simplemente lanzar un UnsupportedOperationException(o similar)

El último enfoque a menudo se toma en las clases de colección: todos los métodos aún se implementan, pero algunos pueden lanzar una excepción si se llaman en tiempo de ejecución.


5

De hecho, me inspira SurfaceView.Callback2. Creo que esta es la forma oficial

public class Foo {
    public interface Callback {
        public void requiredMethod1();
        public void requiredMethod2();
    }

    public interface CallbackExtended extends Callback {
        public void optionalMethod1();
        public void optionalMethod2();
    }

    private Callback mCallback;
}

Si su clase no necesita implementar métodos opcionales, simplemente "implementa Callback". Si su clase necesita implementar métodos opcionales, simplemente "implementa CallbackExtended".

Perdón por la mierda en inglés.


5

En Java 8 y versiones posteriores, la respuesta a esta pregunta sigue siendo válida, pero ahora tiene más matices.

Primero, estas declaraciones de la respuesta aceptada siguen siendo correctas:

  • las interfaces están destinadas a especificar sus comportamientos implícitos en un contrato (una declaración de reglas de comportamiento que las clases de implementación deben obedecer para ser consideradas válidas)
  • hay una distinción entre el contrato (reglas) y la implementación (codificación programática de las reglas)
  • Los métodos especificados en la interfaz SIEMPRE DEBEN implementarse (en algún momento)

Entonces, ¿cuál es el matiz nuevo en Java 8? Cuando se habla de "métodos opcionales", cualquiera de los siguientes es ahora apto:

1. Un método cuya implementación es contractualmente opcional

La "tercera declaración" dice que los métodos de interfaz abstracta siempre deben implementarse y esto sigue siendo cierto en Java 8+. Sin embargo, como en Java Collections Framework, es posible describir algunos métodos abstractos de interfaz como "opcionales" en el contrato.

En este caso, el autor que está implementando la interfaz puede optar por no implementar el método. Sin embargo, el compilador insistirá en una implementación, por lo que el autor usa este código para cualquier método opcional que no sea necesario en la clase de implementación en particular:

public SomeReturnType optionalInterfaceMethodA(...) {
    throw new UnsupportedOperationException();
}

En Java 7 y versiones anteriores, este era realmente el único tipo de "método opcional" que existía, es decir, un método que, si no se implementaba, arrojaba una UnsupportedOperationException. Este comportamiento está necesariamente especificado por el contrato de interfaz (por ejemplo, los métodos de interfaz opcionales de Java Collections Framework).

2. Un método predeterminado cuya reimplementación es opcional

Java 8 introdujo el concepto de métodos predeterminados . Estos son métodos cuya implementación puede ser proporcionada por la propia definición de interfaz. Generalmente, solo es posible proporcionar métodos predeterminados cuando el cuerpo del método se puede escribir usando otros métodos de interfaz (es decir, las "primitivas"), y cuando thispuede significar "este objeto cuya clase ha implementado esta interfaz".

Un método predeterminado debe cumplir con el contrato de la interfaz (como debe hacerlo cualquier otra implementación de método de interfaz). Por lo tanto, especificar una implementación del método de interfaz en una clase de implementación queda a discreción del autor (siempre que el comportamiento sea adecuado para su propósito).

En este nuevo entorno, Java Collections Framework podría reescribirse como:

public interface List<E> {
    :
    :
    default public boolean add(E element) {
        throw new UnsupportedOperationException();
    }
    :
    :
}

De esta manera, el método "opcional" add()tiene el comportamiento predeterminado de lanzar una UnsupportedOperationException si la clase de implementación no proporciona un comportamiento nuevo propio, que es exactamente lo que le gustaría que sucediera y que cumple con el contrato de List. Si un autor está escribiendo una clase que no permite agregar nuevos elementos a una implementación de Lista, la implementación de add()es opcional porque el comportamiento predeterminado es exactamente lo que se necesita.

En este caso, la "tercera declaración" anterior sigue siendo cierta, porque el método se ha implementado en la propia interfaz.

3. Un método que devuelve un Optionalresultado

El último tipo nuevo de método opcional es simplemente un método que devuelve un Optional. La Optionalclase proporciona una forma decididamente más orientada a objetos de tratar los nullresultados.

En un estilo de programación fluido, como el que se ve comúnmente al codificar con la nueva API de Java Streams, un resultado nulo en cualquier punto hace que el programa se bloquee con una NullPointerException. La Optionalclase proporciona un mecanismo para devolver resultados nulos al código del cliente de una manera que habilita el estilo fluido sin que el código del cliente se bloquee.


4

Si revisamos el código de AbstractCollection.java en grepCode, que es una clase antecesora para todas las implementaciones de colecciones, nos ayudará a comprender el significado de los métodos opcionales. Aquí está el código para el método add (e) en la clase AbstractCollection. El método add (e) es opcional según la interfaz de recopilación

public boolean  add(E e) {

        throw new UnsupportedOperationException();
    } 

El método opcional significa que ya está implementado en clases ancestrales y arroja UnsupportedOperationException al ser invocado. Si queremos que nuestra colección sea modificable, entonces debemos anular los métodos opcionales en la interfaz de la colección.


4

Bueno, este tema se ha abordado ... sí ... pero piense, falta una respuesta. Estoy hablando de los "métodos predeterminados" de las interfaces. Por ejemplo, imaginemos que tendrá una clase para cerrar cualquier cosa (como un destructor o algo así). Digamos que debería tener 3 métodos. Llamémoslos "doFirst ()", "doLast ()" y "onClose ()".

Entonces decimos que queremos que cualquier objeto de ese tipo al menos se dé cuenta de "onClose ()", pero los otros son opcionales.

Puede darse cuenta de eso, utilizando los "Métodos predeterminados" de las interfaces. Lo sé, la mayoría de las veces esto anularía la razón de una interfaz, pero si está diseñando un marco, esto puede ser útil.

Entonces, si desea realizarlo de esta manera, se vería como sigue

public interface Closer {
    default void doFirst() {
        System.out.print("first ... ");
    }
    void onClose();
    default void doLast() {
        System.out.println("and finally!");
    }
}

¿Qué pasaría ahora? Si, ​​por ejemplo, lo implementaras en una clase llamada "Prueba", el compilador estaría perfectamente bien con lo siguiente:

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }
}

con la Salida:

first ... closing ... and finally!

o

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }

    @Override
    public void doLast() {
        System.out.println("done!");
    }
}

con la salida:

first ... closing ... done!

Todas las combinaciones son posibles. Todo lo que tenga "predeterminado" se puede implementar, pero no se debe implementar, sin embargo, se debe implementar cualquier cosa que no tenga.

Espero que no sea del todo incorrecto que ahora responda.

¡Que tengas un gran día a todos!

[edit1]: Nota: esto solo funciona en Java 8.


Sí, lo siento, olvidé mencionar esto ... Debería editarse ahora.
Thorben Kuck

1

Estaba buscando una forma de implementar la interfaz de devolución de llamada, por lo que era necesario implementar métodos opcionales, ya que no quería implementar todos los métodos para cada devolución de llamada.

Entonces, en lugar de usar una interfaz, usé una clase con una implementación vacía como:

public class MyCallBack{
    public void didResponseCameBack(String response){}
}

Y puede configurar la variable miembro CallBack de esta manera,

c.setCallBack(new MyCallBack() {
    public void didResponseCameBack(String response) {
        //your implementation here
    }
});

entonces llámalo así.

if(mMyCallBack != null) {
    mMyCallBack.didResponseCameBack(response);
}

De esta manera, no tendrá que preocuparse por implementar todos los métodos por devolución de llamada, sino solo anular los que necesita.


0

A pesar de que no responde a la pregunta de la OP, vale la pena señalar que a partir del 8 de Java añadiendo métodos predeterminados a las interfaces de hecho es factible . La defaultpalabra clave colocada en la firma del método de una interfaz dará como resultado que una clase tenga la opción de anular el método, pero no lo requiera.


0

Tutorial de colecciones Java de Oracle:

Para mantener manejable el número de interfaces de colección principales, la plataforma Java no proporciona interfaces separadas para cada variante de cada tipo de colección. (Dichas variantes pueden incluir inmutable, de tamaño fijo y solo para agregar). En cambio, las operaciones de modificación en cada interfaz se designan como opcionales; una implementación determinada puede optar por no admitir todas las operaciones. Si se invoca una operación no admitida , una colección genera una UnsupportedOperationException . Las implementaciones son responsables de documentar cuáles de las operaciones opcionales admiten. Todas las implementaciones de propósito general de la plataforma Java admiten todas las operaciones opcionales.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.