2013年10月31日 星期四

ArrayList 移除 element 的問題

    今天查了 ArrayList 用法的時候,發現在 Javaworld 有人發了一篇 arraylist的移除問題,下面是他的 Sample Code
 
   ArrayList<integer> list = new ArrayList<integer>();
   list.add(2);
   list.add(2);
   list.add(3);
   list.add(1);
   list.add(2);
   System.out.println(list);

   for (int i=0; i<list.size(); i++){
      if(list.get(i)<3) {
         list.remove(i);
      }
   }
   System.out.println(list);

     原發問者想要將小於 3 的值全部移掉只剩下 3 這個元素,但是卻印出 [1 , 2],上面的的Sample Code 我有修改過,所以顯示為 [2,3,2] ,因為原程式在判斷條件小於3 的 if 後面多了分號 Orz。
     Anyway, 回到原來的問題,當你刪除一個元素時 size 也會跟著縮小,但是 i 的值卻會遞增,在 for 迴圈最後面加入除錯訊息,就可以知道為何印出 [2, 3, 2]。下面是我在 for 迴圈中加入除錯訊息所印出的資料:
i = 0 : [2, 3, 1, 2] size:4
i = 1 : [2, 3, 1, 2] size:4
i = 2 : [2, 3, 2] size:3

    要解決這個問題有下面三種方式

  1. 透過 Iterator 
  2. 透過 for 迴圈反向檢查,可以在 ConcurrentModificationException for ArrayList 中查到方式。
  3. 透過 CopyOnWriteArrayList

透過 Iterator 方式如下:
        
        Iterator<Integer> Allelement = list.iterator();
        while (Allelement.hasNext()) {
            if (Allelement.next() < 3) {
                Allelement.remove(); // 刪除最後 Iterator 所回傳的值,也就是目前所指到值
            }
        }
透過 for 迴圈反向檢查:
        
       for (int i = list.size() - 1; i >= 0; i--) {
            if(list.get(i) < 3){
                list.remove(i);
            }
        }

透過 CopyOnWriteArrayList:
        CopyOnWriteArrayList <Integer> list = 
                     new CopyOnWriteArrayList <Integer>();
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(1);
        list.add(2);
        for (Integer val : list) {
            if (val  < 3) {
                list.remove(val);
            }
        }
請注意,使用 foreach 去詢訪 ArrayList 時,你不可以在 foreach 中 執行 remove,如果執行了應該會出現 java.util.ConcurrentModificationException 的例外訊息。


2013年10月23日 星期三

Scanner(System.in) 無法取的輸入的問題

    當你的只使用一個 Scanner(System.in) 在你的程式中並沒有甚麼問題,但是如果你有兩個類別都使用 Scanner(System.in) 來取得輸入時,最後使用 Scanner(System.in) 來取得輸入的類別是無法取得輸入,除非第一個使用的那個類別沒有呼叫close來關閉 Scanner。

    範例程式如下:
// InputDemo.java
import java.io.IOException;
import java.util.Scanner;

public class InputDemo {
    public static void main(String[] args) throws IOException {
        OtherInput otherInput = new OtherInput();

        int number = -1;
        Scanner scanner = new Scanner(System.in);
        
        // 嘗試執行下列敘述會得到IO例外訊息
        //System.out.println("System.in.available()" + System.in.available());
        
        System.out.print("Input a number:");
        if (scanner.hasNextInt()) {
            number = scanner.nextInt();
        }
        scanner.close();
        // 因為 scanner.nextInt() 沒有執行,所以 number 為 -1
        System.out.println("You input a number " + number);
    }
}

// OtherInput.java 
import java.util.Scanner;
public class OtherInput {
    public OtherInput() {
        Scanner scanner = new Scanner(System.in);
        
        int age = 0;
        String name = null;
        
        System.out.print("Please input your name:");
        if (scanner.hasNextLine()) {
            name = scanner.nextLine();
        }
        
        System.out.print("Please input your age:");
        if (scanner.hasNextInt()) {
            age = scanner.nextInt();
        }
        scanner.close();
        System.out.println("Your name: " + name + " and age: " + age);
    }
}

    在 Eclipse 下如果你沒有關閉 Scanner 會有一個警告訊息:"Resource leak: 'scanner' is never closed"。所以不建議不關閉 Scanner 物件。為什麼關閉 Scanner 物件會有問題呢? 因為當你執行 scanner.close() 時也會把 System.in 標準輸入也關閉掉,所以最後使用Scanner 物件的 calss 無法正常的取得輸入。
   你可以試著把上面的範例 InputDemo.java 中的 System.in.available() 註解符號拿掉,你會得到下列的例外訊息:
   Exception in thread "main" java.io.IOException: Stream closed
 at java.io.BufferedInputStream.getInIfOpen(Unknown Source)
 at java.io.BufferedInputStream.available(Unknown Source)
 at InputDemo.main(InputDemo.java:10)

   所以當你有多個類別要使用 Scanner(System.in) 取得輸入時,最好可以在主類別建立一個 Scanner 物件,然後將該物件傳遞給需要的類別,然後在主類別呼叫 Scanner 的 close 方法。

[其他參考]
java.util.NoSuchElementException - Scanner reading user input

Eclipse Console 視窗取得輸入的問題

     剛剛在嘗試了一下,發現是輸入法的問題,如果發現無法輸入可以將輸入切換到一般英文的模式,不要切換到有任何中文的輸入法。 : )

     在 Java 中要取得 Console 視窗輸入的文字可以透過 Scanner 物件來達到,今天如果你使用Eclipse 來開發時,當你的程式在取得輸入文字前,如果有先列印出一段提示文字時,你會發現 Eclipse 的 Console 視窗的游標會在最前面 (請參考下圖),並不會接在你所列印的提示文字後面,此時,如果你直接輸入任何文字嘗試取得輸入,會發現 Eclipse Console 視窗並不會顯示任何文字,且即便你按 Enter 想要結束輸入,Eclipse Console 視窗也不會有任何反應,此時你只能按停止執行或者重新執行程式來逃離這個狀況。

     要解決無法取得輸入的情況,當你執行程式時,必須先將游標移到正確的位置,然後再輸入,此時 Eclipse Console 視窗就可以正確的取得輸入的文字,注意,如果你的提示文字使用 println 來顯示,你必須移到下一行的開頭,如果使用 print 來顯示,則必須將游標移到提示文字的最後面才可開始輸入文字。

     範例程式如下:
import java.util.Scanner;

public class InputDemo {
    public static void main(String[] args) {
        int number = -1;
        Scanner scanner = new Scanner(System.in);
        System.out.print("Input a number:");
        
        if (scanner.hasNextInt()) {
            number = scanner.nextInt();
        }
        scanner.close();
        System.out.println("You input a number " + number);
    }
}