2011-11-02 17 views
12

Prendete questi due classi Java:Un modo carino per fare riferimento a due oggetti immutabili?

class User { 
    final Inventory inventory; 
    User (Inventory inv) { 
     inventory = inv; 
    } 
} 

class Inventory { 
    final User owner; 
    Inventory (User own) { 
     owner = own; 
    } 
} 

Esiste un modo without using reflection* per tirare fuori questo? In realtà non mi aspetto che lo sia, ma non può far male a chiedere.

Aggiornamento: Poiché nella costruzione bytecode ha due fasi (1. destinare oggetto, 2. chiamata del costruttore **) questo potrebbe essere (ab) usato per fare questo, con bytecode scritto a mano o un compilatore personalizzato? Sto parlando di eseguire prima il passaggio 1 per entrambi gli oggetti, poi il passaggio 2 per entrambi, usando i riferimenti del passaggio 1. Ovviamente qualcosa del genere sarebbe piuttosto ingombrante, e questa parte della domanda è accademica.

(* Perché riflessione può dare problemi con un responsabile della sicurezza)

(** dice la mia limitata conoscenza)

+1

le dipendenze circolari non sono spesso una buona idea – Bozho

+0

Mi dispiace, potresti approfondire un po 'il problema. Non vedo quale sia il problema, dal momento che hai già un riferimento all'altro oggetto? – Anders

+0

@Il problema è che entrambi richiedono un riferimento all'altro in fase di costruzione e tale riferimento è disponibile solo dopo la costruzione. –

risposta

13

Questo può funzionare solo in modo pulito se uno degli oggetti è stato creato da altri. Ad esempio è possibile cambiare la classe User a qualcosa di simile (pur mantenendo la classe Inventory invariato):

class User { 
    private final Inventory inventory; 
    User() { 
     inventory = new Inventory(this); 
    } 
} 

È necessario essere attenti su come accedere all'oggetto User nel costruttore Inventory, però: non è completamente inizializzato ancora . Ad esempio, il suo campo inventory sarà ancora null!

Aggiornamento annuncio: ora ho verificato che l'approccio bytecode-manipolazione non non funziona. L'ho provato usando Jasmin e non è mai riuscito a caricare con un VerifyError.

Scavando più a fondo nel problema, ho trovato § 4.10.2.4 Instance Initialization Methods and Newly Created Objects. Questa sezione spiega come la JVM garantisce che solo le istanze di oggetti inizializzate vengano passate in giro.

+0

Qualche idea sui trucchi del bytecode (vedi domanda aggiornata)? Ciò che descrivo qui è probabilmente impossibile. –

+0

@BartvanHeukelom: teoricamente potrebbe funzionare. Non so se alla JVM piaccia davvero un 'new' senza il' invokespecial 'corretto per invocare il costruttore. Lasciatemi provare che ... –

+0

Sono state apportate modifiche a 1,4 (dopo JVSpec 2nd Ed.) Che hanno consentito l'accesso a determinati campi finali al di fuori della classe. Non riesco a ricordare i cambiamenti esatti. –

7

È possibile farlo se non è necessario iniettare uno degli oggetti.

class User { 
    private final Inventory inventory; 
    User() { 
     inventory = new Inventory(this); 
    } 
} 
3
class User { 
    private final Inventory inventory; 
    User (/*whatever additional args are needed to construct the inventory*/) { 
     //populate user fields 
     inventory = new Inventory(this); 
    } 
} 

class Inventory { 
    private final User owner; 
    Inventory (User own) { 
     owner = own; 
    } 
} 

Questo è il meglio che posso pensare. Forse c'è un modello migliore.

0

Questa è una "soluzione", anche se la perdita di uno final è scomoda.

class User { 
    Inventory inventory; 
    User() { } 
    // make sure this setter is only callable from where it should be, 
    // and is called only once at construction time 
    setInventory(inv) { 
     if (inventory != null) throw new IllegalStateException(); 
     inventory = inv; 
    } 
} 

class Inventory { 
    final User owner; 
    Inventory (User own) { 
     owner = own; 
    } 
} 
+1

Giusto. Non hai più due oggetti immutabili, che il titolo della tua domanda indica come premessa;) – aioobe

+0

@aioobe Infatti, così ho modificato e inserito "soluzione" tra virgolette. È comunque immutabile per il mondo esterno, perché non c'è un setter che possano usare. –

+0

Giusto, a meno che non si considerino le altre classi nello stesso pacchetto come il mondo esterno: P – aioobe

0

Se siete interessati solo a JVM bytecode e non si cura di codifica in Java specificamente, magari utilizzando Scala o Clojure potrebbe aiutare. Avrai bisogno di una sorta di macchinario letrec.

1

Leggermente pedante, ma non è strettamente necessario crearne uno dentro l'altro, se non vi dispiace un po 'di indecisione. Potrebbero entrambi essere classi interne.

public class BadlyNamedClass { 
    private final User owner; 
    private final Inventory inventory; 

    public BadlyNamedClass() { 
     this.owner = new User() { 
      ... has access to BadlyNamedClass.this.inventory; 
     }; 
     this.inventory = new Inventory() { 
      ... has access to BadlyNamedClass.this.owner; 
     }; 
    } 
    ... 
} 

O anche:

public class BadlyNamedClass { 
    private final User owner; 
    private final Inventory inventory; 

    public BadlyNamedClass() { 
     this.owner = new User(this); 
     this.inventory = new Inventory(this); 
    } 
    public User getOwner() { return owner; } 
    public Inventory getInventory() { return inventory; } 
    ... 
} 
+0

Trovo che questa sia una buona soluzione. La relazione tra utente e inventario è qualcosa che ha il suo scopo e quindi merita la sua classe. Fare riferimento a un altro oggetto può anche significare che devi andare in un altro modo per chiedere a qualcuno a cui si riferisce un'istanza. – SpaceTrucker

+0

Ben pensato, tuttavia c'è ancora un problema dal momento che il terzo oggetto immutabile è in fuga nel suo costruttore. –

+0

@AlexandreDupriez Quale? la semantica di campo 'finale', ecc., di' owner' e 'inventory' è irrilevante. Sono coperti da quelli di 'BadlyNamedClass'. ('String', che in genere è implementato usando un' char [] 'è l'esempio canonico di questo.) –

0

B: "Inventario creati dall'utente è la nostra ultima speranza".
Y: "No, ce n'è un altro."

Se si astraggono i riferimenti a una terza parte, è possibile controllarne la relazione.

Ad esempio.

public class User 
{ 
    private final String identifier; // uniquely identifies this User instance. 

    public User(final String myIdentifier) 
    { 
     identifier = myIdentifier; 

     InventoryReferencer.registerBlammoUser(identifier); // Register the user with the Inventory referencer. 
    } 

    public Inventory getInventory() 
    { 
     return InventoryReferencer.getInventoryForUser(identifier); 
    } 
} 

public interface Inventory // Bam! 
{ 
    ... nothing special. 
} 

// Assuming that the Inventory only makes sence in the context of a User (i.e. User must own Inventory). 
public class InventoryReferencer 
{ 
    private static final Map<String, Inventory> referenceMap = new HashMap<String, Inventory>(); 

    private InventoryReferencer() 
    { 
     throw ... some exception - helps limit instantiation. 
    } 

    public static void registerBlammoUser(final String identifier) 
    { 
     InventoryBlammo blammo = new InventoryBlammo(); 
     referenceMap.add(indentifier, blammo); 
    } 

    public static void registerKapowUser(final String identifier) 
    { 
     InventoryBlammo kapow = new InventoryKapow(); 
     referenceMap.add(indentifier, kapow); 
    } 

    public static Inentory getInfentoryForUser(final String identifier) 
    { 
     return referenceMap.get(identifier); 
    } 
} 

// Maybe package access constructors. 
public class InventoryBlammo implements Inventory 
{ 
    // a Blammo style inventory. 
} 

public class InventoryKapow implements Inventory 
{ 
    // a Kapow style inventory. 
} 
Problemi correlati