关于java: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.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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理