为什么java中很多范围取值都是左闭右开呢?
今天在写随机数的时候,注意到左闭右开,突然就想java中很多范围都是取左闭右开(二分查找,分治,for循环,数组下标,list等等)这仅仅是程序员的习惯吗?
我们拿字符串举例
String a = "0123456789";
System.out.println(a.substring(0,0)); //输出为“”,什么都不显示
System.out.println(a.substring(0,1)); //0
System.out.println(a.substring(0,2)); //01
System.out.println(a.substring(10,10)); // 输出为“”,什么都不显示
System.out.println(a.substring(2,3)); //2
可以看到substring这个方法就是左闭右开,当取substring(0,0)时候是什么都不显示的,也就是说取值是0<= x <0这样的值是不存在的,而取a.substring(0,1)即0<=x<1 那么x能取到的整数也就是0了,所以输出下下标为0的数字。
说到这里还是没有说为什么,别急重要的来了
问题讨论
关于这个问题,其实还有一位伟大的数学家曾经讨论过他的合理性。
这个人就是Dijkstra,他也是离散数学中应用广泛的最短路径算法的提出者,并且还提出了银行家算法
他首先提出一个问题,让我们通过一个条件表达式表示 2,3,4,5,6,7,8,9,10,11,12 这11个数字,其实一般有以下四种写法:
- a) 2 ≤ i < 13
- b) 1 < i ≤ 12
- c) 2 ≤ i ≤ 12
- d) 1 < i < 13
这几种也是我们在写for循环的时候可能会用到的一些表示式,那着四种写法有没有好坏之分呢?
我们其实可以观察到,a) 和 b)有个优点,上下边界的相减得到的差,正好等于子序列的长度,即
13-2 = 12-1 = 11
; 这样的写法可以让我们快速知道这个表示表达式中一共包含多少个自然数。接下来,Dijkstra分别从表达式的上下界讨论了到底使用
≤
还是<
更合理。首先,他论证了一下表达式的下界使用哪种形式合理。
他认为,当我们想要表达自然数2-12的时候,如果使用
1 < i
作为这个序列的下界的话,这个下界的起始值进入了非自然数的区域。而使用2 ≤ i
,那么就可以严格的保证这个下界就是一个自然数2 。所以,他认为下界使用≤
更加合理。符合这种形式的就是a) 和 c)两种。那么a) 和 c)还有一个区别,就是上界一个用了
≤
一个用了<
,那该使用哪种方式更加合适呢?Dijkstra提出,如果想要表达一个空序列,使用a) 形式可以很容易的表达,如
0<= i <0
就可以表示一个空序列。但是如果上界和下界都用<=
就无法表示了,除非用1 <= i <= 0
,但是这种形式就很不合逻辑。
所以,综上,他认为a) 2 ≤ i < 13
这种表达方式更加合理一些。
也就是说,使用左闭右开的形式定义表达式合理也更加优雅!
结论
左闭右开的方式更利于表达序列的长度。
如数组中10个数字,通常下标为0-9,10取不到,但是为了能看出这个数组的长度,我们一般写for循环都是for(int i=0;i<10;i++) 这样既保证数组不越界,又保证序列长度可知
表示空序列更符合逻辑
如本文开篇所示例的substring(0,0)
附上随机数生成的三种方式
随机数的写法(三种)
new Random()借助java.util.Random类来产生一个随机数发生器,也是最常用的一种,构造函数有两个
- Random()
- Random(long seed) 指定种子值(种子就是产生随机数的第一次使用值,机制是通过一个函数,将这个种子的值转化为随机数空间中的某一个点上,并且产生的随机数均匀的散布在空间中。以后产生的随机数都与前一个随机数有关)
//示例 public static void main(String[] args) { Random r = new Random(1);//可以尝试不同种子值所产生的随机数分布 for (int i = 0; i < 5; i++) { int ran1 = r.nextInt(100);//生成[0,100)之间的随机数 注意左闭右开 System.out.println(ran1); } }
Math.random():返回的数值是 [0.0,1.0) 的double型数值,由于double类数的精度很高,可以在一定程度下看做随机数,借助(int)来进行类型转换就可以得到整数随机数了,代码如下。
public static void main(String[] args) { int max = 100, min = 1; int ran2 = (int) (Math.random() * (max - min) + min); System.out.println(ran2); }
currentTimeMills():方法返回从1970年1月1日0时0分0秒(这与UNIX系统有关)到现在的一个long型的毫秒数,取模之后即可得到所需范围内的随机数。
public static void main(String[] args) { int max = 100, min = 1; long randomNum = System.currentTimeMillis(); int ran3 = (int) (randomNum % (max - min) + min); System.out.println(ran3); }
以第一种方法为例,抽象出来一个生成[min,max]之间的数左右都闭
int randNumber =rand.nextInt(MAX - MIN + 1) + MIN;