2013年11月17日 星期日

旋轉 Windows 畫面

    今天電腦讓小朋友亂按,結果開始列跑到右邊去,上網查了一下如何旋轉畫面,終於把畫面給轉回來了~: )

    如果你想要旋轉 Windows 畫面可以透過下面的 Shortcut Key:
  1. Ctrl + Alt + Up Arrow  (這是我們一般操作的畫面 開始列在下方)
  2. Ctrl + Alt + Down Arrow (開始列在上面,畫面上下顛倒)
  3. Ctrl + Alt + Right Arrow (開始列在右邊)
  4. Ctrl + Alt + Left Arrow (開始列在左邊)
參考資料:

透過 getenv 與 getProperties 取得系統環境變數與JVM屬性設定

      在 Java 中我們可以透過 System.getProperties() 取得 JVM 參數設定System.getenv() 來取得作業系統的環境變數設定。你可以在 The Java Tutorials 裡面參考 System Properties 與 Environment Variables 教學文章看到其他用法。

     如果你使用 Eclipse 你可以透過 -D<Key>=<Value> 的方式在 Run > Run Configurations... > (x)= Arguments > VM Arguments 來設定額外的 JVM 屬性設定。例如你可以填入 -DColor=blue 來設定 Color 屬性為 blue。如下圖所示:


簡易的範例程式如下:
    // 列出目前所有的 JVM 屬性設定
    System.getProperties().list(System.out); 
    
    // 透過 getProperty 單獨取出特定的某個屬性設定值    
    System.out.println("Color = " + System.getProperty("file.separator"));    

    // 取出我們設定的屬性值,注意屬性名稱是 case sensitive
    // 如果你寫成 color 得到的會是 null
    System.out.println("Color = " + System.getProperty("Color"));

    // 你也可以使用下列方式,當所指定的屬性沒有鍵值時,回傳你所設定的預設值
    // 下面的範例當沒有設定 Color 屬性鍵值時會回傳預設的 Pink 值
    System.out.println("Color = " + System.getProperty("Color", "Pink")); 

    // 列出目前作業系統所有的環境變數設定
    Set<Map.Entry<String, String>> envSet = System.getenv().entrySet();
    for (Map.Entry<String, String> set: envSet) {
        System.out.println(set.getKey() + " = " + set.getValue());
    }
    // 透過 System.getenv(key) 的方式單獨取出你想要的屬性設定
    System.out.println("SystemDrive = " + System.getenv("SystemDrive"));
    System.out.println("TEMP = " + System.getenv("TEMP"));


參考文章:



2013年11月13日 星期三

保護回傳參考型態的物件

      今天在 "最新 Java 7 程式語言 施威銘" 這本書中看到了一篇內容,大概的內容是講述當我有一個類別 Circle, 該類別裡面包含了另一個類別 Point 物件紀錄 Circle 的中心點,為了保護 Point 物件,所以在 Circle 裡面使用 private 來修飾 Point 物件;但當外部需要取得中心點資料時,便在 Circle 類別寫了一個傳回 Point 的參考物件 method,此時因為外部可以取得 Point 的物件參考,所以外部就可透過 所得到的 Point 物件參考來設定修改資料,這樣就違背了當初我們不想讓外部的使用者來隨便設定 Circle 的中心點的用意,也失去了把 Point 設成 private 作用。
     所以為了避免這種情況發生,可以使用下列方式:
  1. new 一個新的 Point 物件,拷貝目前 Circle 物件內的 Point 物件資料。
  2. 將 Point 變成 immutable 的類別,也就是不提供設定(setXX ) method。
  3. Point 類別 implements Cloneable 類別,然後回傳 clone 物件。
    書本使用第一個方式,在回傳 Point 物件參考的 method 中,可以先用 new 的方式將目前在 Circle 類別內的 Point 物件拷貝一份,然後傳回去。這樣外部修改 Point 物件時就不會影響原本的 Circle 物件裡 Point 物件資料了。

範例程式:

class Point implements Cloneable {
    private int x;
    private int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    // 2. 把 setPoint 移除,讓 Point 變成 immutable 的類別
    public void setPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Circle {
    private Point point;
    private int radius;
    public Circle(int x, int y, int r) {
        point = new Point(x, y);
        radius = r;
    }
    public Point getPoint() {
        return point;
    }
    public Point getReadOnlyPoint() {
        try {
            // 3. 讓 Point 類別實作 Cloneable,然後回傳 clone 物件 
            return (Point)point.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    public Point getReadOnlyPoint2() {
        //1. 使用 new 的方式,將目前的Point 資料拷貝一份
        return new Point(point.getX(), point.getY());
    }
    
    @Override
    public String toString() {
        return String.format("(%d, %d) ,radius = %d", point.getX(), point.getY(), radius);
    }
}

public class ProtectReferenceObj {
    public static void main(String[] args) {
        Circle circle = new Circle(1,1,2);
        Point pt = circle.getPoint();
        System.out.println("Before: " + circle);
        pt.setPoint(10, 20);
        System.out.println("After: " + circle);
        System.out.println("================================");
        System.out.println("Before: " + circle);
        Point pt2 = circle.getReadOnlyPoint();
        pt2.setPoint(11, 22);
        System.out.println("After: " + circle);
        System.out.println("pt2.x =  " + pt2.getX() + " pt2.y = " + pt2.getY());
        System.out.println("================================");
        System.out.println("Before: " + circle);
        Point pt3 = circle.getReadOnlyPoint2();
        pt3.setPoint(55, 99);
        System.out.println("After: " + circle);
        System.out.println("pt3.x =  " + pt3.getX() + " pt3.y = " + pt3.getY());
    }
}


輸出結果:
Before: (1, 1) ,radius = 2
After: (10, 20) ,radius = 2
================================
Before: (10, 20) ,radius = 2
After: (10, 20) ,radius = 2
pt2.x =  11 pt2.y = 22
================================
Before: (10, 20) ,radius = 2
After: (10, 20) ,radius = 2
pt3.x =  55 pt3.y = 99


2013年11月5日 星期二

ArrayList 使用 toarray 轉換成物件陣列

     在某些情況下,我們希望能夠將 ArrayList 物件所存的內容轉換成陣列的形式,在 ArrayList 中提供了 toarray 方法來達到我們的需求。下面是 Java 所提供的 toarray 方法的原始碼:

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    public T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

     有上面可知,我們有兩個方式可以使用 toarray 方法,一個是不帶任何參數 toarray 方法回傳 Object 陣列,另外一個是回傳你所傳入的型態陣列。
     我們先來看 public T[] toArray(T[] a) 的方法,從原始碼中,我們發現此方法會使用參數的 length 的屬性,代表該參數必須要先配置過記憶體才行,如果直接傳 null 給它會引發 "java.lang.NullPointerException" 的例外事件。再回頭看一下原始碼,會發現當你所配置的陣列元素小於目前 ArrayList 所儲存的元素時,toarray 方法會幫你配置足夠的元素來儲存目前 ArrayList 物件中所含的所有元素。而當你的所配置的陣列元素個數大於目前會將你所配置的第 size 元素設定為null。基本上你可以想成超出的元素都會被設置成 null 就可以。(PS: 要使用 ArrayList 其型態一定是參考型態,如果使用基本型態會出現編譯失敗 ,而物件陣列的預設值為 null)。

    另外一個 toArray法是回傳 Object 陣列,由於兩個陣列基底型別不同時,不能互相參照,所以你無法直接將回傳的 Object 陣列轉成你要的型態陣列。但是你可以針對每個 Object 元素做轉型。

範例程式如下:

     static void toArrayDemo() {
        
        ArrayList<integer> list = new ArrayList<integer>(){{
            add(1);
            add(2);
            add(3);
            add(4);
        }};

        //"main" java.lang.NullPointerException
        //Integer[] test = null; 
        //test = list.toArray(test); 
        // or Integer[] test = list.toArray(null);
        
        // 配置的元素小於 ArrayList 所存的元素各數少時,
        // toArray 會幫你重新配置足夠的陣列個數來儲存。
        Integer[] test = list.toArray(new Integer[0]);
        System.out.print("<1>[ ");
        for (Integer val : test) {
            System.out.print(val + " ");
        }
        System.out.println("]");
        // 配置大於 ArrayList 所存的 元素個數,多餘的陣列元素都是 null
        Integer[] test2 = list.toArray(new Integer[10]);
        System.out.print("<2>[ ");
        for (Integer val : test2) {
            System.out.print(val + " ");
        }
        
        System.out.println("]");
        
        // 配置符合目前 ArrayList 所含的元素個數
        Integer[] test3 = list.toArray(new Integer[list.size()]);
        System.out.print("<3>[ ");
        for (Integer val : test3) {
            System.out.print(val + " ");
        }
        System.out.println("]");
        
        //無法將 Object 陣列轉成其他型態的陣列
        //[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
        //Integer[] test4 = (Integer[] )list.toArray();
        
        Object [] obj = list.toArray();
        
        System.out.print("<4>[ ");
        for (Object val : obj) {
            // 你可以對每一個 Object 中的元素轉型成 Integer 型態的物件
            // 因為我們宣告的 ArrayList 是儲存 Integer 物件,所以每一個
            // Object 物件都是參考到  Integer 物件
            
            System.out.print( ((Integer)val).intValue() + " ");
            // 下面方式與上面轉型所列印出來的結果相同,因為每一個 Object 元素
            // 都是參考到 Integer 物件,而 Integer 的 toString method 就是列印其值。
            //System.out.print( val + " ");
        }
        System.out.println("]");
    }

輸出結果:
<1>[ 1 2 3 4 ]
<2>[ 1 2 3 4 null null null null null null ]
<3>[ 1 2 3 4 ]
<4>[ 1 2 3 4 ]

補充:
    Object[] obj = new Object[size] 與 Object[] intObj = new Integer[2]  的不同是,obj 陣列可以指定
任何型態的物件參考,每一個元素值不一定要儲存相同的物件。而 intObj 是參考到 Integer 陣列所以其元素只能儲存 Integer 物件,儲存其他型態物件會引發 "java.lang.ArrayStoreException" 例外事件。另外 intObj 是參考到 Integer 陣列,所以你可以將 intObj 指定給其他 Integer 陣列來參考。

範例程式如下:

    static void arrayDemo() {
        Object [] intobj = new Integer[2];
        System.out.println("obj instance Integer[] = " + (intobj instanceof Integer[]));
        obj[0] = new Integer(10);
        //Exception in thread "main" java.lang.ArrayStoreException: java.lang.Float
        //obj[1] = new Float(10.f);
        
        Object [] obj2 = new Object[2];
        System.out.println("obj2 instance Integer[] = " + (obj2 instanceof Integer[]));
        obj2[0] = new Integer(10);
        obj2[1] = new Float(10.f);
        System.out.println("obj2[0] = " + obj2[0] + " , obj2[1] = " + obj2[1]);
    }

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);
    }
}