6. java安全编码指南之:Number操作
简介
java中可以被称为Number的有byte,short,int,long,float,double和char,我们在使用这些Nubmer的 过程中,需要注意些什么内容呢?一起来看看吧。
Number的范围
每种Number类型都有它的范围,我们看下java中Number类型的范围:
考虑到我们最常用的int操作,虽然int的范围够大,但是如果我们在做一些int操作的时候还是可能超出int的范围。
超出了int范围会发送什么事情呢?看下面的例子:
public void testIntegerOverflow(){
System.out.println(Integer.MAX_VALUE+1000);
}
运行结果:-2147482649。
很明显Integer.MAX_VALUE+1000将会超出Integer的最大值范围,但是我们没有得到异常提醒,反而得到了一个错误的结果。
正确的操作是如果我们遇到了Overflow的问题,需要抛出异常:ArithmeticException。
怎么防止这种IntegerOverflow的问题呢?一般来讲,我们有下面几种方式。
- 第一种方式:在做Integer操作之前,进行预判断是否超出范围:
举个例子:
static final int safeAdd(int left, int right) {
if (right > 0 ? left > Integer.MAX_VALUE - right
: left < Integer.MIN_VALUE - right) {
throw new ArithmeticException("Integer overflow");
}
return left + right;
}
上面的例子中,我们需要进行两个整数相加操作,在相加之前,我们需要进行范围的判断,从而保证计算的安全性。
- 第二种方式:使用Math的addExact和multiplyExact方法:
Math的addExact和multiplyExact方法已经提供了Overflow的判断,我们看下addExact的实现:
public static int addExact(int x, int y) {
int r = x + y;
// HD 2-12 Overflow iff both arguments have the opposite sign of the result
if (((x ^ r) & (y ^ r)) < 0) {
throw new ArithmeticException("integer overflow");
}
return r;
}
看下怎么使用:
public int addUseMath(int a, int b){
return Math.addExact(a,b);
}
- 第三种方式:向上转型
既然超出了Integer的范围,那么我们可以用范围更大的long来存储数据。
public static long intRangeCheck(long value) {
if ((value < Integer.MIN_VALUE) || (value > Integer.MAX_VALUE)) {
throw new ArithmeticException("Integer overflow");
}
return value;
}
public int addUseUpcasting(int a, int b){
return (int)intRangeCheck((long)a+(long)b);
}
上面的例子中,我们将a+b转换成了两个long相加,从而保证不溢出范围。
然后进行一次范围比较,从而判断相加之后的结果是否仍然在整数范围内。
- 第四种方式:使用BigInteger
我们可以使用BigInteger.valueOf(a)将int转换成为BigInteger,再进行后续操作:
public int useBigInteger(int a, int b){
return BigInteger.valueOf(a).add(BigInteger.valueOf(b)).intValue();
}
区分位运算和算数运算
我们通常会对Integer进行位运算或者算数运算。虽然可以进行两种运算,但是最好不要将两种运算同时进行,这样会造成混淆。
比如下面的例子:
x += (x << 1) + 1;
上面的例子是想做什么呢?其实它是想将3x+1的值赋给x。
但是这样写出来让人很难理解,所以我们需要避免这样实现。
再看下面的一个例子:
public void testBitwiseOperation(){
int i = -10;
System.out.println(i>>>2);
System.out.println(i>>2);
System.out.println(i/4);
}
本来我们想做的是将i除以4,结果发现只有最后一个才是我们要的结果。
我们来解释一下,第一个i>>>2是逻辑右移,将会把最左边的填充成0,所以得出的结果是 一个正值1073741821。
第二个i>>2是算数右移,最左边的还是会填充成1,但是会向下取整,所以得出结果是-3.
直接使用i/4,我们是向上取整,所以得出结果是-2.
注意不要使用0作为除数
我们在使用变量作为除数的时候,一定要注意先判断是否为0.
兼容C++的无符号整数类型
在java中只有16位的char表示的是无符号整数,而int实际上表示的是带符号的整数。
而在C或者C++中是可以直接表示无符号的整数的,那么,如果我们有一个32位的无符号整数,该怎么用java来处理呢?
public int readIntWrong(DataInputStream is) throws IOException {
return is.readInt();
}
看上面的例子,我们从Stream中读取一个int值,如果是一个32位的无符号整数,那么读出来int就变成了有符号的负整数,这和我们的期望是相符的。
考虑一下,long是64位的,我们是不是可以使用long来表示32位的无符号整数呢?
public long readIntRight(DataInputStream is) throws IOException{
return is.readInt() & 0xFFFFFFFFL; // Mask with 32 one-bits
}
看上面的例子,我们返回的是long,如果将32位的int转换成为64位的long,会自动根据符号位进行补全。
所以这时候我们需要和0xFFFFFFFFL进行mask操作,将高32位重置为0.
NAN和INFINITY
在整型运算中,除数是不能为0的,否则直接运行异常。但是在浮点数运算中,引入了NAN和INFINITY的概念,我们来看一下Double和Float中的定义。
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;