简介
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;
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
public static final float NaN = 0.0f / 0.0f;
1 除以 0 就是 INFINITY,而 0 除以 0 就是 NaN。
接下来,咱们看一下 NAN 和 INFINITY 的比拟:
public void compareInfinity(){System.out.println(Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY);
}
运行后果是 true。
public void compareNaN(){System.out.println(Double.NaN == Double.NaN);
}
运行后果是 false。
能够看到 NaN 和 NaN 相比是 false。
那么咱们怎么比拟 NaN 呢?
别急,Double 提供了一个 isNaN 办法,咱们能够这样应用:
System.out.println(Double.isNaN(Double.NaN));
接下来咱们看一个在代码中常常会用到的一个 Double 解析:
public void incorrectParse(String userInput){
double val = 0;
try {val = Double.valueOf(userInput);
} catch (NumberFormatException e) { }
//do something for val
}
这段代码有没有问题?咋看下如同没有问题,然而,如果咱们的 userInput 是 NaN,Infinity,或者 -Infinity,Double.valueOf 是能够解析失去后果的。
public void testNaN(){System.out.println(Double.valueOf("NaN"));
System.out.println(Double.valueOf("Infinity"));
System.out.println(Double.valueOf("-Infinity"));
}
运行输入:
NaN
Infinity
-Infinity
所以,咱们还须要额定去判断 NaN 和 Infinity:
public void correctParse(String userInput){
double val = 0;
try {val = Double.valueOf(userInput);
} catch (NumberFormatException e) { }
if (Double.isInfinite(val)){// Handle infinity error}
if (Double.isNaN(val)) {// Handle NaN error}
//do something for val
}
不要应用 float 或者 double 作为循环的计数器
思考上面的代码:
for (float x = 0.1f; x <= 1.0f; x += 0.1f) {System.out.println(x);
}
下面的代码有什么问题呢?
咱们都晓得 java 中浮点数是不精确的,然而不肯定有人晓得为什么不精确。
这里给大家解释一下,计算机中所有与的数都是以二进制存储的,咱们以 0.6 为例。
0.6 转成为二进制格局是乘 2 取整,0.6×2=1.2,取整残余 0.2,持续下面的步骤 0.2×2=0.4,0.4×2=0.8,0.8×2=1.6, 取整残余 0.6,产生了一个循环。
所以 0.6 的二进制格局是.1001 1001 1001 1001 1001 1001 1001 … 有限循环上来。
所以,有些小数是无奈用二进制准确的示意的,最终导致应用 float 或者 double 作为计数器是不准的。
BigDecimal 的构建
为了解决 float 或者 Double 计算中精度缺失的问题,咱们通常会应用 BigDecimal。
那么在应用 BigDecimal 的时候,请留神肯定不要从 float 构建 BigDecimal,否则可能呈现意想不到的问题。
public void getFromFloat(){System.out.println(new BigDecimal(0.1));
}
下面的代码,咱们失去的后果是:0.1000000000000000055511151231257827021181583404541015625。
这是因为二进制无奈完满的展现所有的小数。
所以,咱们须要从 String 来构建 BigDecimal:
public void getFromString(){System.out.println(new BigDecimal("0.1"));
}
类型转换问题
在 java 中各种类型的 Number 能够相互进行转换:
比方:
- short to byte or char
- char to byte or short
- int to byte, short, or char
- long to byte, short, char, or int
- float to byte, short, char, int, or long
- double to byte, short, char, int, long, or float
或者反向:
- byte to short, int, long, float, or double
- short to int, long, float, or double
- char to int, long, float, or double
- int to long, float, or double
- long to float or double
- float to double
从大范畴的类型转向小范畴的类型时,咱们要思考是否超出转换类型范畴的状况:
public void intToByte(int i){if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE)) {throw new ArithmeticException("Value is out of range");
}
byte b = (byte) i;
}
比方下面的例子中,咱们将 int 转换成为 byte,那么在转换之前,须要先判断 int 是否超出了 byte 的范畴。
同时咱们还须要思考到精度的切换,看上面的例子:
public void intToFloat(){System.out.println(subtraction(1111111111,1111111111));
}
public int subtraction(int i , float j){return i - (int)j;
}
后果是多少呢?
答案不是 0,而是 -57。
为什么呢?
因为这里咱们做了两次转换,第一次从 1111111111 转换到 float,float 尽管有 32 位,然而只有 23 位是寄存真正的数值的,1 位是符号位,剩下的 8 位是指数位。
所以从 1111111111 转换到 float 发送了精度失落。
咱们能够把 subtraction 办法批改一下,首先判断 float 的范畴,如果超出了 23bit 的示意范畴,则阐明发送了精度失落,咱们须要抛出异样:
public int subtraction(int i , float j){System.out.println(j);
if ((j > 0x007fffff) || (j < -0x800000)) {throw new ArithmeticException("Insufficient precision");
}
return i - (int)j;
}
当然还有一种方法,咱们能够用精度更高的 double 来做转换,double 有 52 位来寄存真正的数据,所以足够了。
public int subtractionWithDouble(int i , double j){System.out.println(j);
return i - (int)j;
}
本文的代码:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-number/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!