分類
Java

List 速度比較紀錄

以下是參考 think in java 實作 ArrayList v.s LinkedList 速度比較的紀錄。
size 指的是 element 的數量,每一個比較項目下方是速度單位,越少代表越快,element 的型態是 Integer。

----------------------------------------------   LinkedList   ----------------------------------------------
    size             add             get             set     iter.add(x)        add(x,y)          remove
      10             164              27              26              18             369             445
     100              12              39              41              15             285              20
    1000              15             281             283               9              57              18
   10000              14            3947            3825               8              14              15
-----------------------------------------------   ArrayList   -----------------------------------------------
    size             add             get             set     iter.add(x)        add(x,y)          remove
      10             176              13              14              28             440             165
     100               6              11              12              21             398              23
    1000               7              11              12              66             400              80
   10000               9              12              14             576            1864             689

大部分的其他比較並沒有考慮到 size 的因素,基本上都是說

若常用 add, get, set 就選擇 ArrayList , 若常用 iter.add, add(x,y), remove 就選擇 LinkedList。

上面的比較表格把 size 加入考慮的因素,e.g. 若 size 為 10000,且常用 add 和 remove,那麼建議選擇 LinkedList。
若 size 為 10,也是常用 add 和 remove , 那麼選擇 ArrayList 會比較好。
 

分類
Java

Java的參數傳遞(pass by value)

基本概念卻讓很多人搞不清(包括我)。
首先在 java,函式的參數傳遞確實為 pass by value。
pass by value 代表當呼叫函式傳遞參數的時候,會複製參數的副本並傳入函式中,代表在函式內的參數和在函式外的變數是不同的。
所以在函式內部改變其值都不影響函式外部原先的變數。
基本型別(boolean , char , byte  , short , int , long , float , double)的情況很好理解。

    @Test
    public void testInt()
    {
        int intNumber = 0;
        System.out.println("before increase: intNumber:"+intNumber);
        increase(intNumber);
        System.out.println("after increase: intNumber:"+intNumber);
    }
    private void increase(int number)
    {
        number++;
        System.out.println("in increase intNumber:"+number);
    }

output:

before increase: intNumber:0
in increase intNumber:1
after increase: intNumber:0

但如果是非基本型別,如物件或是陣列就要小心,因為這時候複製的副本是連結到物件的參考,而不是物件本身的副本。
也就是說傳入函式內的參考和和函式外部的參考連結到的是同一個物件,而因為連結的是同一個物件,所以在函式內改變了參考也會影響原先的物件。
情況就會完全不同。
為了方便示範,先建立一個Account 類別。

public class Account {
    private int mAge;
    private String mName;
    public Account(int age, String name){
        mAge = age;
        mName = name;
    }
    public void setAge(int age)
    {
        mAge = age;
    }
    public void setName(String name)
    {
        mName = name;
    }
    public int getAge(){
        return mAge;
    }
    public String getName()
    {
        return mName;
    }
}

測試如下

@Test
    public void testAccount()
    {
        Account foxx = new Account(18, "foxx");
        System.out.println("before foxx.getAge():"+foxx.getAge()+" foxx.getName()"+foxx.getName());
        changeAccount(foxx);
        System.out.println("after foxx.getAge():"+foxx.getAge()+" foxx.getName():"+foxx.getName());
    }
    private void changeAccount(Account account)
    {
        account.setAge(81);
        account.setName("new foxx");
        System.out.println("in changeAccount account.getAge():"+account.getAge()+" account.getName():"+account.getName());
    }

output:

before foxx.getAge():18 foxx.getName()foxx
in changeAccount account.getAge():81 account.getName():new foxx
after foxx.getAge():81 foxx.getName():new foxx

從輸出可以看到在函式中藉由參考修改了不同的數值也會影響原先外部的物件。 
因此要小心在函式中的參考有什麼變化。修改 changeAccount 如下

    private void changeAccount(Account account)
    {
        account = new Account(0, "zero");
        System.out.println("in changeAccount account.getAge():"+account.getAge()+" account.getName():"+account.getName());
    }

output:

before foxx.getAge():18 foxx.getName()foxx
in changeAccount account.getAge():0 account.getName():zero
after foxx.getAge():18 foxx.getName():foxx

因為在 changeAccount 的第3行我們讓傳進來的參考連結一個新的物件,因此在第3行之後的修改完全不會影響原先的物件。
 
 
補上 Array 以及 String 的測試

    @Test
    public void testArray()
    {
        int[] intArray = new int[]{0,0,0};
        System.out.println("before intArray[0]:"+intArray[0]+" intArray[1]:"+intArray[1]+" intArray[2]:"+intArray[2]);
        changeArray(intArray);
        System.out.println("after intArray[0]:"+intArray[0]+" intArray[1]:"+intArray[1]+" intArray[2]:"+intArray[2]);
    }
    private void changeArray(int[] intArray)
    {
        intArray[0] = 100;
        intArray[1] = 101;
        intArray[2] = 102;
        System.out.println("in changeArray intArray[0]:"+intArray[0]+" intArray[1]:"+intArray[1]+" intArray[2]:"+intArray[2]);
    }

output:

before intArray[0]:0 intArray[1]:0 intArray[2]:0
in changeArray intArray[0]:100 intArray[1]:101 intArray[2]:102
after intArray[0]:100 intArray[1]:101 intArray[2]:102

 可以看到Array也是傳入參考的副本,所以在函式內的修改會影響原值。
 
String 的測試

    @Test
    public void testString()
    {
        String name = "foxx";
        System.out.println("before name:"+name);
        changeName(name);
        System.out.println("after name:"+name);
    }
    private void changeName(String name)
    {
        name = "xxof";
        System.out.println("in changeName name:"+name);
    }

output:

before name:foxx
in changeName name:xxof
after name:foxx

String 的結果就不太一樣了,它雖然不是基本型別,但測試結果卻和基本型別一樣。 
在函式內的修改不會影響原值。
 
再補上 Integer 的測試

    @Test
    public void testInteger()
    {
        Integer integer = new Integer(10);
        System.out.println("before integer:"+integer);
        changeInteger(integer);
        System.out.println("after integer:"+integer);
    }
    private void changeInteger(Integer integer)
    {
        integer = Integer.MAX_VALUE;
        System.out.println("in change integer:"+integer);
    }

output:

before integer:10
in change integer:2147483647
after integer:10

看起來 Integer 也是和基本型別相同,在函式中的改變不會影響外部。
 
 
 
 
 
 

分類
Java

檢查方法參數的有效性

撰寫方法時容易忽略”非正常流程”的處理,特別是在趕工的時候。
對於檢查方法參數的有效性也有一套準則,從 Effective Java 整理出來。
Rule:
1.對於方法或是建構式的參數限制應該寫在註解中,並在方法的開頭檢查參數。
2.public 方法或是建構式可以使用 throw new xxxException 並在方法宣告加上註解@throws 說明違反參數規則會出現的異常。
3.private 方法可以使用 assert 斷言來限制, assert 必須開啟才有效。
(For eclipse → right click class in package explorer → run as → run configuration → arguments → VM arguments → type -ea)
4.不需要作檢查的情況為檢查成本太大,或是檢查會隱含在計算過程中(即使做了檢查也沒多大意義)。


 
jdk 中 File 類別的建構式,第7行檢查參數的有效性不可為null。

    /**
    ...
     * @throws  NullPointerException
     *          If the pathname argument is null
     */
    public File(String pathname) {
        if (pathname == null) {
            throw new NullPointerException();
        }
        this.path = fs.normalize(pathname);
        this.prefixLength = fs.prefixLength(this.path);
    }

另一個私有建構式也做了參數的檢查(第7,8行)

    /**
     * Internal constructor for already-normalized pathname strings.
     * The parameter order is used to disambiguate this method from the
     * public(File, String) constructor.
     */
    private File(String child, File parent) {
        assert parent.path != null;
        assert (!parent.path.equals(""));
        this.path = fs.resolve(parent.path, child);
        this.prefixLength = parent.prefixLength;
    }

 
 
至於第4點的後段”檢查會隱含在計算過程中” e.g.

    private File createFile(String filePath)
    {
        assert filePath != null;
        File result = new File(filePath);
        return result;
    }

第3行使用了 assert 來檢查傳入的參數是否為 null , 其實就是不需要的檢查。若是傳入 null,在第4行會丟出NullPointerException,
而不需要在方法開頭檢查。
 
 
 

分類
Java

自定義異常(customize exception)

自定義異常目的
為了表示應用程序的一些特定錯誤訊息並提供新的含意。
如考慮伺服器的連線問題雖然可以全部使用 IOException 來表示,但各種連線問題卻可再度細分,如
伺服器關閉(ServiceClosedException) , 連線超時(ConnectTimeoutException),使用者中斷(ClientInterruptException)等等。
 
Note :
自定義異常必須在Throwable 體系中派生,否則不能在應用程序中傳播。
不可直接繼承Throwable,因為Throwable已經定義好2個主要分支(Error & Exception)
也不要繼承自Error 及其子類別,因為自定義通常不符合錯誤標準。
一般繼承自Exception


 
 
自定義異常步驟
1.定義異常類別
需要繼承Throwable 或是Throwable的子類別,並提供多個建構式,最好能夠傳入String 以便儲存錯誤訊息。

public class ServiceClosedException extends Exception
{
    private Date mConnectDate = new Date();
    public ServiceClosedException() {
    }
    public ServiceClosedException(String message) {
        super(message);
    }
    public ServiceClosedException(String message, Date connectDate){
        super(message);
        mConnectDate = connectDate;
    }
    public Date getConnectDate(){
        return mConnectDate;
    }
}

 
2.聲明方法拋出自定義類別

public void connectService() throws ServiceClosedException
{
}

 
3.找到故障點,建立異常並加上關鍵字 throw

public void connectService() throws ServiceClosedException
{
        if(!mIsServiceConnect){
            throw new ServiceClosedException();
        }
}

 
4.自定義異常完成後,client呼叫會丟出自定義異常的方法就必須處理或是聲明該自定義異常

    @Test
    public void testConnectService()
    {
        try {
            connectService();
        } catch (ServiceClosedException e) {
            e.printStackTrace();
        }
    }

 
 

分類
Java

處理異常的選項(handle or declare)

1.處理(handle) : 直接捕獲異常並處理。

處理異常動作:使用異常處理區塊(try-catch-finally)

  1. try:區塊內放置可能拋出異常語句。
  2. catch:區塊內處理異常的動作,動作步驟為產生異常後,try區塊停止動作,JVM尋找相關的catch區塊。可能會有多個catch區塊,異常會交給第一個符合的catch 區塊處理。
  3. finally:無論try catch 是否會執行,finally區塊一定會執行

處理異常規則:
try之後必須接著catch 或是 finallycatchfinally可以一起出現,但必須至少有一個。
在多個catch 區塊的情況下,經驗法則為從最具體排到最一般。
選擇處理的優點:在本地端直接處理,容易找到發生問題點,方法調用者不須多寫處理。
選擇處理的缺點:缺少通用性,無法選擇其他的處理方式,因為必須多寫處理的代碼因此增加該方法的複雜度。


 
 

2.聲明(declare) : 聲明該方法拋出異常,讓呼叫端接手處理。

聲明異常動作:在方法宣告中加上 throws ExceptionName
聲明異常的規則:

  1. 必須聲明方法中可能拋出的可檢測異常(checked exception),非檢測異常(unchecked exception)不是必須的,可聲明也可以不聲明。
  2. 聲明異常類別可以是其異常子類。

選擇聲明原因:

  1. 需要傳送異常發送到系統其他部份。
  2. 無法在本地端處理異常。

 
 

3.選擇處理或是聲明異常

經驗法則:盡可能處理異常,若無法處理才聲明異常。
 


 
 

4.標準異常處理選項

1.紀錄異常相關訊息

  • 使用標準輸出或錯誤流
  • 開發自定義類
  • 使用java 紀錄api (java.util.logging)

2.要求客戶端輸入訊息
透過GUIclient 選擇下一步
3.使用預設值或替換數據

  • 簡單情況將預設值設定為靜態常量加在程式碼中。
  • 複雜情況可將預設值紀錄在配置文件(Properties)中,並在發生問題時載入。

4.將控制轉移到應用程序的其他部份

  • 簡單情況提取catch 處理方法
    try {
          result = bufferReader.readLine();
        } catch (IOException e) {
            handleReadLineException();
        }
    }
    private void handleReadLineException(){
    // do handle exception for read line
    }
  • 複雜情況開發一個異常處理類別,來集中處理應用程序代碼

5.將異常轉換成其他形式

  • 可提供更適當的上下文用來描述異常
private void connect(String ip) throws ServerConnectionException
{
        try {
            //some exception
        } catch (IOException e) {
            throw new ServerConnectionException ("specify message from Server Connect", e);
        }
}

6.準備停止系統

  • 有打開的文件就關閉
  • 有連接的資源就關閉
  • 儲存必要資訊
  • 通知其他部份應用程序即將結束

7.重試操作

  • 等待一段時間後重試操作
try {
            connect();
    } catch (IOException e) {
            retryConnect();
    }
private void retryConnect()
{
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        try{
            connect();
        }catch(IOException e){
            //retry connect
        }
    }

 
8.替換或恢復操作

  • 如無法儲存在伺服器上就改為存在本機硬碟
       try {
            saveToServer();
        } catch (IOException e) {
            saveToLocalDisk();
        }
    private void saveToLocalDisk(){
    }

 
9.忽略問題

  • 當異常對系統其他部份沒有影響,如調用 I/O stream的關閉方法
private FileReader mFileReader;
...
...
try {
      mFileReader.close();
} catch (IOException e)
{
      e.printStackTrace();
}

 


 
 

5.解決異常建議事項

  • 盡可能處理異常:盡可能處理,除非條件不予許再考慮聲明異常。
  • 具體問題具體解決:需要為特定類型的問題建立特定的處理區塊。
  • 紀錄可能影響應用程序運行的異常:可以採取永久的方式紀錄影響應用程序的異常,可以幫助處理問題
  • 根據情況將異常轉換為業務上下文:建立異常的成本不高,可以考慮為特定的問題轉換特定的異常。

 
 

6.解決異常不建議事項

  • 不要忽略異常
try {
      printStream = new PrintStream(new File("./error_record"));
      System.setOut(printStream);
      printStream.println("error message");
     } catch (FileNotFoundException e)
{//do nothing here}

 

  • 不要使用覆蓋式異常處理
    try {
          printStream = new PrintStream(new File("./error_record"));
          System.setOut(printStream);
          printStream.println("error message");
        } catch (Exception e) {
               // do something
        }
  • 不要將特定異常轉為一般異常
    try {
          printStream = new PrintStream(new File("./error_record"));
          System.setOut(printStream);
          printStream.println("error message");
        } catch (IOException e) {
                throw new Exception(exceptionMessage);
        }
  • 不要處理能夠避免的異常(使用判斷式取代拋出異常)
  • 使用
    if(printStream != null){
        printStream.println("error message");
    }
    取代
    try{
         printStream.println("error message");
    }catch(NullPointerException e){
         System.out.println(''PrintStream is null'');
    }

     

 
 
 
 
 

分類
Java

可檢測異常與非檢測異常(Checked Exception and Unchecked Exception)

可檢測異常(Check Exception)與非檢測異常(Uncheckecd Exception)

 

1.非檢測異常不需要強制處理或聲明,為 Error RuntimeException 2個類別。e.g.

public void throwUncheckedException(String target)
{
        if(target == null)
        {
            throw new NullPointerException();
        }
}

throwUncheckedException 方法為很常見的檢查方法參數有效性的動作,在方法中(第5行)會拋出NullPointerException。
 
因為 NullPointerException 為 Unchecked Exception,所以不需要在方法宣告位置(第1行)加上聲明的處理 e.g.

public void throwUncheckedException(String target) throws NullPointerException
{
        if(target == null)
        {
            throw new NullPointerException();
        }
}

Unchecked Exception 不需要強制呼叫端處理,可以直接呼叫沒問題。e.g.

@Test
public void testThrowUncheckedException()
{
        throwUncheckedException(null);
}

 
 


 

2.可檢測異常必須強制處理或聲明,主要為 Exception 及其子類別(除了RuntimeException以外)e.g.

public void throwCheckedException(String target) throws IOException
{
        if(target == null)
        {
            throw new IOException();
        }
}

throwCheckedException 在方法中(第5行)會拋出IOException,因為 IOException 屬於 Checked Exception,必須強制處理。
所以可以選擇在方法宣告聲明 IOException,讓呼叫端去處理或是在原始位置處理 e.g.

public void throwCheckedException(String target)
{
        if(target == null)
        {
            try {
                throw new IOException();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}

 
如果選擇在方法宣告聲明IOException , 那呼叫端就必須去處理 IOException e.g.

@Test
public void testThrowCheckedException()
{
        try {
            throwCheckedException(null);
        } catch (IOException e) {
            e.printStackTrace();
        }
}

 

分類
Android Java Uncategorized

工具類別的實體化限制(Prevents Utility class instantiated)

工具類別(Utility Class)類似於 java.lang.Math,該類別提供公共靜態方法給外界使用。
因此工具類別並不需要該實體的產生,但編譯器會提供給每個類別一個對外的預設建構式。
因此我們必須修改預設建構式避免外部可以實體化工具類別。

public final class ExceptionPrinter
{
    private ExceptionPrinter(){
        //Prevents utility class being instantiated
        throw new AssertionError();
    }
    public static void printError(String TAG, Exception e)
    {
        StringWriter errorMessage = new StringWriter();
        e.printStackTrace(new PrintWriter(errorMessage));
        Log.e(TAG, errorMessage.toString());
    }
}

 
 
 

分類
Java

Enum(列舉) in java

列舉(enum) 為一組有限集合(固定的常量)的類型,每個列舉成員都為靜態 final 的特徵。
因為列舉為單例(Singleton),因此外部無法創造列舉的實例,也不能進行擴展。
有以下幾種常用的情況
 

1.使用 enum 取代狀態常量

狀態常量通常為一組相似的常量用來表示某種狀態,並根據這些常量來判斷進行動作。e.g.

public class ErrorHandler {
    public static final int ALARM_ARRAY_DATA_ERROR = 1;
    public static final int SET_ALARM_ARRAY_ERROR = 2;
    public static final int OUT_OF_BOUNDARY_ERROR = 3;
...
    private void checkErrorCode(int errorCode){
        switch (errorCode) {
            case ErrorHandler.ALARM_ARRAY_DATA_ERROR:
                ...
                break;
            case ErrorHandler.SET_ALARM_ARRAY_ERROR:
                ...
                break;
            case ErrorHandler.OUT_OF_BOUNDARY_ERROR:
                ...
                break;
            }
    }
}

 
第3到5行定義一組代表不同的錯誤狀態常量,並在第9行的方法中判斷錯誤狀態並進行相對動作。
這種狀態常量稱為(int enum pattern),具有幾種缺點。
1. 無法百分之百確定傳入switch的數值範圍處於定義的範圍之內。
2. 若想印出錯誤狀態,必須另外再寫方法。
使用 enum 取代 狀態常量。e.g.

public class ErrorHandler{
...
   public enum ErrorType{
        ALARM_ARRAY_DATA_ERROR,
        SET_ALARM_ARRAY_ERROR,
        OUT_OF_BUNDARY_ERROR
    }
...
    public void checkErrorCode(ErrorType errorType){
        switch (errorType) {
            case ALARM_ARRAY_DATA_ERROR:
                ...
                break;
            case SET_ALARM_ARRAY_ERROR:
                ...
                break;
            case OUT_OF_BOUNDARY_ERROR:
                ...
                break;
            }
    }
...
}

enum 的命名跟隨 class 的命名,大寫開頭,其成員由於具有 static final 的特性故使用全大寫加上底線命名。
如果 ErrorType 只會被使用在 ErrorHandler 中,存取權限為 private。若會被多個 class 使用就獨立為一個 public 類別。

2.將方法或是屬性加到 enum。

用於想讓 enum 的成員具有其他資訊,ErrorType 也具有數值的表示。

    public enum ErrorType{
        ALARM_ARRAY_DATA_ERROR(0),
        SET_ALARM_ARRAY_ERROR(1),
        OUT_OF_BUNDARY_ERROR(2);
        private int mNumber;
        private ErrorType(int number){
            mNumber = number;
        }
        public int getNumber(){
            return mNumber;
        }
    }

 
 

3.常用的 enum 方法。

    public void showAllEnumMembersByValues() {
        for(ErrorHandler.ErrorType errorType: ErrorHandler.ErrorType.values()){
            System.out.println("errorType.toString():"+errorType.toString());
        }
    }
    public static void checkMemberNameInEnum(){
        ErrorHandler.ErrorType.valueOf(ErrorHandler.ErrorType.ALARM_ARRAY_DATA_ERROR.toString());
        ErrorHandler.ErrorType.valueOf("no this member");
    }

第2行 values() 會依照宣告的順序傳回 enum 的成員們。
第3行 toString() 預設的情況下會回傳成員本身名稱。
第8及第9行 valuesOf() 會檢查傳入的字串是否符合成員的名稱,是的話回傳該成員,否的話丟出 illegalArgumentException。
因此第9行會丟出例外。
 

4.應該使用列舉的狀況為需要一組固定常量的時候,也包含真實世界的現象,如星期的天數,月份等等。

 

5.不要使用 ordinal() 來取得 enum 成員的順序。

ordinal() 會回傳每個 enum 成員在該 enum 中的位置,但若改變其他成員的順序,ordinal() 回傳值會不如預期。
如果想要確實的取得 enum 成員的相關數值,那就另外新增一個屬性並用該屬性來保存(參考第2點)。
 

6.若有相同的方法,但每個 enum member 的實現都各有不同,可以新增 abstract method 再讓各 member 去實現。

public enum ErrorType {
        ALARM_ARRAY_DATA_ERROR(0) {
            @Override
            String getNote() {
                return "alarm error because...";
            }
        },
        SET_ALARM_ARRAY_ERROR(1) {
            @Override
            String getNote() {
                return "set alarm array error because ...";
            }
        },
        OUT_OF_BUNDARY_ERROR(2) {
            @Override
            String getNote() {
                return "out of bundary error because...";
            }
        };
        private int mNumber;
        private ErrorType(int number) {
            mNumber = number;
        }
        abstract String getNote();
        public int getNumber() {
            return mNumber;
        }
    }

第27行的 getNote() 對於每個 enum member 來說都不相同,我們把它設定為 abstract 並讓 member 去分別實作它。
 

7. EnumSet 的使用,EnumSet 的使用方式和一般的 Set 沒有甚麼不同。

差別在於 EnumSet 只能接受 enum 的成員加入,優點是EnumSet 的效率相當高。

    public void testEnumSet(){
        EnumSet<ErrorHandler.ErrorType> errorTypes = EnumSet.noneOf(ErrorHandler.ErrorType.class);
        errorTypes.add(ErrorType.ALARM_ARRAY_DATA_ERROR);
        errorTypes.addAll(EnumSet.of(ErrorType.ALARM_ARRAY_DATA_ERROR, ErrorType.OUT_OF_BUNDARY_ERROR));
        errorTypes.remove(ErrorType.ALARM_ARRAY_DATA_ERROR);
    }

第2行 EnumSet 建構式比較特別,參數必須指定 enum class。
 

8. EnumMap 的使用與 EnumSet 類似,其鍵值必須來自 enum 的 member。

其他的使用方式就如同一般的 Map。

EnumMap<ErrorHandler.ErrorType , String > errorTypes = new EnumMap<ErrorHandler.ErrorType, String>(ErrorHandler.ErrorType.class);
        errorTypes.put(ErrorType.ALARM_ARRAY_DATA_ERROR, ErrorType.ALARM_ARRAY_DATA_ERROR.toString());
        System.out.println("errorTypes.get(ErrorType.ALARM_ARRAY_DATA_ERROR):"+errorTypes.get(ErrorType.ALARM_ARRAY_DATA_ERROR));
        errorTypes.clear();

第1行建構式必須指定 enum class。