2009-04-09 18 views
15

Qualcuno conosce una libreria o almeno qualche ricerca sulla creazione e l'uso di strutture di dati persistenti in Java? Non mi riferisco alla persistenza come memoria a lungo termine, ma alla persistenza in termini di immutabilità (vedi Wikipedia entry).Strutture dati persistenti in Java

Attualmente sto esplorando diversi modi per modellare un'API per le strutture persistenti. Utilizzando i costruttori sembra essere una soluzione interessante:

// create persistent instance 
Person p = Builder.create(Person.class) 
      .withName("Joe") 
      .withAddress(Builder.create(Address.class) 
       .withCity("paris") 
       .build()) 
       .build(); 

// change persistent instance, i.e. create a new one 
Person p2 = Builder.update(p).withName("Jack"); 

Person p3 = Builder.update(p) 
       .withAddress(Builder.update(p.address()) 
       .withCity("Berlin") 
       .build) 
      .build(); 

Ma questo ancora si sente un po 'boilerplated. Qualche idea?

+2

Non sono sicuro se il tag di programmazione funzionale qui sia appropriato. Prendo la programmazione funzionale per significare lingue come Haskell e Lisp. :) – uriDium

+7

Penso funzionale perché le strutture dati persistenti sono comuni ai linguaggi di programmazione funzionale. Immagino che voglia essere in grado di fare qualcosa in Java che è facilmente in grado di fare in un linguaggio funzionale. Perché wiki della comunità? Mi sembra una domanda di programmazione solida. –

risposta

6

immagino le scelte ovvie sono:

o Passare a una struttura di dati transitoria (costruttore) per l'aggiornamento. Questo è abbastanza normale. StringBuilder per la manipolazione String ad esempio. Come esempio.

Person p3 = 
    Builder.update(p) 
    .withAddress(
     Builder.update(p.address()) 
     .withCity("Berlin") 
     .build() 
    ) 
    .build(); 

o Utilizzare sempre strutture persistenti. Anche se sembra esserci molta copia, dovresti condividere quasi tutti gli stati, quindi non è neanche lontanamente così male come sembra.

final Person p3 = p 
    .withAddress(
     p.address().withCity("Berlin") 
    ); 

o Esplodere la struttura dati in molte variabili e ricombinarsi con un costruttore enorme e confuso.

final Person p3 = Person.of(
    p.name(), 
    Address.of(
     p.house(), p.street(), "Berlin", p.country() 
    ), 
    p.x(), 
    p.y(), 
    p.z() 
); 

o Utilizzare le interfacce di richiamata per fornire i nuovi dati. Ancora più caldaia.

final Person p3 = Person.of(new PersonInfo(
    public String name () { return p.name();) 
    public Address address() { return Address.of(new AddressInfo() { 
     private final Address a = p.address(); 
     public String house () { return a.house() ; } 
     public String street() { return a.street() ; } 
     public String city () { return "Berlin" ; } 
     public String country() { return a.country(); } 
    })), 
    public Xxx  x() { return p.x(); } 
    public Yyy  y() { return p.y(); } 
    public Zzz  z() { return p.z(); } 
}); 

o Utilizzare hack brutto per rendere i campi transitoriamente disponibili per il codice.

final Person p3 = new PersonExploder(p) {{ 
    a = new AddressExploder(a) {{ 
     city = "Berlin"; 
    }}.get(); 
}}.get(); 

(Stranamente ero appena messo giù una copia di strutture di dati puramente funzionali da Chris Okasaki.)

+0

Se integriamo il metodo di aggiornamento nel tipo persistente, potremmo anche farlo semplicemente con la persona finale p3 = p2.withAddress (p3.address(). WithCity ("Berlin")) Sono curioso di sapere se esiste un modo per ometti il ​​riferimento duplicato a "p3". – ordnungswidrig

+0

L'esempio non avrebbe dovuto avere il builder (aggiornato). Potresti aggiungere brutti hack per ottenere l'indirizzo, quindi tornare alla persona, ma sarebbe malvagio e confuso.Un altro brutto attacco potrebbe essere una classe che fa esplodere la Persona e quindi usa un doppio idioma - non abbastanza caratteri per descriverlo. –

+0

Notando semplicemente, l'inizializzazione a doppia parentesi viene definita "cattiva" per un motivo: crea una sottoclasse (impatto sulle dimensioni e sulle prestazioni del codice) che spesso riceve un riferimento all'oggetto che lo contiene (potrebbe causare una perdita di memoria). – v6ak

1

E 'molto difficile, se non impossibile, per rendere le cose immutabili che non è stato progettato in modo .

Se è possibile progettare da zero:

  • uso solo campi finali
  • Non fare riferimento oggetti non immutabili
11

Costruttori renderanno il vostro codice di troppo prolisso per essere utilizzabili. In pratica, quasi tutte le strutture di dati immutabili che ho visto passano in stato attraverso il costruttore.Per quello che vale la pena, qui ci sono un bel serie di post che descrivono strutture di dati immutabili in C# (che dovrebbe convertire facilmente in Java):

C# e Java sono estremamente prolisso, in modo che il codice in questi articoli è abbastanza spaventoso. Raccomando di imparare OCaml, F # o Scala e familiarizzare con l'immutabilità con quelle lingue. Una volta padronanza della tecnica, sarai in grado di applicare lo stesso stile di codifica a Java molto più facilmente.

+1

Cosa intendi per "Costruttori renderà il tuo codice a verbose inutilizzabile"? Intendevi dire "troppo prolisso per essere utilizzabile"? Se è così, sono assolutamente in disaccordo. I costruttori rendono il codice semplice, facile da capire e molto chiaro. – kgrad

+0

Conosco scala e haskell, ecco alcune delle motivazioni per indagare sulle strutture dati persistenti in java. – ordnungswidrig

+0

Il codice per implementare i builder tende a inutili verbose, certamente. Usando i costruttori c'è la solita zuppa di punteggiatura di lingue simili a Java. –

0

Vuoi immutabilità:

codice
  1. modo esterno non può modificare i dati?
  2. quindi una volta impostato un valore non può essere modificato?

In entrambi i casi esistono modi più semplici per ottenere il risultato desiderato.

arresto codice esterno di modificare i dati è facile con interfacce:

public interface Person { 
    String getName(); 
    Address getAddress(); 
} 
public interface PersonImplementor extends Person { 
    void setName(String name); 
    void setAddress(Address address); 
} 

public interface Address { 
    String getCity(); 
} 


public interface AddressImplementor { 
    void setCity(String city); 
} 

per interrompere modifiche a un valore impostato volta è anche "easy" utilizzando java.util.concurrent.atomic.AtomicReference (sebbene sospensione o qualche altro la persistenza utilizzo strato può essere necessario modificare):

class PersonImpl implements PersonImplementor { 
    private AtomicReference<String> name; 
    private AtomicReference<Address> address; 

    public void setName(String name) { 
     if (!this.name.compareAndSet(name, name) 
      && !this.name.compareAndSet(null, name)) { 
      throw new IllegalStateException("name already set to "+this.name.get()+" cannot set to "+name); 
     } 
    } 
    // .. similar code follows.... 
} 

Ma perché avete bisogno di qualcosa di più di una semplice interfaccia per realizzare il compito?

6

Dai un'occhiata allo Functional Java. Datastructures persistenti attualmente forniti includono:

  • lista semplicemente legata (fj.data.List)
  • Pigro lista semplicemente legata (fj.data.Stream)
  • lista non vuota (fj.data.NonEmptyList)
  • valore facoltativo (un contenitore di lunghezza 0 o 1) (fj.data.Option)
  • Set (fj.data.Set)
  • Multi-modo di albero (albero rosa aka) (fj.data.Tree
  • Mappa immutabile (fj.data.TreeMap)
  • Prodotti (tuple) di genere 1-8 (fj.P1 ..P8)
  • Vettori di arietà 2-8 (fj.data.vector.V2..V8)
  • lista punta (fj.data.Zipper)
  • albero di punta (fj.data.TreeZipper)
  • type-safe, generico lista eterogenea (fj.data.hlist.HList)
  • array immutabili (fj.data.Array)
  • Disgiunte unione tipo di dati (fj.data.Either)

Un certo numero di esempi di utilizzo sono forniti con la distribuzione binaria. La fonte è disponibile con una licenza BSD da Google Code.

+0

Grazie per il puntatore al java funzionale. Comunque la mia domanda riguardava l'implementazione di un modello di oggetti persistente in gerneral, non su certe (ben note) strutture di dati persistenti. – ordnungswidrig

+0

Invece di "oggetto", pensa "record" o "struct". Un record è fondamentalmente un elenco eterogeneo con capacità di ricerca per tipo. Stavo pensando a come implementare questo genere di cose anche in Java. Fammi sapere come vai. – Apocalisp

3

seguire una molto semplice provvisorio con delega dinamica:

class ImmutableBuilder { 

    static <T> T of(Immutable immutable) { 
     Class<?> targetClass = immutable.getTargetClass(); 
     return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), 
      new Class<?>[]{targetClass}, 
      immutable); 
    } 

    public static <T> T of(Class<T> aClass) { 
     return of(new Immutable(aClass, new HashMap<String, Object>())); 
    } 
} 

class Immutable implements InvocationHandler { 

    private final Class<?> targetClass; 
    private final Map<String, Object> fields; 

    public Immutable(Class<?> aTargetClass, Map<String, Object> immutableFields) { 
     targetClass = aTargetClass; 
     fields = immutableFields; 
    } 

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
     if (method.getName().equals("toString")) { 
      // XXX: toString() result can be cached 
      return fields.toString(); 
     } 

     if (method.getName().equals("hashCode")) { 
      // XXX: hashCode() result can be cached 
      return fields.hashCode(); 
     } 

     // XXX: naming policy here 
     String fieldName = method.getName(); 

     if (method.getReturnType().equals(targetClass)) { 
      Map<String, Object> newFields = new HashMap<String, Object>(fields); 
      newFields.put(fieldName, args[0]); 
      return ImmutableBuilder.of(new Immutable(targetClass, newFields)); 
     } else { 
      return fields.get(fieldName); 
     } 
    } 

    public Class<?> getTargetClass() { 
     return targetClass; 
    } 
} 

di utilizzo:

interface Person { 
    String name(); 
    Person name(String name); 
    int age(); 
    Person age(int age); 
} 

public class Main { 

    public static void main(String[] args) { 
     Person mark = ImmutableBuilder.of(Person.class).name("mark").age(32); 
     Person john = mark.name("john").age(24); 
     System.out.println(mark); 
     System.out.println(john); 
    } 
} 

crescere direzioni:

  • denominazione politica (getName, withName, nome)
  • caching toString(), hashCode()
  • equals() implementazioni dovrebbe essere semplice (anche se non implementato)

Speranza che aiuta :)

+0

Ecco come ho effettivamente implementato una dimostrazione di concetto su strutture di dati persuasive. Ho trovato questa la parte semplice. Quello che non ho trovato è stato un elegante API per "aggiornare" un campo di un'istanza persistente, cioè creare una copia dell'istanza con solo un determinato campo modificato. – ordnungswidrig

+0

scusa ho frainteso la tua domanda! :-( A proposito, scrivere questo codice è stato molto divertente e un utile esercizio :-) – dfa

5

ho implementato un paio di strutture di dati persistenti in Java. Tutti open source (GPL) su Google Code per chiunque sia interessato:

http://code.google.com/p/mikeralib/source/browse/#svn/trunk/Mikera/src/mikera/persistent

I principali quelli che ho finora sono:

  • prova mutevole persistente oggetto
  • hash persistente Mappe
  • Vettori/elenchi persistenti
  • Set persistenti (incluso un set persistente specializzato di interi)
+2

Prima di decidere di non utilizzarlo, cerca un esempio: Hai implementato le interfacce di base da Collezioni Java. È meglio per i metodi NON ESISTE DA che essere bloccati con UnsupportedOperationExceptions. –

+2

Punto di vista interessante David .... Ho ritenuto che valesse la pena implementare queste interfacce in modo che le strutture di dati persistenti possano essere utilizzate con API che prevedono una raccolta Java (che è un bel po 'di API, incluse molte delle API Java principali! Se si desidera tale compatibilità API, non c'è altra scelta se non quella di lanciare una UnsupportedOperationException - tuttavia penso che sia una buona cosa, perché se qualcuno tenta di modificare una collezione immutabile, allora c'è chiaramente qualche bug nella loro logica che dovrebbero essere aggiustati! – mikera

+1

Sì, per compatibilità API UnsupportedOperationExceptions è la via. Ma non devono essere le classi principali che sono compatibili, anche le classi wrapper supportate da te lo faranno. Quindi hai la compatibilità quando ne hai bisogno, e il resto del tempo, le classi che comunicano la loro immutabilità al momento della compilazione. –