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;
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.6x2=1.2,取整剩餘0.2,繼續上面的步驟0.2x2=0.4,0.4x2=0.8,0.8x2=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,char,or int
float to byte,int,or long
double to byte,long,or float
或者反向:
byte to short,float,or double
short to int,or double
char to int,or double
int to long,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
以上這篇java安全編碼指南之:Number操作詳解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。