2017年6月22日 星期四

電腦小算盤 (標準) 的使用

※灰底的使用



※開啟電腦的小算盤後,左邊的圖右邊拉大一點,會變成右邊的圖

※拉大後,兩個橘框會變成「歷程記錄」和「記憶」兩個頁籤

※綠框、C和CE的差別:
假設打了2+678,如下圖


刪除(以下的刪除不包括記憶體刪除):
按綠框(有些版本是向左的箭頭圖)會刪除8,再按一次會刪除7,依此類推
而按「CE」鍵會一次清除678,變成中間的圖
按「C」鍵會回復什麼都還沒輸入的狀態



※M+、M-、MC、MR、MS 的使用

M = Memory
MC = Memory Clean ,清除記憶體
MR = Memory Recall ,叫出記憶體的數字
MS :使用當前數字取代記憶體的數字

要注意一開始,「MC」「MR」是灰色(disabled)的,如果不是灰色的,表示有上次計算的記錄


※假設 (3 + 2) x (4 + 5)
1.得到 5 後,按「M+」,右邊的「記憶」頁籤會顯示
2.得到 9 後,按「*(乘號)」鍵
3.按「MR」會將記憶體的值叫出來,也就是 5,這是按「=」即可
4.按「MC」清除記憶體

※假設 (2 x 3) + (4 x 5) - (6 x 7)
1.得到 6 後,按「M+」,右邊的「記憶」頁籤會顯示 6
2.得到 20 後,按「M+」,右邊的「記憶」頁籤會顯示 26
3.得到 42 後,按「M-」,也就是26 + (-42) 的意思
4.按「MR」取得結果
5.按「MC」清除記憶體

後置遞增減時賦值給自己、不容易發現的無限迴圈、try finally 回傳相反時、快速乘/除 2 的 n 次方

※後置遞增減時賦值給自己

int j=0;
System.out.println(j);
j = j--;
System.out.println(j);
j = j++;
System.out.println(j);
j = j--;
System.out.println(j);
j = j--;
System.out.println(j);

※使用前置遞增/減時,編譯會提示賦值無效,但值是對的
但後置遞增/減並不會,而且永遠都是最後一次給的值,以這個例子,永遠都是 0 「假設第三行 j = j-- 改成 j--,那後面都是 -1」

※正確的寫法是不要用自己去接值,只寫 j++/j-- 即可

※如果真的要接值,要宣告別的變數來接

※在 java 裡原裡如下:
前置遞增
j = j+1;
return j;

後置遞增
int temp = j;
j = j+1;
return temp;

所以問題只會在後置遞增/減時發生
j = j++; 回傳temp值,然後又用自己去接,所以永遠是第一次的值

※java 和 javascript 原理都是這樣;但 C 就不是了


※不容易發現的無限迴圈

int iMax = Integer.MAX_VALUE;
int start = iMax - 10;
int c = 0;
for(int i=start; i<=iMax; i++){
    c++;
}
System.out.println(c);

※一般會寫成 i < iMax,但這裡寫 i <= iMax,所以會多1,但結果並不是 11

※因為 iMax 已經是 Integer 的最大值了,再加1會產生溢位,會變成負的,如此一再的循環,造成無限迴圈

※int i 改 long 可解決



※try finally 回傳相反時

public static void main(String[] args) {
    System.out.println(xxx());
}
    
static boolean xxx() {
    try {
        System.out.println("a");
        // System.exit(0);
        return false;
    } finally {
        System.out.println("b");
        return true;
    }
}

※會印出 a b,並回傳true,編譯完會在 finally 的回傳出現警告「finally block does not complete normally」

※如果將註解打開,只會印 a,System.exit(0) 相當於強制結束


※快速乘/除 2 的 n 次方


// 除 2 的 n 次方
System.out.println(1000 >> 1); // 500
System.out.println(1000 >> 2); // 250
System.out.println(1000 >> 3); // 125
    
// 乘 2 的 n 次方
System.out.println(1000 << 1); // 2000
System.out.println(1000 << 2); // 4000
System.out.println(1000 << 3); // 8000
    
// 只取商
System.out.println(999 >> 1); // 499

※此法不支援小數點

※還有一種「>>>」的很少用,和「>>」差在負數的時候

2017年6月18日 星期日

四則運算

因為 javascript 算術有誤差,主要是有小數點時,但整數不會,所以做法是先轉成整數後再恢復,但有可能進來的小數點位數不確定,所以寫程動態的方式


<script>
'use strict'
    window.onload = () => {
        Number.prototype.plus = function (param){ 
            return accessPlus(this, param); 
        }
    
        Number.prototype.substract = function (param){ 
            return accessSubstract(this, param); 
        }
    
        Number.prototype.multiply = function (param){ 
            return accessMultiple(this, param); 
        }
    
        Number.prototype.divide = function (param){ 
            return accessDivide(this, param); 
        }
    
        /*
         無條件捨去到n位,由於內鍵的Math.floor並沒有小數點的部分,所以加這一支
        */
        Math.floorDecimal = function (value, param){
            let temp = Math.pow(10, param);
            return Math.floor(value * temp)/temp;
        }
    
        // alert(accessDivide(7, 0.35));
        // alert(accessSubstract(n1, n2));
        // alert(accessMultiple(7, 1.35));
        // alert(accessPlus(n1, n2));
    
        let n1 = 7;
        let n2 = 1.35;
        alert(n1.plus(n2));
        alert(n1.substract(n2));
        alert(n1.multiply(n2));
        alert(n1.divide(n2));
        alert(Math.floorDecimal(2.12345679, 6));
    }
    
    let utilArithmetic = (p1, p2) => {
        // 轉字串
        let sp1 = p1.toString();
        let sp2 = p2.toString();
    
        // 小數點後的數字
        let sp1Split = sp1.split(".")[1];
        let sp2Split = sp2.split(".")[1];
    
        // 回傳小數點後的數字長度
        let len1 = typeof(sp1Split) == 'undefined' ? 0 : sp1Split.length;
        let len2 = typeof(sp2Split) == 'undefined' ? 0 : sp2Split.length;
    
        // 將小數點移除
        let np1 = Number(sp1.replace(".", ""));
        let np2 = Number(sp2.replace(".", ""));
    
        return {'len1':len1, 'len2':len2, 'np1':np1, 'np2':np2};
    }
    
    let accessPlus = (p1, p2) => { 
        let json = utilArithmetic(p1, p2);
        let pow = Math.pow(10, Math.max(Number(json.len1), Number(json.len2)));
        return (p1 * pow + p2 * pow) / pow;
    }
    
    let accessSubstract = (p1,p2) => {
        let json = utilArithmetic(p1, p2);
        let max = Math.max(Number(json.len1), Number(json.len2));
        let pow = Math.pow(10, max);
        return ((p1 * pow - p2 * pow) / pow).toFixed(max);
    } 
    
    let accessMultiple = (p1, p2) => {
        let json = utilArithmetic(p1, p2);
        return json.np1 * json.np2 / Math.pow(10, json.len2+json.len1);
    }
    
    let accessDivide = (p1, p2) => {
        let json = utilArithmetic(p1, p2);
        return json.np1 / json.np2 * Math.pow(10, json.len2-json.len1);
    }
</script>



2017年6月17日 星期六

使用 BigDecimal 注意事項

※錯誤的用法

System.out.println(2-1.1);//應該要0.9
System.out.println(0.2*0.7);//應該要1.4
    
BigDecimal b1 = new BigDecimal(2);
BigDecimal b2 = new BigDecimal(1.1);
System.out.println(b1.subtract(b2));
    
BigDecimal b3 = new BigDecimal(0.2);
BigDecimal b4 = new BigDecimal(0.7);
System.out.println(b3.multiply(b4));

※一般都會覺得 b1~b4 就對了,其實還是有問題

※使用 valueOf 方法沒問題,因為源碼已經 toString() 了,所以推薦使用 valueOf 取代建構子的方式

※double d = 0.1f;
這行本身精度就不準了,所以再用 toString() 也是不正確的

※使用小數點做四則運算時,有些時候會出現不可預期的行為,所以 java 提供了 BigDecimal 類別
P.S. 這個問題都是小數點才會有,因為電腦懂得是二進位,所以會將小數轉換成二進位,最後再轉成10進位,但某些數字類似10/3這種除不進的情況,所有結果只是接近我們期待的數字,例如
一、0.5 轉二進制
0.5 * 2 = 1 (取整後必須做到餘 0),所以轉成二進制的結果為 0.1
   驗算 0.1=>   2 的-1次方為 1/2
                       0.5 為 5/10為 1/2,所以正確
二、0.75 轉二進制
0.75 *2 = 1.5 取 1,剩 0.5
       0.5 *2 = 1 取 1,餘0,轉成二進制的結果為 0.11
    驗算 0.11 => 2 的 -1次方 + 2 的 -2 次方,為 1/2 + 1/4 = 3/4
                         0.75 為 75 /100 = 3/4
所以在 BigDecimal 的建構子給 0.5、0.75 不會有問題,但是看例三

三、0.2 轉二進制
0.2 * 2 取 0,剩 0.4
0.4 * 2 取 0,剩 0.8
0.8 * 2 取 1,剩 0.6
0.6 * 2 取 1,剩 0.2
此時又是 0.2,所以結果為 0.0011 0011 0011…只是比較接近而已,所以建構子裡寫 0.2 會有誤差

※0.1~0.9 只有 0.5 沒問題;0.15~0.95,只有 0.25 和 0.75 沒問題 (加或減 0.5 的一半)

※BigDecimal 的原理就是先變成整數,運算完再除即可,如:
0.2 * 0.07 會放大,2 * 7=14 ,小數點後共 3 位數,
所以 14 / 1000 變成 0.014

※toString、toPlainString、toEngineeringString 的差別、判斷正負數、判斷兩個 BigDecimal 大小

BigDecimal d1 = new BigDecimal("10");
BigDecimal d2 = new BigDecimal("0.2");
System.out.println(d1.divide(d2)); // 5E+1
System.out.println(d1.signum()); // 判斷正負0,返回 1、-1、0
System.out.println(d1.compareTo(BigDecimal.TEN)); // 判斷兩個 BigDecimal 大小,返回 1、-1、0
    
    
    
BigDecimal a = BigDecimal.valueOf(1_0000);
BigDecimal b = a.divide(BigDecimal.valueOf(0.2));
System.out.println(b.toPlainString()); // 50000
System.out.println(b.toString()); // 5.000E+4
System.out.println(b.toEngineeringString()); // 50.00E+3
    
BigDecimal c = BigDecimal.valueOf(1000);
BigDecimal d = c.divide(BigDecimal.valueOf(0.2));
System.out.println(d.toPlainString()); // 5000
System.out.println(d.toString()); // 5.00E+3
System.out.println(d.toEngineeringString()); // 5.00E+3
    
BigDecimal bg = new BigDecimal("1E4");
System.out.println(bg.toPlainString()); // 10000
System.out.println(bg.toString()); // 1E+4
System.out.println(bg.toEngineeringString()); // 10E+3

※使用字串包起來才不會有不可預期的情形發生或者用 valueOf 方法

※注意 toString() 、toPlainString() 、toEngineeringString() 的差別,E+1 表示 10 的 1 次方,且只有在運算有小數點時才會發生不一樣的情形,toEngineeringString() 的指數一定是 3 的倍數



※比較 equals 和 compareTo

BigDecimal myZero = new BigDecimal("0.0");
System.out.println(myZero.equals(new BigDecimal("0.00"))); // false
System.out.println(myZero.hashCode() == (new BigDecimal("0.00")).hashCode()); // false
System.out.println(myZero.compareTo(new BigDecimal("0.00"))); // 0
// 所以一般都會使用 compareTo,但其實還有 stripTrailingZeros() 方法可用

// 使用  stripTrailingZeros 可將最後的 0 去除
System.out.println(myZero.stripTrailingZeros().equals(new BigDecimal("0.00").stripTrailingZeros())); // true
System.out.println(myZero.stripTrailingZeros().hashCode() == (new BigDecimal("0.00")).stripTrailingZeros().hashCode()); // true


※除法問題

BigDecimal d1 = new BigDecimal("10");
BigDecimal d2 = new BigDecimal("3");
    
// System.out.println(d1.divide(d2));//Non-terminating decimal expansion; no exact representable decimal result.
System.out.println(d1.divide(d2, BigDecimal.ROUND_UP));//4
System.out.println(d1.divide(d2, 2, RoundingMode.UP));//3.34

※如果有除不盡時就必須要進位,但預設進位是 ROUND_UNNECESSARY,此種方式是除不盡時就直接拋例外,要參考以下介紹的另外七種方式

※BigDecimal.ROUNT_UP 和 RoundingMode.UP 是一樣的,總共有八種進位方式



※八種進位方式

BigDecimal bd = new BigDecimal("42.46");
// System.out.println(bd.setScale(0, BigDecimal.ROUND_UNNECESSARY));//java.lang.ArithmeticException: Rounding necessary
System.out.println(bd.setScale(2, BigDecimal.ROUND_UNNECESSARY));//42.46

※ROUND_UNNECESSARY 是預設值

※只能精確的回傳,如註解的第一個參數是 0,但 42.46 有兩位小數,如果第一個參數不是 2,就會出例外



※UP、DOWN、CEILING、FLOOR

BigDecimal bd = new BigDecimal("32.543");
System.out.println(bd.setScale(0, RoundingMode.UP));//33
System.out.println(bd.setScale(1, RoundingMode.UP));//32.6
System.out.println(bd.setScale(0, RoundingMode.CEILING));//33
System.out.println(bd.setScale(1, RoundingMode.CEILING));//32.6
    
System.out.println(bd.setScale(0, RoundingMode.DOWN));//32
System.out.println(bd.setScale(1, RoundingMode.DOWN));//32.5
System.out.println(bd.setScale(0, RoundingMode.FLOOR));//32
System.out.println(bd.setScale(1, RoundingMode.FLOOR));//32.5

※UP、CEILING 都是無條件進位; DOWN、FLOOR 都是無條件捨去

※CEILING 和 FLOOR 是天花板與地板的意思,數字越大就代表天花板越高,所以CEILING 會往正的方向、FLOOR 會往負的方向

※UP、DOWN 沒有數字觀念,正負數時,數字都一樣 (只要將正的結果乘-1即可),如下:


BigDecimal bd = new BigDecimal("-32.543");
System.out.println(bd.setScale(0, RoundingMode.UP));//-33
System.out.println(bd.setScale(1, RoundingMode.UP));//-32.6
System.out.println(bd.setScale(0, RoundingMode.CEILING));//-32
System.out.println(bd.setScale(1, RoundingMode.CEILING));//-32.5
    
System.out.println(bd.setScale(0, RoundingMode.DOWN));//-32
System.out.println(bd.setScale(1, RoundingMode.DOWN));//-32.5
System.out.println(bd.setScale(0, RoundingMode.FLOOR));//-33
System.out.println(bd.setScale(1, RoundingMode.FLOOR));//-32.6





※四捨五入、五捨六入

final int scale = 0;
System.out.println(new BigDecimal("46.5").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//46
System.out.println(new BigDecimal("46.50").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//46
System.out.println(new BigDecimal("46.501").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.512").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.523").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.534").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.545").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.556").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.567").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.578").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.589").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
System.out.println(new BigDecimal("46.590").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47
    
System.out.println(new BigDecimal("46.55").setScale(1, BigDecimal.ROUND_HALF_DOWN));//46.5
System.out.println(new BigDecimal("46.551").setScale(1, BigDecimal.ROUND_HALF_DOWN));//46.6

※ROUND_HALF_UP 為四捨五入; ROUND_HALF_DOWN 為五捨六入

※注意:
五捨六入和四捨五入的規則不一樣,注意要進位如果是 5 會不一樣
xxx.550 視同 xxx.55,最後的 0 會自動刪除


.假設要取整,那就要看小數第一位如果是 0~4、6~9 和四捨五入一樣
如果是 5,還要再看第二位之後有沒有數字,有就進位;沒有則不進位

.假設要取小數第一位,那就要看小數第二位如果是 5,再看第二位之後有沒有數字,有就進位;沒有則不進位


※ROUND_HALF_EVEN (四捨六入奇進偶不進)

final int scale = 0;
int method = BigDecimal.ROUND_HALF_EVEN;
System.out.println(new BigDecimal("45.5").setScale(scale, method));//46
System.out.println(new BigDecimal("46.5").setScale(scale, method));//46
    
System.out.println(new BigDecimal("45.55").setScale(1, method));//45.6
System.out.println(new BigDecimal("45.65").setScale(1, method));//45.6

奇數時是四捨五入;偶數時是五捨六入
.假設要取整,那就看整數位是奇數還偶數

※個設要取小數第一位,如果小數第二位是 5,再看小數第一位是偶數還奇數,奇進偶不進



※四種方法差異

System.out.println(new BigDecimal("45.55").setScale(1, BigDecimal.ROUND_HALF_UP));//45.6
System.out.println(new BigDecimal("45.55").setScale(1, BigDecimal.ROUND_HALF_DOWN));//45.5
System.out.println(new BigDecimal("45.55").setScale(1, BigDecimal.ROUND_HALF_EVEN));//45.6
    
System.out.println(new BigDecimal("45.65").setScale(1, BigDecimal.ROUND_HALF_UP));//45.7
System.out.println(new BigDecimal("45.65").setScale(1, BigDecimal.ROUND_HALF_DOWN));//45.6
System.out.println(new BigDecimal("45.65").setScale(1, BigDecimal.ROUND_HALF_EVEN));//45.6
    
System.out.println(new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_UP));// -3
System.out.println(new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_DOWN));// -2
System.out.println(new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_EVEN));// -2
System.out.println(Math.round(-2.5)); // -2
System.out.println("-----------------------------------------");
System.out.println(new BigDecimal("-3.5").setScale(0, BigDecimal.ROUND_HALF_UP));// -4
System.out.println(new BigDecimal("-3.5").setScale(0, BigDecimal.ROUND_HALF_DOWN));// -3
System.out.println(new BigDecimal("-3.5").setScale(0, BigDecimal.ROUND_HALF_EVEN));// -4
System.out.println(Math.round(-3.5)); // -3


※負數時,UP、DOWN、EVEN 都和正數一樣,只是是負的而已

※java9 的 BigDecimal.XXX 過時了,要使用 RoundingMode.XXX 取代

※Math.round 正數和 BigDecimal.ROUND_HALF_UP 一樣;但負數和 BigDecimal.ROUND_HALF_DOWN 一樣




MathContext

BigDecimal one1 = new BigDecimal("123.45565");
BigDecimal one2 = new BigDecimal("123.45565", MathContext.UNLIMITED);
BigDecimal one3a = new BigDecimal("123.45565", MathContext.DECIMAL32);
BigDecimal one4a = new BigDecimal("123.45565", new MathContext(7, RoundingMode.HALF_UP));
BigDecimal one3b = new BigDecimal("123.45575", MathContext.DECIMAL32);
BigDecimal one4b = new BigDecimal("123.45575", new MathContext(7, RoundingMode.HALF_UP));
System.out.println(one1); // 123.45565
System.out.println(one2); // 123.45565
System.out.println(one3a); // 123.4556
System.out.println(one4a); // 123.4557
System.out.println(one3b); // 123.4558
System.out.println(one4b); // 123.4558
※one1 和 one2 是一樣的,one1 底層也是用 MathContext.UNLIMITED,是無限制的意思

※MathContext.DECIMAL32 底層是 new MathContext(7, RoundingMode.HALF_EVEN)
7 表示整數加小數位不能超過 7,不包括小數點,但是整數是 0 時不包括,如 0.1234567 是可以顯示到最後的 7 的

※看 one3a 和 one4a 的比較,half_even 是四捨六入,奇數進位;偶數不進位,第 7 位是 6 不進位,所以兩個值不相同

※看 one3b 和 one4b 的比較,四捨六入時,最後是 7 要進位,所以兩個值相同

※MathContext 有個傳 String 的建構子,使用方法 new MathContext("precision=7 roundingMode=HALF_EVEN"),大小寫不能錯

※本來 BigDecimal 建構子,除了0.5外,會有誤差,用 MathContext 就不會了,但 MathContext.UNLIMITED 還是有誤差,但還是不能在建構子裡面做運算,如下:

System.out.println(new BigDecimal(0.8 * 0.4, MathContext.DECIMAL64)); // 0.3200000000000001
System.out.println(new BigDecimal(0.32 / 0.4, MathContext.DECIMAL64)); // 0.7999999999999999

呼叫 Overloading、快速判斷奇偶數、三元運算子問題、判斷 null

※呼叫 Overloading

public static void main(String[] args) {
    xxx(1);
    xxx(new Integer(1));
    xxx("String");
}
    
public static void xxx(Integer x){
    System.out.println("Integer");
}
    
public static void xxx(int x){
    System.out.println("int");
}
    
public static void xxx(Object x){
    System.out.println("Object");
}

※1.如果沒有 int 參數的方法,傳 int 會呼叫 Integer 參數的方法
但如果沒有 int 和 Integer 參數的方法,就會呼叫 Object 參數的方法
所以結果為 基本型態-->Wapper類別-->Object
※注意隱性轉換的問題
以short 為例:
順序如下:
short --> int --> Short --> Integer --> Object

又以 Short 為例
順序如下:
Short --> Object --> short --> int


※2.如果沒有 Integer 參數的方法,傳 Integer 會呼叫 Object 參數的方法
但如果沒有 Integer 和 Object 參數的方法,就會呼叫 int 參數的方法
所以結果為 Wapper類別-->Object-->基本型態
如果想強制呼叫 int 可以用  (int) new Integer(1)

※3.如果給 null,必需剛好只有一個可接收 null 參數的方法,否則會編譯錯誤(ambiguous)
但 Object 參數的方法是個例外,都找不到合適的參數時,會呼叫
所以最多可以有兩個 overloading,Object 和 Wrapper
如果想直接呼叫 Object 參數的方法,可用「(Object) null」,別懷疑,可以強轉的,這時不管有幾個 overloading 都可以了
當然想要 (Integer) null 也是可以的

※4.以上加上 char 和 Character 也有穩性轉換的問題 (int 並不會轉成char;但 char 會轉成 int)
順序如下:
char --> int --> Character --> Object --> 都沒有就編譯錯誤 (和 Integer 沒關係)
Character --> Object --> char --> int --> 都沒有就編譯錯誤 (和 Integer 沒關係)

※使用建構子也有相同的情況

以上總結:
基本類型最終會找到 Objcet 為止;Wrapper 經過 Object 後,會回到基本類型
基本類型:基本型態(注意隱形轉換)-->Wapper類別-->Objec
Wrapper 類型:Wapper類別-->Object-->基本型態(注意隱形轉換)


※快速判斷奇偶數

public static boolean isEven(int num){
    //    return num % 2 != 1;
    //    return num % 2 == 0;
    return (num & 1) == 0;
}

※第一個有可能會回傳負數,所以不建議使用

※可用第三個取代第二個,速度會更快,因為二進制比較快



※三元運算子問題

char x = 'a';
int i = 0; // 97;final--> a
System.out.println(true ? x : 0); // a
System.out.println(true ? x : i); // 97
System.out.println(false ? i : x); // 97
System.out.println(false ? 0 : x); //a

※第一個回傳a,但第二個和第三個居然回傳97,也就是 char 強轉成 int 了,可在 int 加上 final 關鍵字,這樣就不會有強轉的問題了

※使用三元運算子,回傳時最好型態都相同



※判斷 null

如果是 null == 1,像寫死的數字是基本資料類型,因為沒有 null,所以會報錯,但其他就沒有關係了,如 Map、List、Integer 這些有 null 的可以判斷成功

2017年6月16日 星期五

Arrays.asList 使用某些方法時拋出 UnsupportedOperationException

※更方便的判斷

int i = 1;
if (i == 1 || i == 3 || i == 4 || i == 6) {
    // ...
}
    
// 使用 Arrays.asList
List<?> list = Arrays.asList(1, 3, 4, 6);
int i = 1;
if (list.contains(i)) {
    // ...
}

※使用 Arrays.asList 可以很輕鬆的判斷



※使用某些方法會有例外

List<Integer> list = Arrays.asList(1,2,3);
list.add(4);

※執行時拋出了「java.lang.UnsupportedOperationException」

※這是因為 return 是回傳 java.util.Arrays 的私有類別 ArrayList,裡面並沒有add方法,所以會到他的父類別 AbstractList 裡去找,會先找到一個參數的 add,然後繼續呼叫兩個參數的 add,然後直接 new UnsupportedOperationException(),可以看到 remove 、set 方法也是一樣

※所以只要私有類別 ArrayList 沒有實作,且父類也都沒實作,也就是只有部分實作,如果使用到沒實作的方法,就會報例外

※AbstractList 有實作 List,所以可以用 List 接值

※如果一定要使用 add 等方法,只好另外宣告了,如下:
List<Integer> list = Arrays.asList(1, 3, 4, 6);
    
List<Integer> list2 = new ArrayList<>();
list2.addAll(list);
list2.add(7);
System.out.println(list2);

※此時泛型不能用 「?」


List 有一個 subList 方法,產生新的 List 後,對新舊增刪改都會影響到兩個 List,所以也是建議用讀的方式就好,甚至新集合的 size 大於舊集合也是執行期報錯

2017年6月15日 星期四

Call/Pass by Reference

※Call/Pass by Reference 

Call/Pass by Value:將變數傳進另一隻方法當參數,方法對這個變數所做的事,和原本的沒有關係

Call/Pass by Reference:將變傳進另一隻方法當參數,方法對這個變數所做的事,會影響到原來的

這篇講的是 Call/Pass by Reference


<script>
'use strict'
    window.onload = () => {
        var s = new Set();
        zzz(s);
        alert(s.size); // 1
    }
    
    var zzz = (y) => {
        y.add('a');
    }
</script>




※new

<script>
'use strict'
    window.onload = () => {
        var s = new Set();
        zzz(s);
        alert(s.size); // 1
    }
    
    var zzz = (y) => {
        y.add('a');
    
        // y = null;
        // y = 'undefined';
        // y = 'xxx';
        y = new Set();
        y.add('b');
        y.add('c');
    }
</script>

※受影響的只在 new 之前,之後自己是獨立的

※如果將 y = null; y = 'undefined';y = 'sss'; 都不會對原來的產生影響

※甚至宣告一個新的 Set,然後賦值給 y 也沒有用

Call/Pass by Reference

※Call/Pass by Reference 

Call/Pass by Value:將變數傳進另一隻方法當參數,方法對這個變數所做的事,和原本的沒有關係。 基本型態

Call/Pass by Reference:將變傳進另一隻方法當參數,方法對這個變數所做的事,會影響到原來的。不是基本型態的屬於這個



public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    System.out.println(list);
    xxx(list);
    System.out.println(list);
}
    
public static void xxx(List<String> a){
    a.add("a1");
}

※結果:
[]
[a1]



※new

public static void xxx(List<String> a){
    a.add("a1");
    a.add("a2");
    
    // a = null;
    a = new ArrayList<String>();
    a.add("a3");
    a.add("a4");
    System.out.println("in" + a);
}

※結果:
[]
in[a3, a4]
[a1, a2]

※受影響的只在 new 之前,之後自己是獨立的

※a = null、a = 另外一個 List也都和 new 一樣,對原來的 List 不會產生作用,因為指向了別的記憶體空間

在 C++ 語言裡,java 只有 call by value,只不過傳的是地址而已,所以不能將整個物件改掉,如這個例子,new 或者將別的記憶體空間指定過來就和原來的沒關係了,但如果物件裡的方法可以改變內容是可以的,如上個例子的 List 的 add 方法就是這樣

這篇文章有說明,C++ 用 &變數名稱就表示 call by reference,從右邊賦值到左邊是可以的



※另一個範例
public class Test {
    private String xxx = "x";
    // setter/getter...
    
    private List<String> list = new ArrayList<>();
    // setter/getter...

    
    Test() {
        list.add("a");
        list.add("b");
        list.add("c");
    }
        
    public static void main(String[] args) {
        Test t1 = new Test();
        Test2 t2 = new Test2();
    
        t2.setXxx2(t1.xxx);//*
        t2.ooo(t1.getXxx());
        System.out.println(t1.getXxx());
        System.out.println(t2.getXxx2());
    
        System.out.println("\r\n");
    
        t2.setList2(t1.getList());//*
        t2.xxx(t1.getList());
        System.out.println(t1.getList());
        System.out.println(t2.getList2());
    }
}
    
class Test2 {
    private String xxx2;
    // setter/getter...

    private List<String> list2;
    // setter/getter...
    
    public void xxx(List<String> l) {
        l.add("d");
        l.add("e");
        l.add("f");
    }
    
    public void ooo(String s) {
        s = "o";
    }
}