Perché Java 8 non include collezioni immutabili?

123

Il team di Java ha fatto moltissimo lavoro rimuovendo gli ostacoli alla programmazione funzionale in Java 8. In particolare, le modifiche alle collezioni java.util fanno un ottimo lavoro nel concatenare le trasformazioni in operazioni in streaming molto veloci. Considerando quanto hanno svolto un buon lavoro aggiungendo funzioni di prima classe e metodi funzionali alle collezioni, perché non hanno completamente fornito collezioni immutabili o persino interfacce di raccolta immutabili?

Senza modificare alcun codice esistente, il team Java potrebbe in qualsiasi momento aggiungere interfacce immutabili uguali a quelle mutabili, meno i metodi "set" e estendere le interfacce esistenti, come questa:

                  ImmutableIterable
     ____________/       |
    /                    |
Iterable        ImmutableCollection
   |    _______/    /          \   \___________
   |   /           /            \              \
 Collection  ImmutableList  ImmutableSet  ImmutableMap  ...
    \  \  \_________|______________|__________   |
     \  \___________|____________  |          \  |
      \___________  |            \ |           \ |
                  List            Set           Map ...

Certo, operazioni come List.add () e Map.put () attualmente restituiscono un valore booleano o precedente per la chiave data per indicare se l'operazione è riuscita o meno. Le collezioni immutabili dovrebbero trattare tali metodi come fabbriche e restituire una nuova raccolta contenente l'elemento aggiunto, che è incompatibile con la firma corrente. Ma ciò potrebbe essere aggirato usando un nome di metodo diverso come ImmutableList.append () o .addAt () e ImmutableMap.putEntry (). La verbosità risultante sarebbe più che compensata dai vantaggi di lavorare con insiemi immutabili e il sistema di tipi avrebbe evitato errori nel chiamare il metodo sbagliato. Nel tempo, i vecchi metodi potrebbero essere deprecati.

Vittorie di collezioni immutabili:

  • Semplicità: ragionare sul codice è più semplice quando i dati sottostanti non cambiano.
  • Documentazione - se un metodo prende un'interfaccia di raccolta immutabile, sai che non modificherà quella raccolta. Se un metodo restituisce una raccolta immutabile, sai che non puoi modificarla.
  • Concorrenza: le raccolte immutabili possono essere condivise in modo sicuro tra thread.

Come qualcuno che ha assaggiato le lingue che assumono l'immutabilità, è molto difficile tornare al selvaggio West della mutazione dilagante. Le collezioni di Clojure (astrazione di sequenze) dispongono già di tutto ciò che forniscono le raccolte Java 8, oltre all'immutabilità (anche se forse utilizzando memoria e tempo aggiuntivi dovuti a elenchi di collegamenti sincronizzati anziché flussi). Scala ha collezioni sia mutabili che immutabili con un set completo di operazioni, e anche se quelle operazioni sono avide, chiamando .iterator dà una visione pigra (e ci sono altri modi per valutarle pigramente). Non vedo come Java possa continuare a competere senza collezioni immutabili.

Qualcuno può indicarmi la storia o la discussione su questo? Sicuramente è pubblico da qualche parte.

    
posta GlenPeterson 18.12.2013 - 15:53
fonte

6 risposte

109

Perché le collezioni immutabili richiedono assolutamente la condivisione per essere utilizzabili. Altrimenti, ogni singola operazione rilascia un intero altro elenco nell'heap da qualche parte. Le lingue completamente immutabili, come Haskell, generano quantità sorprendenti di spazzatura senza ottimizzazioni aggressive e condivisione. La raccolta utilizzabile solo con < 50 elementi non vale la pena inserirla nella libreria standard.

Inoltre, le collezioni immutabili spesso hanno implementazioni fondamentalmente diverse rispetto alle loro controparti mutabili. Prendi in considerazione, ad esempio, ArrayList , un ArrayList immutabile efficiente non sarebbe affatto una matrice! Dovrebbe essere implementato con un albero bilanciato con un grande fattore di ramificazione, Clojure usa 32 IIRC. Rendere le collezioni mutabili "immutabili" aggiungendo un aggiornamento funzionale è un bug di prestazioni tanto quanto una perdita di memoria.

Inoltre, la condivisione non è fattibile in Java. Java offre troppi hook illimitati alla mutabilità e all'eguaglianza di riferimento per rendere la condivisione "solo un'ottimizzazione". Probabilmente ti irriterebbe un po 'se tu potessi modificare un elemento in una lista e ti rendi conto che hai appena modificato un elemento nelle altre 20 versioni di quella lista che hai avuto.

Ciò esclude anche enormi classi di ottimizzazioni molto vitali per l'immutabilità efficiente, la condivisione, lo streaming della fusione, il nome e la mutabilità. (Sarebbe un buon motto per gli evangelisti del PF)

    
risposta data 18.12.2013 - 17:38
fonte
66

Una collezione mutevole non è un sottotipo di una collezione immutabile. Invece, le collezioni mutevoli e immutabili sono discendenti fratelli di raccolte leggibili. Sfortunatamente, i concetti di "leggibile", "sola lettura" e "immutabile" sembrano confusi insieme, anche se significano tre cose diverse.

  • Una classe di base di raccolta leggibile o un tipo di interfaccia promette che si possono leggere elementi e non fornisce alcun mezzo diretto per modificare la raccolta, ma non garantisce che il codice che riceve il riferimento non possa trasmettere o manipolarlo in tale modo per consentire la modifica.

  • Un'interfaccia di raccolta di sola lettura non include nuovi membri, ma dovrebbe essere implementata solo da una classe che promette che non c'è modo di manipolare un riferimento ad esso in modo tale da mutare la collezione né ricevere un riferimento a qualcosa che potrebbe farlo. Tuttavia, non promette che la raccolta non sarà modificata da qualcos'altro che ha un riferimento agli interni. Si noti che un'interfaccia di raccolta di sola lettura potrebbe non essere in grado di impedire l'implementazione di classi mutabili, ma può specificare che qualsiasi implementazione, o classe derivata da un'implementazione, che consente la mutazione deve essere considerata un'implementazione "illegittima" o derivata di un'implementazione .

  • Una raccolta immutabile è quella che manterrà sempre gli stessi dati finché esiste un riferimento ad essa. Qualsiasi implementazione di un'interfaccia immutabile che non restituisce sempre gli stessi dati in risposta a una particolare richiesta è interrotta.

A volte è utile avere tipi di raccolta mutevoli e immutabili strongmente associati che implementano o derivano dallo stesso tipo "leggibile" e che il tipo leggibile include AsImmutable , AsMutable e AsNewMutable metodi . Tale design può consentire il codice che desidera mantenere i dati in una raccolta per chiamare AsImmutable ; quel metodo creerà una copia difensiva se la collezione è mutevole, ma salterà la copia se è già immutabile.

    
risposta data 21.12.2013 - 09:37
fonte
13

Java Collections Framework offre la possibilità di creare una versione di sola lettura di una raccolta tramite sei metodi statici in classe java.util.Collections :

Come qualcuno ha sottolineato nei commenti alla domanda originale, le raccolte restituite potrebbero non essere considerate immutabili perché anche se le raccolte non possono essere modificate (nessun membro può essere aggiunto o rimosso da una tale raccolta), gli oggetti reali di riferimento dalla collezione può essere modificato se il loro tipo di oggetto lo consente.

Tuttavia, questo problema rimarrebbe indipendentemente dal fatto che il codice restituisca un singolo oggetto o una collezione non modificabile di oggetti. Se il tipo consente di mutare i suoi oggetti, allora questa decisione è stata presa nel progetto del tipo e non vedo come una modifica al JCF possa alterarlo. Se l'immutabilità è importante, i membri di una raccolta dovrebbero essere di tipo immutabile.

    
risposta data 25.12.2013 - 22:44
fonte
7

Questa è un'ottima domanda. Mi piace intrattenere l'idea che di tutto il codice scritto in java e in esecuzione su milioni di computer in tutto il mondo, ogni giorno, 24 ore su 24, circa la metà dei cicli di clock totali devono essere sprecati facendo solo copie di sicurezza delle raccolte che sono essere restituito dalle funzioni. (E raccolta dei rifiuti in millisecondi dopo la loro creazione.)

Una percentuale di programmatori java è a conoscenza dell'esistenza della famiglia di metodi unmodifiableCollection() della classe Collections , ma anche tra loro, molti semplicemente non si preoccupano di farlo.

E non posso biasimarli: un'interfaccia che finge di essere in lettura-scrittura ma che lancia un UnsupportedOperationException se si commette l'errore di invocare uno qualsiasi dei suoi metodi di 'scrittura' è una cosa piuttosto malvagia avere! / p>

Ora un'interfaccia come Collection che mancherebbe dei metodi add() , remove() e clear() non sarebbe un'interfaccia "ImmutableCollection"; sarebbe un'interfaccia "UnmodifiableCollection". Di fatto, non ci potrebbe mai essere un'interfaccia "ImmutableCollection", perché l'immutabilità è una natura di un'implementazione, non una caratteristica di un'interfaccia. Lo so, non è molto chiaro; lasciami spiegare.

Supponiamo che qualcuno ti dia una tale interfaccia di raccolta di sola lettura; è sicuro passarlo ad un altro thread? Se sapessi per certo che rappresenta una collezione veramente immutabile, allora la risposta sarebbe "sì"; sfortunatamente, dal momento che è un'interfaccia, non sai come è implementata, quindi la risposta deve essere un no : per quanto ne sai, potrebbe essere un non modificabile (per te) vista di una collezione che è in effetti mutabile, (come quello che si ottiene con Collections.unmodifiableCollection() ,) quindi tentare di leggerlo mentre un altro thread sta modificando si tradurrebbe nella lettura di dati corrotti.

Quindi, quello che hai essenzialmente descritto è un insieme di interfacce di raccolta non "Immutabili", ma "non modificabili". È importante capire che "non modificabile" significa semplicemente che a chiunque abbia un riferimento a tale interfaccia è impedito di modificare la raccolta sottostante, e vengono impediti semplicemente perché l'interfaccia non ha alcun metodo di modifica, non perché la collezione sottostante sia necessariamente immutabile. La collezione sottostante potrebbe essere facilmente mutabile; non ne hai conoscenza e non ne hai il controllo.

Per avere collezioni immutabili, dovrebbero essere classi , non interfacce!

Queste classi di collezioni immutabili dovrebbero essere definitive, in modo che quando ti viene dato un riferimento a una tale collezione sai per certo che si comporterà come una collezione immutabile, indipendentemente da ciò che tu o chiunque altro abbia un riferimento a potrebbe farcela.

Quindi, per avere un insieme di raccolte complete in java, (o qualsiasi altro linguaggio imperativo dichiarativo) avremmo bisogno di quanto segue:

  1. Un insieme di interfacce non modificabili .

  2. Un insieme di mutable collezione interfacce , estendendo quelle non modificabili.

  3. Un insieme di mutevoli raccolte classi che implementano le interfacce mutabili e, per estensione, anche le interfacce non modificabili.

  4. Un set di immutable collezione classi , che implementa le interfacce non modificabili, ma che vengono principalmente trasmesse come classi, in modo da garantire l'immutabilità.

Ho implementato tutto quanto sopra per divertimento, e li sto usando nei progetti, e funzionano come un incantesimo.

Il motivo per cui non fanno parte del runtime Java è probabilmente perché si pensava che sarebbe troppo / troppo complesso / troppo difficile da capire.

Personalmente, penso che ciò che ho descritto sopra non sia ancora abbastanza; un'altra cosa che sembra essere necessaria è un insieme di interfacce e ampli mutevoli; classi per immutabilità strutturale . (Che può essere chiamato semplicemente "Rigido" perché il prefisso "StructurallyImmutable" è troppo dannatamente lungo.)

    
risposta data 04.06.2015 - 17:07
fonte
2

Le raccolte immutabili possono essere profondamente ricorsive, confrontate tra loro e non irragionevolmente inefficienti se l'uguaglianza degli oggetti è garantita da secureHash. Questa è chiamata una foresta di merkle. Può essere per collezione o parte di essi come un albero AVL (auto bilanciante binario) per una mappa ordinata.

A meno che tutti gli oggetti java in queste raccolte abbiano un ID univoco o una stringa di bit in hash, la raccolta non ha nulla da hash per nominare univocamente se stessa.

Esempio: Sul mio laptop 4x1.6ghz, posso eseguire 200K sha256s al secondo della dimensione più piccola che si adatta a 1 ciclo hash (fino a 55 byte), rispetto a 500K HashMap ops o 3M op in un hashtable di long. 200K / log (collectionSize) Le nuove raccolte al secondo sono abbastanza veloci per alcune cose in cui l'integrità dei dati e la scalabilità globale anonima sono importanti.

    
risposta data 04.09.2016 - 21:52
fonte
-3

Prestazioni. Le collezioni per loro natura possono essere molto grandi. Copiare 1000 elementi in una nuova struttura con 1001 elementi invece di inserire un singolo elemento è semplicemente orribile.

Concorrenza. Se hai più thread in esecuzione, potresti voler ottenere la versione corrente della raccolta e non la versione passata 12 ore fa all'avvio del thread.

bagagli. Con oggetti immutabili in un ambiente multi-thread puoi finire con dozzine di copie dello "stesso" oggetto in diversi punti del suo ciclo di vita. Non importa per un oggetto Calendar o Date ma quando è una collezione di 10.000 widget questo ti ucciderà.

    
risposta data 19.12.2013 - 02:15
fonte

Leggi altre domande sui tag