有了随机数,生活总算有点变化了

相信同学们现在都可以书写一个简单的Java代码了,知道如何给一个变量赋值,也知道怎么接受键盘的输入。
但这些值都是定值,在我们使用前就知道了,感觉很是无趣,那我们能不能在我们的代码里添加一些使用前不确定的值呢?
答案是可以的,在Java中我们把这种不确定的,在使用前每次都不一样的数字称为随机数
接下来就给大家简单讲解下这个不确定的随机数。

什么是随机数?

随机数:简单的说就是在程序运行中生成一个不能确定的值,而且每次生成的数都是不确定的,随机的。
比如说:在登录一些网站时,屏幕上显示的验证码就是一个随机数。每一次生成的验证码都是不确定的,随机的,这就是我们程序中的随机数。
当然,除了上述的随机数,游戏中的概率性技能都会有随机数的影子,同样,我们生活中也有很多随机数的例子。
如:买彩票。彩票对于我们每一个人都不算陌生,知道彩票的人都应该了解,每一期的彩票号码都是随机的,不确定的,这里的彩票号码就是一个随机数。
当然,除了彩票、验证码等这些例子,还有很多使用了随机数的例子,就不一一列举了。

在Java程序中产生一个随机数呢?

在Java中,如果我们想要产生一个随机数,有两种方式:

方式一

使用java.lang.Math类中的random()方法
先给大家举个例子:

1
2
3
4
5
public class TestMath {
public static void main(String[] args) {
double num = Math.random();
}
}

在上述代码中,大家可以看到可以直接使用Math类来调用random()方法。
相信聪明的你已经发现了,我们并没有使用import关键字去导入什么Math类,原因是Math类在java.lang包中默认会导入。

其次,我们是使用

1
Math.random();

的方式来得到一个double类型的数据,也就是说random()方法是静态方法,无需使用Math类的对象调用,并且该方法返回的是一个double类型的数据,当然,输出num就可以得到一个[0,1)的数字,而且每次运行的结果都是不确定的**。
当然,在这里有同学会问了:拿到这个[0,1)的数字有啥用,这个区间也太小了吧,那我要是想要一个更大范围的int数字怎么弄了?
下面给大家看看,我们如何去生成一个更大范围的int的随机数字:
1
2
3
4
5
public class TestMath {
public static void main(String[] args) {
int num = (int)(Math.random()*50)+10;
}
}

既然我们可以得到一个[0,1)的数字,那我们就可以把这个数字进行处理,大家看看,上述的num的取值范围是多少啊?
答案是:[10,59],相信这个问题难不倒大家,一个[0,1)的数字
50,区间变大为:[0,50)再强转为int类型得[0,49],加上10,最终得到[10,59],那么上述的num则可以得到[10,59]之间一个随机的整数。
当然,写法的不同会对产生的随机数造成不同的影响:
大家来看看下面的几种随机数的取值范围:
1
2
3
int num1 = (int)(Math.random() * 10) + 10;
int num2 = (int)Math.random() * 10 + 10;
int num3 = (int)(Math.random() * 10 + 10);

相信大家很快就可以找出这三段代码的不同,在执行中会发现,不管怎么运行,num2的值永远都为10,怎么回事呢?
原因是,在num2产生随机数的过程中,是将[0,1)先强转为int类型,这下就尴尬了,不管是什么0.1还是0.999…,只要一强转,统统的都转为了0,然后*10,也为0,再加上10,结果永远都为10了。

所以请同学们一定要注意:在使用Math生成随机数的过程中如果需要强转为其它类型,一定要注意是应该先强转,还是后强转,一定要想清楚再写,不然会出现上述的问题。

而生成num1和num3的这两种写法都是一样的,区别在于:
num1:先得到double类型的[0,1)→再10得[0,10)→再强转为int类型的[0,9]→再+10→得[10,19];
num2:先得出double类型的[0,1)→再
10得[0,10)→再+10→得[10,20),再强转为int类型[10,19];
上面给大家讲解了如何获得一个随机数。

那我们如何获得一个指定区间的随机数呢?

在上面的讲解中,相信同学们都可以按照代码算出其区间,那如果要写出能产生指定区间的随机数,那我们该如何书写呢?
其实并不难,不知道同学们在刚刚的代码中有没有发现:

1
2
3
4
5
public class TestMath {
public static void main(String[] args) {
int num1 = (int)(Math.random() * 10) + 10;
}
}

上述代码中,我们可以按照代码的执行过程来描述出:
num1:先得到double类型的[0,1)→再10得[0,10)→再强转为int类型的[0,9]→再+10→得[10,19];
那如果我们有一个区间[3,15],那我们能不能反过来求出该随机数的代码呢?
目标很明确,根据区间[3,15]来逆推出代码,也就是完成(int)(Math.random()
A)+B;中A,B的值
不难发现,强转为int类型的[0,9]→再+10→得[10,19]

那开始进行反推:
(1)第一步:[3,15]如何得到起始位0的区间[0,?),其中?是需要求出来的数,那简单了,同时-3不就好了→-3得[0,12],而B就应该是3
(2)第二步:得出强转为int型之前的区间–由[0,10)→再强转为int类型的[0,9]→得知强转前的区间为[0,13);
(3)第三步:由[0,13)来找出[0,1)乘了多少→*13,而A就应该是13
(4)第四步:还原代码为:

1
int num1 = (int)(Math.random() * 13) + 3;

不知道你是否理解了
我们再来一个例题:计算得出区间为[-3,10]的代码
(1)同上面的步骤一样,先得把区间变为[0,?]→得[0,13],+3(注意:这里加了3,反过来应该-3)–B:-3;
(2)变成强转前的样子→[0,14)
(3)算出乘了多少→*15–A:15
(4)第四步:还原代码为:

1
int num1 = (int)(Math.random() * 15) - 3;

从上述代码中不难找到区间与代码中的关系
(1)区间的起始值和B一致,包括正负号
(2)区间的(末值-起始值+1)的结果等于A的值
验算一次:求出随机数范围为[6-21]的区间
OK,按照推出的结论:B:6,A:(21 - 6 + 1) = 16;

最终结果为:

1
2
//求取[6-21]区间的随机数
int num1 = (int)(Math.random() * 16) + 6;

怎么样,简单吧!
上面已经给大家讲解了第一种随机数的产生方式与求得指定区间随机数的算法。
接下来给大家介绍第二种产生随机数和生成指定区间的方式。

方式二

使用java.util.Random类中的方法
在Random类中也有相应的产生随机数方法,并且,在Random类中,可以产生不同类型的随机数。
代码演示如下:

1
2
3
4
Random ran = new Random();
ran.nextBoolean();
ran.nextDouble();
ran.nextInt();

注意事项一:由于Random类并不是java.lang包中的类,所以需要使用import关键字来导入java.util.Random;
注意事项二:从上述代码中,同学们可以了解到,在Random类中想要得到一个随机数,需要先创建Random类的一个对象,然后再通过对象来调用产生随机数的方法,当然这点比Math类要麻烦一点。
注意事项三:虽然Random类在使用上要比Math类麻烦一点点,但是Random类不仅可以创建整型的随机数,还可以创建实型、布尔型等等类型的随机数,所以在不同的应用领域上,Math和Random有着不同的使用优点。

这里我们只拿整型的来举例,如果同学们感兴趣,可以在网上自行查阅。
如果我们要生成一个简单的[1,10]的随机数,则:

1
2
3
4
5
6
public class TestMath {
public static void main(String[] args) {
Random ran = new Random();
int num = ran.nextInt(10)+1;
}
}

从上述代码中看到:使用Random类,需要先创建Random类对象然后再调用nextInt()方法。
不过和Math不同的是:nextInt()可以传入一个数字,而这个数字相当于*10的意思。
也就是说,下列效果是相等的:

1
2
int num = ran.nextInt(10) + 1;
int num1 = (int) (Math.random() * 10) + 1;

同样,我们也可以按照Math的推导方式,推导出Random中生成指定区间的随机数的代码格式:
例:求出指定范围[17-32]的代码
int num = ran.nextInt(A) + B;
B:17,A:(32-17+1)= 16;

所以Math和Random在生成指定范围随机的代码上有很多相似之处。

小结

生成随机数的方式:1.使用Math类中的random方法;2.使用Random类中不同的next…()方法
相似之处:两种方法要得到指定区间的随机数的算法都是一样的。
不同之处:

  1. Math类是java.lang包中的类,不需要使用import关键字导入,而Random是java.util包中的,需要导入;
  2. Math类中的random()方法是静态方法,可以直接使用类名.的方式来调用,而Random类则需要先创建对象,再使用对象.的方式调用产生不同数据类型的next…()方法。

至此,希望同学们对在Java程序中如何产生随机数和如何产生指定区间的随机数的方法有所了解。
当然,当同学们想再进一步去探寻随机数的原理时会发现一个问题–程序中的随机数并不是真正意义上的随机数!!!
这是怎么一回事呢?
下面将继续给大家讲解随机数的相关内容。

Java中,我们所看到的随机数是假的?

随机数怎么还会是假的呢?
原来随机数也分为两种:真随机数和伪随机数。
那什么是真随机数,什么是伪随机数呢?
真随机数是:在某次产生过程中是按照实验过程中表现的分布概率随机产生的,其结果是不可预测的,是不可见的。
伪随机数是:在计算机中的随机数是按照一定算法模拟产生的,其结果是确定的,是可见的。
我们可以这样认为这个可预见的结果其出现的概率是100%。所以用计算机随机函数所产生的随机数并不随机,是伪随机数
伪随机数看似随机实质是固定的周期性序列,也就是有规则的随机,其由确定算法生成的,通过不断算法优化,使随机数更接近真随机数;
请注意一点:通过真实随机事件取得的随机数才是真随机数。

而Java随机数产生原理,是通过线性同余公式产生的,也就是说通过一个复杂的算法生成的–生成的是伪随机数
那伪随机数在程序中使用会有什么问题吗?
Java自带的随机数函数是很容易被黑客破解的,因为黑客可以通过获取一定长度的随机数序列来推出你的种子,然后就可以预测下一个随机数。
这个种子有什么作用呢?
种子就是产生随机数的第一次使用值,相同的种子会产生出相同的随机数。

不用种子的不随机性会增大的原因:
在Java中,Math.random()这个方法实际是在内部调用java.util.Random()的,并且使用一个和当前系统时间有关的数字作为种子数。两个随机数就很可能相同。

当然如果我们想去设置这个种子,可以让Random类的对象去调用setSeed(int long)方法,并传入一个long型的数字,不管什么时候产生随机数,这些随机数的值都相等。

例如:

1
2
3
4
5
6
7
8
9
public class TestMath {
public static void main(String[] args) {
Random rand = new Random();
for (int i = 0; i < 100; i++) {
rand.setSeed(100);
System.out.println(rand.nextInt(100));
}
}
}

上述代码中生成的随机数根据在setSeed()中传入同一个值时,会得到相同的数值。

总结

  1. 我们可以通过两个类:Math类和Random类的方法来得到随机数。

  2. 在得到随机数的基础上,我们还可以通过其推导过程来写出得到指定区间的随机数的代码。

  3. 在生成随机数是,如果我们使用同一个种子,不管生成多少个随机数,当你设定种子的时候,这些随机数是什么已经确定。相同次数生成的随机数字是完全相同的。

  4. 尽管我们可以通过不同的两个类的方法来生成随机数,但实际上,Java的随机数都是通过算法实现的,Math.random()本质上属于Random()类。

  5. 在Java中Math类可以直接通过random()方法得到一个double类型的随机数,而使用Random类中的不同方法,可以得到不同类型的随机数,相对来说Random类使用起来比较灵活一些。