2012-09-25 8 views
7

Tengo una enumeración como la siguiente, pero eclipse dice que hay errores en la primera definición de cada par opuesto.Java enumeración- No se puede hacer referencia a un campo antes de que se defina

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; 
    } 
} 

Lo que quiero lograr es ser capaz de utilizar Baz.something.opposite para obtener el objeto opuesta de Baz.something. ¿Hay una posible solución para esto? ¿Tal vez un marcador de posición vacío para yang y bad antes de yin y good se definen en este ejemplo?

Respuesta

9

Usted podría intentar algo como:

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); 
    } 
} 

y luego hacer referencia a ella como

Baz.something.getOpposite() 

Eso debería lograr lo que usted está buscando para hacer buscando el valor de enumeración por ella de representación de cadena. No creo que puedas hacer que funcione con la referencia recursiva a Baz.

+0

+1 Tienes razón, no se puede conseguir que funcione utilizando los objetos reales porque 'evil' ganó' t se definirá hasta que 'good' ya esté inicializado, pero cambiarlos no funciona porque entonces 'good' no se define cuando 'evil' se inicializa. Buena idea para usar los nombres :) – Brian

+0

Reemplazar los valores enum escritos con cadenas sin tipo no es una buena solución. El objetivo de las enumeraciones es proporcionar seguridad de tipo a las constantes, y las cadenas no te dan eso. Además, hay soluciones mucho mejores: hacer esto en un bloque de inicializador estático, como señala @meriton en su respuesta. –

10

Con la sentencia 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 diferida de inicialización:

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

    public Baz opposite; 

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

Es posible que desee hacer que el campo mutable privada y proporcionar un captador.

+0

No había oído hablar de la inicialización diferida, parece una buena solución, pero la versión String de jcern parece ser el mejor método. – Zaq

+1

+1 Para su segunda sugerencia. Odio la idea de 'cambiar'. – OldCurmudgeon

+0

Plus 1 para la inicialización estática –

0

Y aún otra posible implementación (similar a algunas de las otras soluciones, pero con un 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

¿Acaba de copiar y pegar la respuesta de OldCurmudgeon? 0.o – Zaq

+0

¡Bueno, usó un HashMap en lugar de EnumMap, eso cuenta! : D Pero sí, el patrón del mapa es bastante sencillo. – Natix

+0

No, no lo hizo. Publicamos al mismo tiempo. Tenga en cuenta que utilizo un 'EnumMap' en lugar de un' HashMap'. Es espeluznante, ¿eh? – OldCurmudgeon

1

¿Qué tal 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

Una alternativa más :) usando un mapa. Es bastante detallado, pero de esta manera puede definir cada par solo una vez, se infiere la otra dirección.

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, me gusta Meriton 's segundo ejemplo de lo mejor.

0

Y luego está la solución 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í, puede ser una exageración ... Esta es una respuesta excelente y completa, pero creo que una respuesta más simple puede ser mejor en este caso . (también, puedo agregar un valor que sea el opuesto, en cuyo caso, esto no es ideal) – Zaq

+0

Estoy totalmente de acuerdo. Esto fue más por un poco de diversión. Creo que la segunda sugerencia de @ meriton es la mejor. – OldCurmudgeon

1

Años después, la solución más corta y más hacky

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

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

Se basa en la suposición de que cada miembro tiene un opuesto y que están dispuestos por pares. Reemplaza el campo por un método con la esperanza de que la JVM optimice toda la sobrecarga. Esto es razonable en el escritorio, menos razonable en Android.

Para eliminar la sobrecarga, podría usar el inicializador estático como muchas otras soluciones aquí.

+0

No es años después, solo unos pocos meses. Esta es una buena respuesta y me gusta su simplicidad. Prefiero las minúsculas en las instancias Java enum porque son prácticamente las mismas que las propiedades finales estáticas. ¿La JVM realmente optimizaría lo opuesto? No estoy seguro de cuánto optimiza la JVM. – Zaq

+0

@ user1494396 Llamada de método corto simple get * always * inlined siempre que la JVM crea que es relevante para el rendimiento. La llamada a 'values ​​()' normalmente [incluye clonación] (http://stackoverflow.com/a/1163121/581205), pero este es el caso más fácil para [escape analysis] (http://docs.oracle.com /javase/7/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis). Así que espero que no quede nada más que carga de campo, xor y carga de campo estática. Esto sigue siendo 3 veces más trabajo que con un campo, pero creo que es difícil encontrar un ejemplo en el mundo real, donde se pueda medir. – maaartinus

+0

@ user1494396 ¿Qué más debería escribirse en mayúsculas si no en enumeraciones? Las convenciones de codificación dicen que las constantes deberían ser, pero no explican qué es una constante. Tal vez una [constante en tiempo de compilación] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.28), tal vez una variable final (estática?) Que apunta a un objeto inmutable? – maaartinus

1

También puede usar métodos abstractos para retrasar, que tiene ventajas de seguridad del tipo sobre la respuesta aceptada.

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(); 
    } 

} 

Y código de prueba, porque lo necesitaba ....

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

Esta respuesta es incorrecta: la ofuscación con la clase anónima no retrasa la lectura del campo estático, específicamente, 'Baz.yin.oppositeHolder.opposite' es' null'. Todo lo que hace es confundir al compilador lo suficiente como para que ya no advierta sobre la lectura de un campo final estático antes de que se le asigne. – meriton

+0

Es cierto, quise editar esto, pero lo olvidé. Actualizado ahora con una versión de trabajo. – Pool

+0

... ¿y cómo se aseguraría de que setOpposite se invoque antes de que alguien lea lo contrario? – meriton

Cuestiones relacionadas