原來你是這樣的JAVA--[07]聊聊Integer和BigDecimal

今天來聊聊Java中跟數值處理相關的兩個類型Integer和BigDecimal。 說起這兩個類型,我們肯定都不陌生,但是其中有些容易踩到的坑需要注意避讓。

Integer

整型我們應該每天都會用到,但是每種語言還是有自己的特性。從敬姐剛從.NET轉過來的時候踩過的一個坑說起:話說在.NET世界中,數值的基本類型和包裝類型是會自動轉換的,所以數值比較很自然地就會使用 a==b,但是到java這卻行不通了,頓時一臉懵。

數值比較及自動裝箱

    @Test
    public void Interger(){
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y));
        System.out.println("m == n: " + (m==n));
        System.out.println("x.equals(y): " + x.equals(y));
        System.out.println("m.equals(n): " + m.equals(n));
        //執行結果
//        x == y: true
//        m == n: false
//        x.equals(y): true
//        m.equals(n): true
    }

仔細觀察可以發現,==比較,較小的兩個相同的Integer返回true,較大的兩個相同的Integer返回false,這是爲何呢?

一起看一下Integer類的源碼,發現其中的IntegerCache類。這是Java爲了節省空間、提升性能採取的優化機制,常量池的大小爲一個字節(-128~127)。

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

而對於valueOf(int i)方法,直接使用了常量池IntegerCache

  public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

所以當遇到 Integer x = 127; 時,會進行自動裝箱,調用的是:

Integer x = Integer.valueOf(127);

爲了節省內存,Integer.valueOf()對於較小的數,始終返回相同的實例,因此,比較“恰好”爲true。但我們絕不能因爲Java標準庫的Integer內部有緩存優化就用比較,必須用equals()方法比較兩個Integer。

創建實例

因爲Integer.valueOf()可能始終返回同一個Integer實例,因此,在我們自己創建Integer的時候,以下兩種方法:

方法1:Integer n = new Integer(100);
方法2:Integer n = Integer.valueOf(100);

方法2更好,因爲方法1總是創建新的Integer實例,方法2把內部優化留給Integer的實現者去做,即使在當前版本沒有優化,也有可能在下一個版本進行優化。
我們把能創建“新”對象的靜態方法稱爲靜態工廠方法。Integer.valueOf()就是靜態工廠方法,它儘可能地返回緩存的實例以節省內存。簡而言之:創建新對象時,優先選用靜態工廠方法而不是new操作符。

上個看個有點特別的例子:

@Test
    public void instance() {
        //每次創建一個新實例
        Integer a1 = new Integer(100);
        Integer a2 = new Integer(100);
        Assert.assertFalse(a1 == a2);
        //add
        Integer a3 = new Integer(200);
        //注意這裏嘍!!
        Assert.assertTrue(a1 + a2 == 200);
        Assert.assertTrue(a1 + a2 == a3);
    }

因爲+這個操作符不適用於 Integer 對象,首先 a1 和 a2 進行自動拆箱操作,進行數值相加,即a3 == 40。

BigDecimal

BigDecimal適合商業計算場景,用來對超過16位有效位的數進行精確的運算。

Double轉換爲BigDecimal

我們在使用BigDecimal時,爲了防止精度丟失,推薦使用它的 BigDecimal(String) 構造方法來創建對象。

    @Test
    public void double2decimal() {
        Double d = 0.1d;
        System.out.println(new BigDecimal(d));//0.1000000000000000055511151231257827021181583404541015625
        System.out.println(new BigDecimal(d.toString()));//0.1
        System.out.println(BigDecimal.valueOf(d));//0.1
    }

保留幾位小數

通過 setScale方法設置保留幾位小數以及保留規則。

@Test
    public void decimalTest() {
        BigDecimal a = new BigDecimal("1.2345");
        System.out.println(a.toString());
        //BigDecimal保留幾位小數
        BigDecimal b = a.setScale(3, RoundingMode.HALF_DOWN);
        System.out.println(b.toString());
    }

BigDecimal 值比較

BigDecimal的等值比較應該使用compareTo()方法,而不是equals()方法。

/**
     * BigDecimal等值比較
     * equals:既比較數值,又比較精度;
     * compareTo:僅比較數值
     */
    @Test
    public void compare() {
        BigDecimal a = BigDecimal.valueOf(1);
        BigDecimal b = BigDecimal.valueOf(1.00);
        Assert.assertFalse(a.equals(b));
        Assert.assertEquals(0, a.compareTo(b));
    }

調試一下BigDecimal的equals和compareTo方法,發現equals()方法會比較精度,但是compare()方法不會。


BigDecimal 除法

BigDecimal.divide(),除法運算注意要設置精度,否則在除不盡的情況下會拋異常。

    @Test
    public void divide(){
        BigDecimal a=BigDecimal.valueOf(1);
        BigDecimal b=BigDecimal.valueOf(3);
        //直接拋異常
//        System.out.println(a.divide(b));
        //正常返回 0.3333
        System.out.println(a.divide(b,4,RoundingMode.HALF_EVEN));
    }

代碼示例

文示例代碼參考:jing-yes-java (https://github.com/cathychen00/jing-yes-java/tree/master/jing-yes-j2se/src/test/java/com/jingyes/j2se/tests)


本人公衆號[ 敬YES ]同步更新,歡迎大家關注~

img

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章