2009-12-29 2 views
107

Se ho un enum come questo:Java: scegli un valore casuale da un enum?

public enum Letter { 
    A, 
    B, 
    C, 
    //... 
} 

Qual è il modo migliore per scegliere uno a caso? Non è necessario che la qualità della produzione sia a prova di proiettile, ma una distribuzione abbastanza uniforme sarebbe piacevole.

avrei potuto fare qualcosa di simile

private Letter randomLetter() { 
    int pick = new Random().nextInt(Letter.values().length); 
    return Letter.values()[pick]; 
} 

Ma c'è un modo migliore? Sento che questo è qualcosa che è stato risolto prima.

+0

cosa ne pensi è sbagliato con la soluzione? Mi sembra abbastanza buono. –

+1

@GregS - il problema è che ogni chiamata a 'Letter.values ​​()' deve creare una nuova copia dell'array di valori 'Lettera' interno. –

+0

dannazione, stavo per fare la stessa domanda. nice one (+1) –

risposta

104

L'unica cosa che suggerirei è il caching del risultato di values() perché ogni chiamata copia una matrice. Inoltre, non creare un Random ogni volta. Tiene uno. A parte questo, quello che stai facendo va bene. Quindi:

public enum Letter { 
    A, 
    B, 
    C, 
    //... 

    private static final List<Letter> VALUES = 
    Collections.unmodifiableList(Arrays.asList(values())); 
    private static final int SIZE = VALUES.size(); 
    private static final Random RANDOM = new Random(); 

    public static Letter randomLetter() { 
    return VALUES.get(RANDOM.nextInt(SIZE)); 
    } 
} 
+7

Se lo ritieni utile, puoi creare una classe di utilità per farlo. Qualcosa come RandomEnum con un costruttore che riceve la Classe per la creazione dell'elenco. – helios

+12

Davvero non vedo il punto di convertire la matrice 'values ​​()' in una lista non modificabile. L'oggetto 'VALUES' è già incapsulato in virtù dell'essere dichiarato' privato'. Sarebbe più semplice e più efficiente renderlo 'letter Letter finale privato [] VALUES = ...'. –

+4

Gli array in Java sono mutabili, quindi se si dispone di un campo array e lo si restituisce in un metodo pubblico, il chiamante può modificarlo e modifica l'archivio privato, quindi è necessario copiare l'array in modo difensivo. Se chiami quel metodo un sacco di volte può essere un problema quindi lo metti in una lista immutabile invece di evitare una copia difensiva non necessaria. – cletus

37

Combinando i suggerimenti di cletus e helios,

import java.util.Random; 

public class EnumTest { 

    private enum Season { WINTER, SPRING, SUMMER, FALL } 

    private static final RandomEnum<Season> r = 
     new RandomEnum<Season>(Season.class); 

    public static void main(String[] args) { 
     System.out.println(r.random()); 
    } 

    private static class RandomEnum<E extends Enum> { 

     private static final Random RND = new Random(); 
     private final E[] values; 

     public RandomEnum(Class<E> token) { 
      values = token.getEnumConstants(); 
     } 

     public E random() { 
      return values[RND.nextInt(values.length)]; 
     } 
    } 
} 

Edit: Oops, ho dimenticato il parametro di tipo limitato, <E extends Enum>.

+1

Vedere anche [* Class Literals come token di tipo Runtime *] (http://docs.oracle.com/javase/tutorial/extra/generics/literals.html). – trashgod

3

Se si esegue questa operazione per i test, è possibile utilizzare Quickcheck (this is a Java port I've been working on).

import static net.java.quickcheck.generator.PrimitiveGeneratorSamples.*; 

TimeUnit anyEnumValue = anyEnumValue(TimeUnit.class); //one value 

Supporta tutti i tipi primitivi, composizione tipo, collezioni, funzioni di distribuzione differenti, limiti ecc Ha il supporto per i corridori l'esecuzione di più valori:

import static net.java.quickcheck.generator.PrimitiveGeneratorsIterables.*; 

for(TimeUnit timeUnit : someEnumValues(TimeUnit.class)){ 
    //..test multiple values 
} 

Il vantaggio di QuickCheck è che è possibile definire test basati su un specification dove TDD semplice funziona con scenari.

+0

sembra intrigante. Dovrò fare un tentativo. –

+0

Puoi mandarmi una mail se qualcosa non funziona. Dovresti usare la versione 0.5b. –

4
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)]; 
78

Un unico metodo è tutto ciò che serve per tutte le enumerazioni casuali:

public static <T extends Enum<?>> T randomEnum(Class<T> clazz){ 
     int x = random.nextInt(clazz.getEnumConstants().length); 
     return clazz.getEnumConstants()[x]; 
    } 

Quale che verrà utilizzato:

randomEnum(MyEnum.class); 

anche io preferisco usare SecureRandom come:

private static final SecureRandom random = new SecureRandom(); 
2

Probabilmente è easi est per avere una funzione per selezionare un valore casuale da una matrice. Questo è più generico ed è semplice da chiamare.

<T> T randomValue(T[] values) { 
    return values[mRandom.nextInt(values.length)]; 
} 

chiamata in questo modo:

MyEnum value = randomValue(MyEnum.values()); 
2

E'eaiser per implementare una funzione random sul enum.

public enum Via { 
    A, B; 

public static Via viaAleatoria(){ 
    Via[] vias = Via.values(); 
    Random generator = new Random(); 
    return vias[generator.nextInt(vias.length)]; 
    } 
} 

e poi si chiamano dalla classe è necessario in questo modo

public class Guardia{ 
private Via viaActiva; 

public Guardia(){ 
    viaActiva = Via.viaAleatoria(); 
} 
8

D'accordo con Stphen C & helios. Modo migliore per andare a prendere elemento casuale Enum è:

public enum Letter { 
    A, 
    B, 
    C, 
    //... 

    private static final Letter[] VALUES = values(); 
    private static final int SIZE = VALUES.length; 
    private static final Random RANDOM = new Random(); 

    public static Letter getRandomLetter() { 
    return VALUES[RANDOM.nextInt(SIZE)]; 
    } 
} 
10

linea singola

return Letter.values()[new Random().nextInt(Letter.values().length)]; 
2

Ecco una versione che utilizza riordino e torrenti

List<Direction> letters = Arrays.asList(Direction.values()); 
Collections.shuffle(letters); 
return letters.stream().findFirst().get(); 
2

Questo è probabilmente il modo più conciso per raggiungere il tuo obiettivo. Tutto quello che devi fare è chiamare Letter.getRandom() e otterrai una lettera enum casuale.

public enum Letter { 
    A, 
    B, 
    C, 
    //... 

    public static Letter getRandom() { 
     return values()[(int) (Math.random() * values().length)]; 
    } 
} 
3

userei questo:

private static Random random = new Random(); 

public Object getRandomFromEnum(Class<? extends Enum<?>> clazz) { 
    return clazz.values()[random.nextInt(clazz.values().length)]; 
}