Metodi predefiniti di Java 8 come tratti: sicuro?


116

È una pratica sicura utilizzare i metodi predefiniti come versione umana dei tratti in Java 8?

Alcuni sostengono che potrebbe rendere i panda tristi se li usi solo per il gusto di farlo, perché è bello, ma non è mia intenzione. Spesso viene anche ricordato che i metodi predefiniti sono stati introdotti per supportare l'evoluzione delle API e la compatibilità con le versioni precedenti, il che è vero, ma questo non rende sbagliato o distorto usarli come tratti di per sé.

Ho in mente il seguente caso d'uso pratico :

public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

O forse, definisci un PeriodTrait:

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

È ammesso che si possa usare la composizione (o anche classi di aiuto) ma sembra più prolissa e disordinata e non consente di beneficiare del polimorfismo.

Quindi, è corretto / sicuro utilizzare metodi predefiniti come tratti di base o dovrei essere preoccupato per effetti collaterali imprevisti?

Diverse domande su SO sono legate ai tratti Java vs Scala; non è questo il punto qui. Non sto nemmeno chiedendo solo opinioni. Invece, cerco una risposta autorevole o almeno un approfondimento sul campo: se hai utilizzato metodi predefiniti come tratti del tuo progetto aziendale, si è rivelata una bomba a orologeria?


Mi sembra che potresti ottenere gli stessi vantaggi ereditando una classe astratta e non devi preoccuparti di far piangere i panda ... L'unico motivo per cui posso vedere per usare un metodo predefinito in un'interfaccia è che hai bisogno della funzionalità e non puoi modificare un mucchio di codice legacy basato sull'interfaccia.
Deven Phillips

1
Sono d'accordo con @ infosec812 sull'estensione di una classe astratta che definisce il proprio campo logger statico. Il tuo metodo logger () non istanzerebbe una nuova istanza logger ogni volta che viene chiamato?
Eidan Spiegel

Per la registrazione, potresti voler guardare Projectlombok.org e la loro annotazione @ Slf4j.
Deven Phillips

Risposte:


120

La risposta breve è: è sicuro se li usi in modo sicuro :)

La risposta snarky: dimmi che cosa si intende per tratti, e forse ti darò una risposta migliore :)

In tutta serietà, il termine "tratto" non è ben definito. Molti sviluppatori Java hanno più familiarità con i tratti così come sono espressi in Scala, ma Scala è ben lungi dall'essere il primo linguaggio ad avere tratti, sia nel nome che negli effetti.

Ad esempio, in Scala, i tratti sono stateful (possono avere varvariabili); in Fortress sono puro comportamento. Le interfacce di Java con i metodi predefiniti sono senza stato; questo significa che non sono tratti? (Suggerimento: quella era una domanda trabocchetto.)

Anche in questo caso, in Scala, i tratti sono composti attraverso la linearizzazione; se la classe Aestende tratti Xe Y, l'ordine in cui Xe Ysono mescolati in determina come i conflitti tra Xe Ysi risolvono. In Java, questo meccanismo di linearizzazione non è presente (è stato rifiutato, in parte, perché troppo "non simile a Java".)

La ragione approssimativa per aggiungere metodi predefiniti alle interfacce era supportare l' evoluzione dell'interfaccia , ma eravamo ben consapevoli che stavamo andando oltre. Che tu lo consideri "evoluzione dell'interfaccia ++" o "tratti--" è una questione di interpretazione personale. Quindi, per rispondere alla tua domanda sulla sicurezza ... fintanto che ti attieni a ciò che il meccanismo effettivamente supporta, piuttosto che cercare di estenderlo desiderosamente a qualcosa che non supporta, dovresti stare bene.

Uno degli obiettivi principali del progetto era che, dal punto di vista del client di un'interfaccia, i metodi predefiniti non fossero distinguibili dai metodi di interfaccia "normali". L'impostazione predefinita di un metodo, quindi, è interessante solo per il progettista e l' implementazione dell'interfaccia.

Ecco alcuni casi d'uso che rientrano perfettamente negli obiettivi di progettazione:

  • Evoluzione dell'interfaccia. Qui, stiamo aggiungendo un nuovo metodo a un'interfaccia esistente, che ha un'implementazione predefinita ragionevole in termini di metodi esistenti su quell'interfaccia. Un esempio potrebbe essere l'aggiunta del forEachmetodo a Collection, dove l'implementazione predefinita è scritta in termini di iterator()metodo.

  • Metodi "opzionali". Qui, il progettista di un'interfaccia sta dicendo: "Gli implementatori non hanno bisogno di implementare questo metodo se sono disposti a convivere con i limiti di funzionalità che ciò comporta". Ad esempio, è Iterator.removestato assegnato un valore predefinito che genera UnsupportedOperationException; poiché la stragrande maggioranza delle implementazioni Iteratorha comunque questo comportamento, l'impostazione predefinita rende questo metodo essenzialmente opzionale. (Se il comportamento da AbstractCollectionfosse espresso come predefinito su Collection, potremmo fare lo stesso per i metodi mutativi.)

  • Metodi di convenienza. Questi sono metodi rigorosamente per comodità, ancora una volta generalmente implementati in termini di metodi non predefiniti sulla classe. Il logger()metodo nel tuo primo esempio ne è un ragionevole esempio.

  • Combinatori. Questi sono metodi di composizione che istanziano nuove istanze dell'interfaccia in base all'istanza corrente. Ad esempio, i metodi Predicate.and()o Comparator.thenComparing()sono esempi di combinatori.

Se fornisci un'implementazione predefinita, dovresti anche fornire alcune specifiche per l'impostazione predefinita (nel JDK, usiamo il @implSpectag javadoc per questo) per aiutare gli implementatori a capire se vogliono sovrascrivere il metodo o meno. Alcuni valori predefiniti, come metodi di convenienza e combinatori, non vengono quasi mai sovrascritti; altri, come i metodi opzionali, sono spesso sovrascritti. È necessario fornire specifiche sufficienti (non solo documentazione) su ciò che il valore predefinito promette di fare, in modo che l'implementatore possa prendere una decisione sensata sulla necessità di sovrascriverlo.


9
Grazie Brian per questa risposta esauriente. Ora posso usare metodi predefiniti con un cuore leggero. Lettori: maggiori informazioni da Brian Goetz sull'evoluzione dell'interfaccia e sui metodi predefiniti possono essere trovate in NightHacking Worldwide Lambdas, ad esempio.
tuo

Grazie @ brian-goetz. Da quello che hai detto, penso che i metodi predefiniti di Java siano più vicini alla nozione di tratti tradizionali come definiti nel documento di Ducasse et al. ( Scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). I "tratti" di Scala non mi sembrano affatto tratti, poiché hanno stato, usano la linearizzazione nella loro composizione, e sembrerebbe che risolvano anche implicitamente i conflitti di metodo - e queste sono tutte cose che i tratti tradizionali non fanno avere. In effetti, direi che i tratti Scala sono più simili ai mixin che ai tratti. Cosa ne pensi? PS: non ho mai codificato in Scala.
adino

Che ne dici di utilizzare implementazioni predefinite vuote per i metodi in un'interfaccia che funge da ascoltatore? L'implementazione dell'ascoltatore può interessare solo l'ascolto di pochi metodi di interfaccia, quindi impostando i metodi come predefiniti, l'implementatore deve solo implementare i metodi di cui ha bisogno per ascoltare.
Lahiru Chandima,

1
@LahiruChandima Like with MouseListener? Nella misura in cui questo stile API ha senso, si inserisce nel bucket "metodi facoltativi". Assicurati di documentare chiaramente l'opzionalità!
Brian Goetz

Sì. Proprio come in MouseListener. Grazie per la risposta.
Lahiru Chandima,
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.