Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發筆記之:用Enum(枚舉類型)取代整數集的應用詳解

Android開發筆記之:用Enum(枚舉類型)取代整數集的應用詳解

編輯:關於Android編程

在Android的API中可以發現有很多用整數集來作為參數的地方,先來看一下實例。
LinearLayout是大家所熟知的一個UI基本元素,它裡面有一個方向的屬性,可以通過以下方法來設置:
復制代碼 代碼如下:
LinearLayout.setOrientation(int);

使用的時候,通常都是這樣:
復制代碼 代碼如下:
LinearLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.setOrientation(LinearLayout.VERTICAL);

但也可以這樣使用:
復制代碼 代碼如下:
LinearLayout.setOrientation(0); // LinearLayout.HORIZONTAL = 0
LinearLayout.setOrientation(1); // LinearLayout.VERTICAL = 0x01

甚至可以這樣:
復制代碼 代碼如下:
LinearLayout.setOrientation(Integer.MAX_VALUE);
LinearLayout.setOrientation(Integer.MIN_VALUE);
LinearLayout.setOrientation(2012);

因為方法setOrientation接收的參數是一個整數,所以你可以傳任意合法的整數---至少這在編譯時不會有任何問題。它只會在運行時可能引發問題,但如你所知,開發者只關注程序能否編譯成功,至於運行時,那是用戶關心的事兒,因為開發者不一定使用他們所開發出的程序。

除了這個例子,在Android的API中到處可以看到這種API,比如設置View的可見性,設置Wifi狀態等等。都是定義了整數集,然後用整數來做為參數,並寄希望開發者能傳遞整數集中定義的常量來作為參數。但如你所知,並不是每個人都那麼的守規矩,如果每個人都能遵守規則,這個世界就真的和諧了,蛋扯遠了。
因為開發者通常只能關注編譯,所以如果能把這個規則應用在編譯時,那麼就會大大減少出錯的可能。有興趣的朋友可以去試試看,給這些接收整數參數的方法傳一些“平常”的數值,比如2012,Integer.MAX_VALUE,Integer.MIN_VALUE等等,看會出現什麼狀況。
另外,如果開發者傳遞與常量定義一致的整數值,雖然編譯運行都不會有錯,但代碼的可讀性會大大的降低,比如:
復制代碼 代碼如下:
LinearLayout.setOrientation(0);
LinearLayout.setOrientation(1);

這完全沒有錯,但是代碼的閱讀者和維護者通常都會蛋疼的。
當然,Android自身還是有保護措施的,如果對API傳遞不合法參數,不會造成其他影響,只是設置不能生效,但API會使用默認值,因為對於每個內置參數,都有相應的默認值。如LinearLayout的orientation,默認值就是LinearLayout.HORIZONTAL,所以如果對setOrientation()傳入非法值,LinearLayout會保持水平排列,無其他影響。後面有個對Linearlayout的orientation做的試驗。
另外,如果在Layout XML文件中設置這些屬性就不會有些問題,如:
復制代碼 代碼如下:
<LinearLayout
android:orientation="vertical"
android:gravity="center">

因為XML布局會在編譯時被處理,如果有非法的值,會有編譯錯誤的。我想這也就是Android特別鼓勵開發者用XML來制作所有的布局的一個原因吧。實例,三個沒有設置指向的線性布局,默認是水平放置,在代碼中設置了幾個離譜的值,發現它們還是水平的,也就是說設置離譜的值不會出錯,但也不起作用:運行結果如下:


代碼如下:
復制代碼 代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    >
    <LinearLayout
        android:id="@+id/linearlayout_test_1"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content">

   <TextView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:textColor="#ff00ff00"
     android:background="#aa331155"
     android:layout_weight="1"
     android:textSize="18sp"
     android:text="Microsoft"
        />
   <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#ffff0000"
        android:background="#aa117711"
        android:layout_weight="1"
        android:textSize="18sp"
        android:text="Apple"
        />
   <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#ff0000ff"
        android:background="#aa774411"
        android:layout_weight="1"
        android:textSize="18sp"
        android:text="Google"
        />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/linearlayout_test_2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

      <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textColor="#ff00ff00"
           android:background="#aa331155"
           android:layout_weight="1"
           android:textSize="18sp"
           android:text="Microsoft"
           />
      <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textColor="#ffff0000"
           android:background="#aa117711"
           android:layout_weight="1"
           android:textSize="18sp"
           android:text="Apple"
           />
      <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textColor="#ff0000ff"
           android:background="#aa774411"
           android:layout_weight="1"
           android:textSize="18sp"
           android:text="Google"
           />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/linearlayout_test_3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

      <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textColor="#ff00ff00"
           android:background="#aa331155"
           android:layout_weight="1"
           android:textSize="18sp"
           android:text="Microsoft"
           />
      <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textColor="#ffff0000"
           android:background="#aa117711"
           android:layout_weight="1"
           android:textSize="18sp"
           android:text="Apple"
           />
      <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textColor="#ff0000ff"
           android:background="#aa774411"
           android:layout_weight="1"
           android:textSize="18sp"
           android:text="Google"
           />
    </LinearLayout>
</LinearLayout>

和:
復制代碼 代碼如下:
package com.android.explorer;
import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
public class LinearLayoutTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.linearlayout_test);
        LinearLayout one = (LinearLayout) findViewById(R.id.linearlayout_test_1);
        one.setOrientation(2012);
        LinearLayout two = (LinearLayout) findViewById(R.id.linearlayout_test_2);
        two.setOrientation(Integer.MAX_VALUE);
        LinearLayout three = (LinearLayout) findViewById(R.id.linearlayout_test_3);
        three.setOrientation(Integer.MIN_VALUE);
    }
}

用Enum代替整數集
其實很簡單,用Enum(枚舉)就可以很方便的解決這個問題,使用起來也不比定義整數集繁瑣,同樣的可讀。另外的優點就是,它的封裝更好,最重要的是它會在編譯時被檢查。因為Java是一種Strong Type,也就是說在編譯時,編譯器會對所有原型類型和參數類型進行檢查,如果類型不對,並且沒有強制轉型的,就會報出編譯錯誤,當然編譯器所支持的自動轉型除外。比如一個需要int,而傳的參數是long,雖然都差不多,沒有溢出等,但還是會有編譯錯誤。
所以,如果LinearLayout使用Enum,就像這樣定義:
復制代碼 代碼如下:
public class LinearLayout extends ViewGroup {
    private Orientation mOrientation;

    public enum Orientation {
        HORIZONTAL, VERTICAL
    };

    public void setOrientation(Orientation dir) {
        mOrientation = dir;
    }
}

然後這樣使用:
復制代碼 代碼如下:
import android.widget.LinearLayout;
LinearLayout.setOrientation(Orientation.HORIZONTAL);
LinearLayout.setOrientation(Orientation.VERTICAL);

那麼,開發者就不會用錯了,因為首先,它看到setOrientation所需要的參數是一個Orientation的枚舉類型,就會自然的傳送Orientation中定義的類型;另外,如果傳其他的值,比如0或者1,編譯器也不會答應的。

可悲的是Android中幾乎所有的API都是以整數集的方式來定義的,所以就要時刻提醒自己和組裡的人,一定要傳所定義的整數集中的常量。
那麼我們能做的,除了要傳整數集中定義的常量,對於那些以整數集方式定義的API,以外。更重要的是當自己定義接口的時候,盡量用Enum而不要使用整數集。
還有一點需要注意的是,對於某些弱類型語言,也就是說在編譯時不會對類型做特別細致的檢查,比如C++,C等,那麼即使使用了Enum,也不一定安全,因為對於C++和C來講Enum中的常量與整數常量完全一樣,連編譯器都分不清。所以,對於這類語言,只能寄希望於開發者了。
後記:
寫完這篇,讓我想起了另外一些與參數定義相關的問題,比如布爾型參數也不是一個很好的設計,因為使用者很難到底應該傳True還是傳False,特別是當方法名字不能體現Boolean參數作用時和文檔不夠清楚的時候。如果只有一個參數還好,根據方法名字和常識都能知道,比如:
復制代碼 代碼如下:
Button.setEnabled(true); // enable the button
Button.setEnabled(false); // disable the button

但對於某些情況,當方法的名字不能體現Boolean參數的作用時,或是多於一個參數時,而方法的主要目的又不能體現Boolean參數的作用時,就很不清楚,比如:
復制代碼 代碼如下:
// com/android/mms/data/ContactList.java
public String[] getNumbers(boolean);

您能猜出來這個boolean變量是決定是否要為彩信對聯系人做特殊的處理嗎?您在使用這個API的時候能很快知道該傳True還是該傳False嗎?當讀到這些語句的時候:
復制代碼 代碼如下:
String[] mms = getNumbers(true);
String[] sms = getNumbers(false);

您能知道True和False的含義與作用嗎?至少我看到這樣的代碼時,如果不去跟蹤它的實現,是猜不出來的。
但現實的問題是,API通常又需要從調用者那裡得到做還是不做的決定。一個可行的途徑是用方法來封裝和隱藏,比如:
復制代碼 代碼如下:
Button.setEnabled(true); // enable the button
Button.setEnabled(false); // disable the button

可以改成:
復制代碼 代碼如下:
Button.enable();
Button.disable();

這是簡單的情況,對於稍復雜的情況,比如後一個例子,可以添加另外的接口,而不是用重載方法,但內部的實現,可能還是需要重載,但是這就把問題縮小了,起碼對使用者來說是隱藏的:
復制代碼 代碼如下:
// com/android/mms/data/ContactList.java
public String[] getNumbersForSms();
public String[] getNumbersForMms();

這樣一來,對外來講就是良好的封裝。內部實現可能還是需要一個類似這樣的私有方法:
復制代碼 代碼如下:
// com/android/mms/data/ContactList.java
public String[] getNumbersForSms() {
   return getNumbers(false);
}
public String[] getNumbersForMms() {
   return getNumbers(true);
}
private String[] getNumbers(boolean) {
   // implementation
}

但至少把問題縮小化了,也可以加上注釋來說明。就不必導致使用者來猜方法的用法和含義了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved