Metodi opzionali nell'interfaccia Java


120

Da quanto ho capito, se si implementa un'interfaccia in java, i metodi specificati in quell'interfaccia devono essere utilizzati dalle sottoclassi che implementano detta interfaccia.

Ho notato che in alcune interfacce come l'interfaccia Collection ci sono metodi commentati come opzionali, ma cosa significa esattamente? Mi ha sconvolto perché pensavo che tutti i metodi specificati nell'interfaccia sarebbero stati richiesti?


A quali metodi ti riferisci? Non riesco a trovarlo nel JavaDoc o nel codice sorgente
dcpomero


Risposte:


232

Sembra esserci un'enorme confusione nelle risposte qui.

Il linguaggio Java richiede che ogni metodo in un'interfaccia sia implementato da ogni implementazione di quell'interfaccia. Periodo. Non ci sono eccezioni a questa regola. Dire "Le collezioni sono un'eccezione" suggerisce una comprensione molto confusa di ciò che sta realmente accadendo qui.

È importante rendersi conto che ci sono una sorta di due livelli di conformità a un'interfaccia:

  1. Cosa può controllare il linguaggio Java. Questo si riduce praticamente a: c'è qualche implementazione per ciascuno dei metodi?

  2. Effettivamente adempiere al contratto. Cioè, l'implementazione fa ciò che la documentazione nell'interfaccia dice che dovrebbe?

    Interfacce ben scritte includeranno documentazione che spiega esattamente cosa ci si aspetta dalle implementazioni. Il tuo compilatore non può verificarlo per te. Devi leggere i documenti e fare quello che dicono. Se non fai quello che dice il contratto, avrai un'implementazione dell'interfaccia per quanto riguarda il compilatore , ma sarà un'implementazione difettosa / non valida.

Durante la progettazione dell'API delle collezioni, Joshua Bloch ha deciso che invece di avere interfacce a grana fine per distinguere tra le diverse varianti delle raccolte (ad esempio: leggibile, scrivibile, ad accesso casuale, ecc.) Avrebbe avuto solo un set di interfacce molto grossolano, principalmente Collection, List, Sete Map, e poi documentare alcune operazioni come "optional". Questo per evitare l'esplosione combinatoria che sarebbe derivata da interfacce a grana fine. Dalle domande frequenti sulla progettazione di API di raccolte Java :

Per illustrare il problema in dettaglio cruento, supponiamo di voler aggiungere la nozione di modificabilità alla Gerarchia. Sono necessarie quattro nuove interfacce: ModifiableCollection, ModifiableSet, ModifiableList e ModifiableMap. Quella che prima era una semplice gerarchia ora è una disordinata eterarchia. Inoltre, è necessaria una nuova interfaccia Iterator da utilizzare con collezioni non modificabili, che non contenga l'operazione di rimozione. Ora puoi eliminare UnsupportedOperationException? Sfortunatamente no.

Considera gli array. Implementano la maggior parte delle operazioni List, ma non rimuovono e aggiungono. Sono elenchi di "dimensione fissa". Se vuoi catturare questa nozione nella gerarchia, devi aggiungere due nuove interfacce: VariableSizeList e VariableSizeMap. Non è necessario aggiungere VariableSizeCollection e VariableSizeSet, perché sarebbero identici a ModifiableCollection e ModifiableSet, ma potresti scegliere di aggiungerli comunque per motivi di coerenza. Inoltre, è necessaria una nuova varietà di ListIterator che non supporta le operazioni di aggiunta e rimozione, per andare insieme a List non modificabile. Ora abbiamo fino a dieci o dodici interfacce, più due nuove interfacce Iterator, invece delle quattro originali. Abbiamo finito? No.

Considera i log (come log degli errori, log di audit e journal per oggetti dati recuperabili). Sono sequenze naturali di sola aggiunta, che supportano tutte le operazioni di List ad eccezione di remove e set (replace). Richiedono una nuova interfaccia principale e un nuovo iteratore.

E che dire delle collezioni immutabili, in contrapposizione a quelle non modificabili? (cioè, collezioni che non possono essere modificate dal client E non cambieranno mai per nessun altro motivo). Molti sostengono che questa sia la distinzione più importante di tutte, perché consente a più thread di accedere contemporaneamente a una raccolta senza la necessità di sincronizzazione. L'aggiunta di questo supporto alla gerarchia dei tipi richiede altre quattro interfacce.

Ora siamo a una ventina di interfacce e cinque iteratori, ed è quasi certo che nella pratica ci sono ancora raccolte che non si adattano perfettamente a nessuna delle interfacce. Ad esempio, le viste di raccolta restituite da Map sono raccolte di sola eliminazione naturale. Inoltre, ci sono raccolte che rifiuteranno determinati elementi sulla base del loro valore, quindi non abbiamo ancora eliminato le eccezioni di runtime.

Quando tutto è stato detto e fatto, abbiamo ritenuto che fosse un valido compromesso ingegneristico eludere l'intero problema fornendo un set molto piccolo di interfacce principali che possono generare un'eccezione di runtime.

Quando i metodi nell'API Collections sono documentati come "operazioni facoltative", non significa che puoi semplicemente lasciare l'implementazione del metodo fuori nell'implementazione, né significa che puoi usare un corpo del metodo vuoto (per prima cosa, molti devono restituire un risultato). Piuttosto, significa che una scelta di implementazione valida (una che è ancora conforme al contratto) è lanciare un file UnsupportedOperationException.

Nota che poiché UnsupportedOperationExceptionè a RuntimeExceptionpuoi lanciarlo da qualsiasi implementazione del metodo, per quanto riguarda il compilatore. Ad esempio, potresti lanciarlo da un'implementazione di Collection.size(). Tuttavia, tale implementazione violerebbe il contratto come la documentazione per Collection.size()non dire che questo è permesso.

A parte: l'approccio utilizzato dalle collezioni API di Java è alquanto controverso (probabilmente meno ora rispetto a quando è stato introdotto per la prima volta, tuttavia). In un mondo perfetto, le interfacce non avrebbero operazioni opzionali e sarebbero invece utilizzate interfacce a grana fine. Il problema è che Java non supporta né i tipi strutturali dedotti né i tipi di intersezione, motivo per cui il tentativo di fare le cose nel "modo giusto" finisce per diventare estremamente ingombrante nel caso delle raccolte.


30
+1 per There are no exceptions to this rule. Chiedendosi perché questa risposta non è contrassegnata come accettata. Altri sono buoni ma hai dato più che abbastanza.
xyz

9
"Il linguaggio Java richiede che ogni metodo in un'interfaccia sia implementato da ogni implementazione di quell'interfaccia. Punto. Non ci sono eccezioni a questa regola." Tranne ... quando ci sono. :-) Le interfacce Java 8 possono specificare un'implementazione del metodo di default, quindi, in Java 8 ... NON è vero che ogni metodo in un'interfaccia deve essere IMPLEMENTATO DA ogni implementazione dell'interfaccia, almeno non nel senso che devi codificare l'implementazione nella classe conrete.
DaBlick

1
@DaBlick Quando ho detto "è implementato da ogni implementazione" non intendevo dire che l'implementazione di tale metodo deve risiedere nel sorgente della classe di implementazione. Anche prima di Java 8, si può ereditare un'implementazione di un metodo di interfaccia, anche da una classe che non implementa detta interfaccia. es: crea Fooche non implementa Runnablecon metodo pubblico void run(). Ora crea una classe Barche extends Fooe implements Runnablesenza esagerare run. Implementa ancora il metodo, anche se indirettamente. Allo stesso modo, un'implementazione del metodo predefinito è ancora un'implementazione.
Laurence Gonsalves

Scuse. Non stavo cercando di essere pedanticamente critico quanto di attirare l'attenzione su una funzionalità di Java 8 che potrebbe essere rilevante per il post originale. In Java 8, ora hai la possibilità di avere implementazioni che non sono codificate in NESSUNA superclasse o sottoclasse. Questo (IMHO) apre un nuovo mondo di modelli di progettazione, inclusi alcuni che potrebbero essere appropriati nei casi in cui il divieto di ereditarietà multipla avrebbe potuto presentare alcune sfide. Penso che questo genererà una nuova serie di modelli di design molto utili. \
DaBlick

3
@AndrewS perché in Java 8 è removestata fornita un'implementazione predefinita. Se non lo implementi, la tua classe ottiene l'implementazione predefinita. Gli altri due metodi menzionati non hanno implementazioni predefinite.
Laurence Gonsalves

27

Per compilare una classe di implementazione (non astratta) per un'interfaccia, tutti i metodi devono essere implementati.

Tuttavia , se pensiamo a un metodo la cui implementazione è una semplice eccezione come "non implementato" (come alcuni metodi Collectionnell'interfaccia), l' Collectioninterfaccia è l'eccezione in questo caso, non il caso normale. Di solito , l'implementazione della classe dovrebbe (e lo farà) implementare tutti i metodi.

L '"opzionale" nella raccolta significa che la classe di implementazione non deve' implementarlo '(secondo la terminologia sopra), e verrà semplicemente lanciata NotSupportedException).

Un buon add()metodo di esempio per raccolte immutabili: il calcestruzzo implementerà semplicemente un metodo che non fa altro che lanciareNotSupportedException

Nel caso in Collectioncui venga fatto per prevenire alberi di ereditarietà disordinati, ciò renderà i programmatori infelici - ma per la maggior parte dei casi, questo paradigma non è consigliato e dovrebbe essere evitato se possibile.


Aggiornare:

A partire da Java 8, è stato introdotto un metodo predefinito .

Ciò significa che un'interfaccia può definire un metodo, inclusa la sua implementazione.
Questo è stato aggiunto per consentire l'aggiunta di funzionalità alle interfacce, pur supportando la compatibilità con le versioni precedenti per parti di codice che non richiedono la nuova funzionalità.

Si noti che il metodo è ancora implementato da tutte le classi che lo dichiarano, ma utilizzando la definizione dell'interfaccia.


Invece di "non fare casini", penso che sia più di "è proprio così che è".

@pst: credo che quello che stavano pensando i designer quando lo implementavano in primo luogo, ma non ho modo di saperlo con certezza. Penso che qualsiasi approccio diverso creerebbe solo un pasticcio, ma ancora una volta potrebbe essere sbagliato. Il punto che stavo cercando di mostrare qui è: questo esempio è l'eccezione, non il solito - e sebbene a volte potrebbe essere utile - per il caso generale - dovrebbe essere evitato, se possibile.
amit

12
"non deve implementarlo (probabilmente creerà solo un metodo che genera ...)". Questo è implementare il metodo.
Marchese di Lorne

1
Poiché questa purtroppo è stata la risposta accettata, suggerirei di riscriverla. La " Normalmente , la classe di implementazione dovrebbe (e implementerà) tutti i metodi" è fuorviante come EJP ha già sottolineato.
Alberto

2
L '"opzionale" nella raccolta significa che la classe di implementazione non deve implementarlo. "- questo è semplicemente falso. Con" non deve implementare "intendi qualcos'altro.
djechlin

19

Un'interfaccia in Java dichiara semplicemente il contratto per l'implementazione delle classi. Tutti i metodi in quell'interfaccia devono essere implementati, ma le classi di implementazione sono libere di lasciarli non implementati, cioè vuoti. Ad esempio artificioso,

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

Ora non l'ho doSomethingElse()implementato, lasciandolo libero di implementare le mie sottoclassi. Questo è facoltativo.

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

Tuttavia, se stai parlando di interfacce Collection, come altri hanno detto, sono un'eccezione. Se alcuni metodi non vengono implementati e li chiami, potrebbero generare UnsupportedOperationExceptioneccezioni.


Potrei baciarti amico mio.
Micro

16

I metodi opzionali nell'interfaccia Collection indicano che l'implementazione del metodo può generare un'eccezione, ma deve essere implementata comunque. Come specificato nei documenti :

Alcune implementazioni di raccolte hanno limitazioni sugli elementi che possono contenere. Ad esempio, alcune implementazioni vietano gli elementi nulli e alcune hanno restrizioni sui tipi dei loro elementi. Il tentativo di aggiungere un elemento non idoneo genera un'eccezione non controllata, in genere NullPointerException o ClassCastException. Il tentativo di interrogare la presenza di un elemento non idoneo può generare un'eccezione o semplicemente restituire false; alcune implementazioni mostreranno il primo comportamento e alcune mostreranno il secondo. Più in generale, il tentativo di un'operazione su un elemento non idoneo il cui completamento non comporterebbe l'inserimento di un elemento non idoneo nella raccolta può generare un'eccezione o può avere successo, a discrezione dell'implementazione. Tali eccezioni sono contrassegnate come "facoltative"


Non ho mai capito cosa intendessero i javadoc per facoltativo. Credo che intendessero come hai detto tu. Ma la maggior parte dei metodi sono opzionali in base a quello standard new Runnable ( ) { @ Override public void run ( ) { throw new UnsupportedOperationException ( ) ; } }:;
emory

Questo non sembra applicarsi ai metodi opzionali , ma piuttosto che, ad esempio, add((T)null)può essere valido in un caso ma non in un altro. Cioè, questo parla di eccezioni / comportamenti opzionali e per argomenti ("restrizioni sugli elementi" ... "elemento non idoneo" ... "eccezioni contrassegnate come opzionali") e non affronta metodi opzionali .

9

Tutti i metodi devono essere implementati per la compilazione del codice (a parte quelli con defaultimplementazioni in Java 8+), ma l'implementazione non deve fare nulla di utile dal punto di vista funzionale. Nello specifico:

  • Può essere vuoto (un metodo vuoto).
  • Può semplicemente lanciare un UnsupportedOperationException(o simile)

Quest'ultimo approccio viene spesso adottato nelle classi di raccolta: tutti i metodi sono ancora implementati, ma alcuni potrebbero generare un'eccezione se chiamati in fase di esecuzione.


5

In effetti, sono ispirato da SurfaceView.Callback2. Penso che questo sia il modo ufficiale

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

Se la tua classe non ha bisogno di implementare metodi opzionali, "implementa Callback". Se la tua classe ha bisogno di implementare metodi opzionali, "implementa CallbackExtended".

Scusa per la merda inglese.


5

In Java 8 e versioni successive, la risposta a questa domanda è ancora valida ma ora è più sfumata.

Innanzitutto, queste affermazioni dalla risposta accettata rimangono corrette:

  • le interfacce hanno lo scopo di specificare i loro comportamenti impliciti in un contratto (una dichiarazione di regole per il comportamento che le classi di implementazione devono obbedire per essere considerate valide)
  • c'è una distinzione tra contratto (regole) e implementazione (codifica programmatica delle regole)
  • i metodi specificati nell'interfaccia DEVONO SEMPRE essere implementati (ad un certo punto)

Quindi, qual è la sfumatura di nuovo in Java 8? Quando si parla di "Metodi opzionali", uno dei seguenti è ora appropriato:

1. Un metodo la cui implementazione è contrattualmente facoltativa

La "terza affermazione" dice che i metodi di interfaccia astratti devono essere sempre implementati e questo rimane vero in Java 8+. Tuttavia, come nel Java Collections Framework, è possibile descrivere alcuni metodi di interfaccia astratti come "opzionali" nel contratto.

In questo caso, l'autore che sta implementando l'interfaccia può scegliere di non implementare il metodo. Il compilatore insisterà su un'implementazione, tuttavia, e quindi l'autore usa questo codice per tutti i metodi opzionali che non sono necessari nella particolare classe di implementazione:

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

In Java 7 e versioni precedenti, questo era davvero l'unico tipo di "metodo opzionale" disponibile, ovvero un metodo che, se non implementato, generava un'eccezione UnsupportedOperationException. Questo comportamento è necessariamente specificato dal contratto dell'interfaccia (ad es. I metodi di interfaccia opzionali di Java Collections Framework).

2. Un metodo predefinito la cui reimplementazione è facoltativa

Java 8 ha introdotto il concetto di metodi predefiniti . Si tratta di metodi la cui implementazione può essere ed è fornita dalla definizione dell'interfaccia stessa. In genere è possibile fornire metodi predefiniti solo quando il corpo del metodo può essere scritto utilizzando altri metodi di interfaccia (ad esempio, le "primitive") e quando thispuò significare "questo oggetto la cui classe ha implementato questa interfaccia".

Un metodo predefinito deve soddisfare il contratto dell'interfaccia (proprio come qualsiasi altra implementazione del metodo di interfaccia deve). Pertanto, specificare un'implementazione del metodo di interfaccia in una classe di implementazione è a discrezione dell'autore (purché il comportamento sia adatto al suo scopo).

In questo nuovo ambiente, il Java Collections Framework potrebbe essere riscritto come:

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

In questo modo, il metodo "opzionale" add()ha il comportamento predefinito di lanciare un'eccezione UnsupportedOperationException se la classe di implementazione non fornisce alcun nuovo comportamento, che è esattamente quello che vorresti che accadesse e che è conforme al contratto per List. Se un autore sta scrivendo una classe che non consente l'aggiunta di nuovi elementi a un'implementazione di List, l'implementazione di add()è facoltativa perché il comportamento predefinito è esattamente ciò che è necessario.

In questo caso, la "terza affermazione" di cui sopra è ancora vera, perché il metodo è stato implementato nell'interfaccia stessa.

3. Un metodo che restituisce un Optionalrisultato

L'ultimo nuovo tipo di metodo opzionale è semplicemente un metodo che restituisce un file Optional. La Optionalclasse fornisce un modo decisamente più orientato agli oggetti di trattare i nullrisultati.

In uno stile di programmazione fluente, come il tipo comunemente visto quando si codifica con la nuova API Java Streams, un risultato nullo in qualsiasi momento causa l'arresto anomalo del programma con una NullPointerException. La Optionalclasse fornisce un meccanismo per restituire risultati nulli al codice client in un modo che consente lo stile fluente senza causare il blocco del codice client.


4

Se esaminiamo il codice di AbstractCollection.java in grepCode, che è una classe antenata per tutte le implementazioni della raccolta, ci aiuterà a capire il significato dei metodi opzionali. Ecco il codice per il metodo add (e) nella classe AbstractCollection. aggiungere (e) il metodo è facoltativa conformemente raccolta interfaccia

public boolean  add(E e) {

        throw new UnsupportedOperationException();
    } 

Il metodo facoltativo significa che è già implementato nelle classi antenate e genera UnsupportedOperationException al momento del richiamo. Se vogliamo rendere la nostra raccolta modificabile, dovremmo sovrascrivere i metodi opzionali nell'interfaccia della raccolta.


4

Bene, questo argomento è stato affrontato ... sì ... ma pensa, manca una risposta. Sto parlando dei "metodi predefiniti" delle interfacce. Ad esempio, immaginiamo di avere una classe per chiudere qualsiasi cosa (come un distruttore o qualcosa del genere). Diciamo che dovrebbe avere 3 metodi. Chiamiamoli "doFirst ()", "doLast ()" e "onClose ()".

Quindi diciamo che vogliamo che qualsiasi oggetto di quel tipo realizzi almeno "onClose ()", ma gli altri sono opzionali.

Puoi rendertene conto, usando i "metodi predefiniti" delle interfacce. Lo so, la maggior parte delle volte negherebbe la ragione di un'interfaccia, ma se stai progettando un framework, questo può essere utile.

Quindi, se vuoi realizzarlo in questo modo, sarebbe il seguente

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

Cosa succederebbe ora, se ad esempio lo implementassi in una classe chiamata "Test", il compilatore starebbe perfettamente con quanto segue:

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

con l'uscita:

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 l'uscita:

first ... closing ... done!

Sono possibili tutte le combinazioni. Qualunque cosa con "default" può essere implementata, ma non lo deve, tuttavia nulla senza deve essere implementato.

Spero non sia completamente sbagliato che ora risponda.

Buona giornata a tutti!

[edit1]: Nota: funziona solo in Java 8.


Sì, scusa, ho dimenticato di dirlo ... Dovrebbe essere modificato ora.
Thorben Kuck

1

Stavo cercando un modo per implementare l'interfaccia di richiamata, quindi l'implementazione di metodi opzionali era necessaria poiché non volevo implementare tutti i metodi per ogni richiamata.

Quindi, invece di usare un'interfaccia, ho usato una classe con un'implementazione vuota come:

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

E puoi impostare la variabile membro CallBack in questo modo,

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

allora chiamalo così.

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

In questo modo, non dovrai preoccuparti di implementare tutti i metodi per richiamata, ma sovrascrivi solo quelli di cui hai bisogno.


0

Sebbene non risponda alla domanda dell'OP, vale la pena notare che a partire da Java 8 l'aggiunta di metodi predefiniti alle interfacce è di fatto fattibile . La defaultparola chiave inserita nella firma del metodo di un'interfaccia darà come risultato una classe con l'opzione di sovrascrivere il metodo, ma non richiederlo.


0

Tutorial sulle raccolte Java di Oracle:

Per mantenere gestibile il numero di interfacce di raccolta principali, la piattaforma Java non fornisce interfacce separate per ciascuna variante di ciascun tipo di raccolta. (Tali varianti potrebbero includere immutabile, dimensione fissa e solo aggiunta.) Invece, le operazioni di modifica in ciascuna interfaccia sono designate come opzionali: una data implementazione può scegliere di non supportare tutte le operazioni. Se viene richiamata un'operazione non supportata, una raccolta genera un'eccezione UnsupportedOperationException . Le implementazioni sono responsabili della documentazione delle operazioni opzionali supportate. Tutte le implementazioni generiche della piattaforma Java supportano tutte le operazioni facoltative.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.