2012-09-25 12 views
7

Ho un enumerato come quello qui sotto, ma eclissi dice che ci sono errori nella prima definizione di ogni coppia opposta.Java enum - Impossibile fare riferimento a un campo prima che sia definito

public enum Baz{ 
    yin(yang), //Cannot reference a field before it is defined 
    yang(yin), 
    good(evil), //Cannot reference a field before it is defined 
    evil(good); 

    public final Baz opposite; 

    Baz(Baz opposite){ 
    this.opposite = opposite; 
    } 
} 

Quello che voglio realizzare è di essere in grado di utilizzare Baz.something.opposite per ottenere l'oggetto opposta di Baz.something. C'è una soluzione possibile per questo? Forse un segnaposto vuoto per yang e bad prima di yin e good sono definiti in questo esempio?

risposta

9

Si potrebbe provare qualcosa di simile:

public enum Baz{ 
    yin("yang"),  
    yang("yin"), 
    good("evil"), 
    evil("good"); 

    private String opposite; 

    Baz(String opposite){ 
    this.opposite = opposite; 
    } 

    public Baz getOpposite(){ 
    return Baz.valueOf(opposite); 
    } 
} 

e quindi fare riferimento come

Baz.something.getOpposite() 

Questo dovrebbe compiere ciò che si sta cercando di fare, cercando il valore enum da esso di rappresentazione di stringa. Non penso che tu possa farlo funzionare con il riferimento ricorsivo a Baz.

+0

+1 Hai ragione, non è possibile farlo funzionare con gli oggetti reali a causa 'evil' ha vinto' essere definito fino a quando 'good' è già inizializzato, ma cambiarli non funziona perché allora' good' non è definito quando 'evil' è inizializzato. Buona idea usare i nomi :) – Brian

+0

Sostituire i valori enum digitati con stringhe non tipizzate non è una buona soluzione. L'intero punto delle enumerazioni è di dare sicurezza di tipo alle costanti, e le stringhe non ti danno questo. Inoltre, c'è una soluzione molto migliore - facendo questo in un blocco di inizializzazione statico, come @meriton sottolinea nella sua risposta. –

10

Con l'istruzione switch:

public enum Baz{ 
    yin, 
    yang, 
    good, 
    evil; 

    public Baz getOpposite() { 
    switch (this) { 
     case yin: return yang; 
     case yang: return yin; 
     case good: return evil; 
     case evil: return good; 
    } 
    throw new AssertionError(); 
} 

o differito di inizializzazione:

public enum Baz{ 
    yin, 
    yang, 
    good, 
    evil; 

    public Baz opposite; 

    static { 
    yin.opposite = yang; 
    yang.opposite = yin; 
    good.opposite = evil; 
    evil.opposite = good; 
    } 
} 

Si potrebbe voler rendere il campo mutabile privato e fornire un getter.

+0

Non avevo sentito l'inizializzazione posticipata, questa sembra una buona soluzione, ma la versione di jcern String sembra il metodo migliore. – Zaq

+1

+1 per il tuo secondo suggerimento. Odio l'idea di 'switch'. – OldCurmudgeon

+0

Più 1 per l'inizializzazione statica –

0

E ancora un'altra implementazione possibile (simile ad alcune delle altre soluzioni, ma con una HashMap).

import java.util.Map; 
import java.util.HashMap; 

public enum Baz { 
yin, 
yang, 
good, 
evil; 

private static Map<Baz, Baz> opposites = new HashMap<Baz, Baz>(); 
static { 
    opposites.put(yin, yang); 
    opposites.put(yang, yin); 
    opposites.put(good, evil); 
    opposites.put(evil, good); 
} 


public Baz getOpposite() { 
    return opposites.get(this); 
} 

} 
+0

Hai appena copiato e incollato la risposta di OldCurmudgeon? 0.o – Zaq

+0

Beh, ha usato una HashMap invece di EnumMap, questo conta! : D Ma sì, lo schema della mappa è piuttosto semplice. – Natix

+0

No, non l'ha fatto. Abbiamo pubblicato allo stesso tempo. Nota che uso un 'EnumMap' invece di un' HashMap'. Spettrale però eh? – OldCurmudgeon

1

Che ne dici di un EnumMap?

public enum Baz { 
    yin, 
    yang, 
    good, 
    evil; 
    private static final Map<Baz, Baz> opposites = new EnumMap<Baz, Baz>(Baz.class); 

    static { 
    opposites.put(yin, yang); 
    opposites.put(yang, yin); 
    opposites.put(good, evil); 
    opposites.put(evil, good); 
    } 

    public Baz getOpposite() { 
    return opposites.get(this); 
    } 
} 
0

Un'altra alternativa :) utilizzando una mappa. È abbastanza dettagliato, ma in questo modo è possibile definire ogni coppia solo una volta, l'altra direzione viene dedotta.

enum Baz { 

    YIN, YANG, GOOD, EVIL; 

    private static final Map<Baz, Baz> opposites = new EnumMap<>(Baz.class); 

    static { 
     opposites.put(YIN, YANG); 
     opposites.put(GOOD, EVIL); 

     for (Entry<Baz, Baz> entry : opposites.entrySet()) { 
      opposites.put(entry.getValue(), entry.getKey()); 
     } 
    } 

    public Baz opposite() { 
     return opposites.get(this); 
    } 
} 

Personalmente, mi piace Meriton 's secondo esempio il meglio.

0

E poi c'è la soluzione totalmente OTT.

public enum Baz { 
    yin, 
    yang, 
    good, 
    evil, 
    right, 
    wrong, 
    black, 
    white; 

    private static class AutoReversingMap<K extends Enum<K>> extends EnumMap<K, K> { 
    public AutoReversingMap(Class<K> keys) { 
     super(keys); 
    } 

    // Make put do both the forward and the reverse. 
    public K put(K key, K value) { 
     super.put(key, value); 
     super.put(value, key); 
     // Better to return null here than a misleading real return of one of the supers. 
     return null; 
    } 
    } 
    private static final Map<Baz, Baz> opposites = new AutoReversingMap<Baz>(Baz.class); 

    static { 
    // Assume even and odd ones are opposites. 
    for (int i = 0; i < Baz.values().length; i += 2) { 
     opposites.put(Baz.values()[i], Baz.values()[i + 1]); 
    } 
    } 

    public Baz getOpposite() { 
    return opposites.get(this); 
    } 
} 
+0

Sì, il massimo potrebbe essere giusto ... Questa è una risposta grande e completa, ma penso che la risposta più semplice potrebbe essere migliore in questo caso . (Inoltre, posso aggiungere un valore che è il suo opposto, nel qual caso, questo non è l'ideale) – Zaq

+0

Sono totalmente d'accordo. Questo era più per un po 'di divertimento. Penso che il secondo suggerimento di @ meriton sia il più accurato. – OldCurmudgeon

1

Anni dopo, la soluzione più breve e più hacky

public enum Baz { 
    YIN, // Use uppercase for enum names! 
    YANG, 
    GOOD, 
    EVIL; 

    public Baz opposite() { 
     return values()[ordinal()^1]; 
    } 
} 

Essa si basa sul presupposto che ogni membro ha un opposto e che stanno disposti a coppie. Sostituisce il campo con un metodo nella speranza, che la JVM ottimizzerà l'intero overhead. Questo è ragionevole sul desktop, meno ragionevole su Android.

Per eliminare il sovraccarico, potrei utilizzare l'inizializzatore statico come molte altre soluzioni qui.

+0

Non sono passati anni, solo pochi mesi. Questa è una buona risposta e mi piace la sua semplicità. Preferisco le minuscole istanze di enum Java perché sono praticamente le stesse delle proprietà finali statiche. La JVM ottimizzerebbe davvero l'opposto? Non sono sicuro di quanto la JVM si ottimizzi. – Zaq

+0

@ user1494396 La semplice chiamata al metodo breve ottiene * sempre * in linea ogni volta che la JVM pensa che sia rilevante per le prestazioni. La chiamata a 'values ​​()' normalmente [include clonazione] (http://stackoverflow.com/a/1163121/581205), ma questo è il caso più semplice per [analisi di fuga] (http://docs.oracle.com /javase/7/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis). Quindi spero che non rimanga altro che carico di campo, xor e carico statico sul campo. Questo è ancora 3 volte tanto lavoro quanto con un campo, ma immagino che trovare un vero esempio del mondo, dove possa essere misurato sia difficile. – maaartinus

+0

@ user1494396 Cos'altro dovrebbe essere scritto in maiuscolo se non enumerato? Le convenzioni di codifica dicono che le costanti dovrebbero essere, ma non spiega cos'è una costante. Forse una [costante di compilazione] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.28), forse una variabile finale (statica?) Che punta a un oggetto immutabile? – maaartinus

1

È anche possibile utilizzare i metodi astratti per ritardare, con vantaggi della sicurezza del tipo rispetto alla risposta accettata.

public enum Baz { 

    yin(new OppositeHolder() { 
     @Override 
     protected Baz getOpposite() { 
      return yang; 
     } 
    }), 
    yang(new OppositeHolder() { 
     @Override 
     protected Baz getOpposite() { 
      return yin; 
     } 
    }), 
    good(new OppositeHolder() { 
     @Override 
     protected Baz getOpposite() { 
      return evil; 
     } 
    }), 
    evil(new OppositeHolder() { 
     @Override 
     protected Baz getOpposite() { 
      return good; 
     } 
    }); 

    private final OppositeHolder oppositeHolder; 

    private Baz(OppositeHolder oppositeHolder) { 
     this.oppositeHolder = oppositeHolder; 
    } 

    protected Baz getOpposite() { 
     return oppositeHolder.getOpposite(); 
    } 

    private abstract static class OppositeHolder { 
     protected abstract Baz getOpposite(); 
    } 

} 

E codice di prova, perché ne avevo bisogno ....

import org.junit.Test; 
import static org.junit.Assert.fail; 

public class BazTest { 

    @Test 
    public void doTest() { 
     for (Baz baz : Baz.values()) { 
      System.out.println("Baz " + baz + " has opposite: " + baz.getOpposite()); 
      if (baz.getOpposite() == null) { 
       fail("Opposite is null"); 
      } 
     } 
    } 
} 
+0

Questa risposta non è corretta: l'offuscamento con la classe anonima non rallenta la lettura del campo statico, in particolare, 'Baz.yin.oppositeHolder.opposite' è' null'. Tutto ciò non fa altro che confondere il compilatore abbastanza da non avvertire più la lettura di un campo finale statico prima che venga assegnato. – meriton

+0

Abbastanza vero, intendevo modificare questo, ma ho dimenticato. Aggiornato ora con una versione funzionante. – Pool

+0

... e come faresti a garantire che setOpposite venga richiamato prima che qualcuno legga il contrario? – meriton

Problemi correlati