Java随机数生成器在设置种子后始终返回相同的数字?

31

我需要帮助,因为我正在创建一个随机数生成器。我的代码如下(在名为"numbers"的类中):

public int random(int i){
    Random randnum = new Random();
    randnum.setSeed(123456789);
    return randnum.nextInt(i);
}

当我从另一个类中调用此方法(以生成随机数),它总是返回相同的数字。例如,如果我这样做:

System.out.println(numbers.random(10));
System.out.print(numbers.random(10));

代码总是打印相同的数字,例如5 5。我该怎么做才能打印出两个不同的数字,例如5 8。

我必须设置种子。

谢谢。


8
每次调用函数时不要创建一个新的Random对象。将其作为私有变量保留,并仅实例化一次。 - Rob
你要将种子设置为什么? - Steven Benitez
手动设置种子不推荐,除非您确切知道它的含义。 - m0skit0
任何考虑使用算术方法生成随机数字的人,当然是处于一种罪恶状态。 - Steve Kuo
7个回答

51
你需要在整个类中共享Random()实例:
public class Numbers {
    Random randnum;

    public Numbers() {
        randnum = new Random();
        randnum.setSeed(123456789);
    }

    public int random(int i){
        return randnum.nextInt(i);
    }
}

41

如果你总是设置种子,你就会总是得到相同的答案。这就是设置种子的作用。


17
有两个问题导致了你所看到的情况。第一个问题是代码为Random实例设置了种子值。第二个问题是实例方法“random”每次都会实例化一个新的Random对象,然后立即使用相同的种子设置其种子。这两者的组合保证了对于相同的i值,“random”方法将始终返回相同的值,并且它将始终是种子生成的序列中的第一个值。
假设设置种子是必需的,为了获得序列中的下一个值而不是每次都获得序列的相同第一个值,randnum Random实例不能在每次调用其next方法之前都设置其种子。为了解决这个问题,将Random局部变量实例从random实例方法的范围移动到类范围。其次,只有在将random分配给Random实例或只要重新开始获取相同的结果序列时才设置种子。Random类的setSeed(long seed)实例方法无法在类范围内执行,因此构造函数必须使用带有长种子参数的Random构造函数进行设置。以下代码显示了更改:
public class RandomDemo { // arbitrary example class name
    // lots of class related stuff may be here...

    // still inside the class scope...
    // private is a good idea unless an external method needs to change it
    private Random randnum = new Random(123456789L);
    // the seed guarantees it will always produce the same sequence
    // of pseudo-random values when the next methods get called
    // for unpredicable sequences, use the following constructor instead:
    // private Random randnum = new Random();

    // lots of code may be here...

    // publicly exposed instance method for getting random number
    // from a sequence determined by seed 123456789L
    // in the range from 0 through i-1
    public int randnum(int i) {
        // don't set the seed in here, or randnum will return the exact same integer
        // for the same value of i on every method call
        // nextInt(i) will give the next value from randnum conforming to range i
        return randnum.nextInt(i);
    } // end randnum

    // lots of more code may be here...

} // end class RandDemo

以上将根据所述的问题给出确切的解决方案。然而,使用强制种子似乎不太常见,考虑到它的作用。

如果这是一个课程项目或软件测试,其中序列必须可预测和可重复,将种子设置为固定值是有意义的。否则,应质疑将种子设置为某个预定值的有效性。以下内容更详细地解释了Random、Random种子以及为什么需要提供种子的原因。

Random有两个构造函数:

Random()

并且

Random(long seed)

以及一个实例方法

setSeed(long seed)

所有这些都会影响从Random实例中获得的数字序列。其中,实例方法,

setSeed(long seed)

将Random对象设置为与构造函数参数相同种子的状态。只使用种子值的低位48位。

如果没有指定种子,将使用系统当前时间的毫秒数作为种子。这确保了除非在同一毫秒内实例化两个Random对象,否则它们将生成不同的伪随机序列。只使用种子值的低位48位。这会导致不可预测的伪随机序列。每次调用next方法时都获取新的Random实例是不必要且浪费计算资源的。

提供Random的种子参数是为了可以实例化一个能够生成可重复序列的Random对象。对于给定的种子,next方法中的值序列在每次使用该种子时保证是相同的序列。这对于需要使用伪随机序列并需要结果可预测和可重复的软件测试非常有用。但在操作中创建不同的不可预测的伪随机序列是没有用处的。

声明“必须设置种子”否定了Random对象的伪随机序列的任何不可预测性。这是否是用于类项目或软件测试,其中程序的输入相同时结果必须相同?


4

在启动时设置种子,而不是每次需要新的随机数时都设置。


3
您正在使用的不是真正的随机数生成器,而是伪随机数生成器。PRNG会生成伪随机数序列,种子选择了序列中的起始点(PRNG可能会生成一个或多个序列)。

2
通常情况下,Random 并不是真正的随机数,而是伪随机数
这意味着它需要一个给定的种子,并使用它来生成一系列看起来像随机数的数字(但是完全可预测如果你输入相同的种子,它会重复出现)。
如果您不提供种子,则第一个种子将从变量源(通常是纳秒级系统时间)中获取。
通常,使用带有种子的值可以使其重复生成精确的伪随机值(例如用于测试)。
建议的解决方案:
  • 不要使用带有种子的Random()
  • 在类的构造函数中仅初始化一次具有种子的Random(long seed),然后每当需要获取伪随机数时调用nextInt

2
你一定要在你的random(int i)方法中创建new Random()吗?如果你必须这样做,你可以将种子设置为当前时间,尽管这并不是完全可靠的,因为你可能会连续快速地调用你的numbers.random(10),导致它最终成为相同的种子。你可以尝试使用纳秒 (System.nanoTime(),如果setSeed只接受int,那么我想你需要将其乘以一个数字)。
然而,如果你被允许这样做,我建议你在方法外部声明你的Random变量。如果你在你的number类构造函数中实例化你的随机变量,你可以设置任何种子,任何时候调用你的方法都会给你一个新的数字。(如果你使用一个恒定的种子,它们每次重新启动应用程序时都会是相同的数字集合,但是你也可以在这种情况下使用时间作为你的种子)。
最后一个问题是,如果你同时声明了几个number类,它们将拥有相同的随机种子,并给你相同的一组随机数。如果发生这种情况,你可以在你的主类中创建一个静态Random,并在你的numbers类中调用它。虽然这将耦合这两个类,但它会起作用。另一个选择是为每个你实例化的number,向你的number类构造函数发送一个递增的值,并使用你传递的值作为种子。
如果你被允许这样做,第二个选项应该对你有好处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接