从枚举中选择一个随机值?


161

如果我有这样的枚举:

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

随机挑选一个的最佳方法是什么?它不需要是生产质量的防弹产品,但是相当均匀的分配将是不错的选择。

我可以做这样的事情

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

但是有更好的方法吗?我觉得这已经解决了。


您认为解决方案有什么问题?对我来说看起来不错。
总统詹姆斯·波尔克(James K. Polk)2009年

1
@GregS-问题在于,每次调用Letter.values()都必须创建内部Letter值数组的新副本。
斯蒂芬·C

Answers:


143

我唯一建议的是缓存结果,values()因为每个调用都会复制一个数组。另外,不要Random每次都创建一个。保留一个。除此之外,您在做什么都很好。所以:

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

8
如果您认为它有用,则可以使它成为一个实用程序类。类似RandomEnum <T的方法扩展了Enum>的构造方法,该构造方法接收Class <T>来创建列表。
helios

15
我真的看不到将values()数组转换为不可修改的列表的意义。该VALUES对象已经通过声明被封装private。这样做会更简单,更有效private static final Letter[] VALUES = ...
斯蒂芬·C

4
Java中的数组是可变的,因此,如果您有一个数组字段并以公共方法返回它,则调用者可以对其进行修改,并且它会修改私有字段,因此您需要防御性地复制该数组。如果您多次调用该方法,则可能会遇到问题,因此应将其放在不可变列表中,以避免不必要的防御性复制。
cletus

1
@cletus:Enum.values()将在每次调用时返回一个新数组,因此无需在其他地方传递/使用它之前将其包装。
CHII

5
私有静态最终字母[]值...可以。它是私人的,因此不可改变。您只需要公共的randomLetter()方法,该方法显然会返回单个值。斯蒂芬·C是对的。
helios 2009年

126

所有随机枚举只需要一个方法:

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

您将使用:

randomEnum(MyEnum.class);

我还更喜欢将SecureRandom用作:

private static final SecureRandom random = new SecureRandom();

1
正是我想要的。我的行为很像被接受的答案,当我需要从第二个Enum中进行随机化时,这给我留下了样板代码。此外,有时很容易忘记SecureRandom。谢谢。
Siamaster '18

您读懂了我的想法,正是我想要在随机实体生成器测试类中添加的内容。感谢您的帮助
Roque Sosa

43

合并的建议克莱图斯太阳神

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<E>> {

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

编辑:糟糕,我忘记了有界类型参数<E extends Enum<E>>



1
我知道答案很老,但这不是E extends Enum<E>吗?
Lino-投票不要说谢谢

1
@Lino:为清楚起见进行了编辑;我认为参数绑定的正确类型推断不是必需的,但是我欢迎进行更正。另请注意,new RandomEnum<>(Season.class)自Java 7起允许使用
。– trashgod

这个单一RandomEnum类将是很好的为微型图书馆,如果你想将其捆绑起来,并发布到中央。
格雷格·查巴拉


10

同意Stphen C和helios。从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)];
  }
}

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

5

这可能是实现目标的最简洁方法。您所要做的就是打电话给Letter.getRandom()您,您会得到一个随机的枚举字母。

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

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

4

具有从数组中选择随机值的函数可能是最简单的。这是更通用的,并且很容易调用。

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

像这样打电话:

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

4

这里是使用随机播放和流的版本

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

4

简单的Kotlin解决方案

MyEnum.values().random()

random()Collection对象的基础Kotlin中包含的默认扩展功能。Kotlin文档链接

如果您想使用扩展功能对其进行简化,请尝试以下操作:

inline fun <reified T : Enum<T>> random(): T = enumValues<T>().random()

// Then call
random<MyEnum>()

使它在您的枚举类上静态。确保导入my.package.random您的枚举文件

MyEnum.randomValue()

// Add this to your enum class
companion object {
    fun randomValue(): MyEnum {
        return random()
    }
}

如果您需要从枚举实例执行此操作,请尝试此扩展

inline fun <reified T : Enum<T>> T.random() = enumValues<T>().random()

// Then call
MyEnum.VALUE.random() // or myEnumVal.random() 

3

如果您这样做是为了进行测试,则可以使用Quickcheck这是我一直在使用的Java端口)。

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

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

它支持所有原始类型,类型组成,集合,不同的分布函数,范围等。它支持跑步者执行多个值:

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

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

Quickcheck的优势在于,您可以基于规范来定义测试,而普通TDD可以与场景一起使用。


看起来很有趣。我得试试看。
尼克·海纳尔

如果出现问题,您可以给我发邮件。您应该使用0.5b版本。
Thomas Jung,

2

易于在枚举上实现随机函数。

public enum Via {
    A, B;

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

然后从类中调用它,就像这样

public class Guardia{
private Via viaActiva;

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


1

我猜想这种单行返回方法足够有效,可以用于如此简单的工作:

public enum Day {
    SUNDAY,
    MONDAY,
    THURSDAY,
    WEDNESDAY,
    TUESDAY,
    FRIDAY;

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

    public static void main(String[] args) {
        System.out.println(Day.getRandom());
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.