寫Android數據庫經常出錯,總想定義好相關的常量,在寫insert,delete的時候就可以直接選擇字段常量,減少出錯的機會,找了這篇博文寫的不錯的分享一下,原帖:https://blog.csdn.net/lilinjie_blog/article/details/81396593
在Java中定義常量的方式
1.在類中定義
public final class Constants {
private Constants() {}
public static final int A = 0;
public static final int B = 1;
public static final int C = 2;
public static final int D = 3;
}
2.在接口中定義
public interface Constants {
int A = 0;
int B = 1;
int C = 2;
int D = 3;
}
那麼這兩種方式區別在哪裏呢?
1.類定義常量,需要定義成final且定義一個private的構造方法, 這樣做是爲了不讓其他類繼承, 禁止實例化此類,調用時直接以”類.常量”的方式調用。
2. 接口中定義的”變量”,其實就是常量,接口中的”變量”默認都是 “public static final”類型,即爲常量, 因此接口可以省略”public static final”而直接寫成 “type variable”。
3.用如上的類定義常量,不能實例化也不能被繼承, 看起了完美無缺。
4.接口定義常量, 雖不能實例化, 確可以被其他類實現。因此有這麼一種設計模式”The constant interface pattern”,所謂的”常量接口模式”就使用這些常量的類實現這個接口,以避免用類名修飾常量名。其實這是對接口的不良使用。接口中定義的常量應爲所有類頻繁使用的常量,但並不是每個類都使用了接口中定義的所有常量,因而導入了不必要的常量到這個類中,並且導入後這個類的子類也會導入基礎的常量, 這樣會導致混亂,應當避免此種用法。並且Effective Java中第19條也明確指出接口應該只被用來定義類型,他們不應該被用來導出常量。但是呢我們大可不必拘泥於接口這一含義他是對行爲的一種抽象。如果該接口僅僅是作爲定義常量使用,那麼我們可以直接通過”接口名.變量名”調用。這樣也就避免了實現改接口,從而將接口中不必要的常量導入其中,但是這也取決於我們合理的使用。
5. 在interface和class中定義相同的常量,interface生成的class文件比class生成的class文件會更小,而且更簡潔, 效率更高。
小結:
1. 不要使用”常量接口模式”,此模式會導致類中的常量混亂,增加維護難度。
2. 不要使用靜態導入,import static **。我非常不贊同這種使用常量的方法,因爲import static會導致可維護性下降,維護的人看代碼時, 不是那麼清楚或者不那麼迅速的知道這個常量位於哪個文件中。 建議使用常量的地方直接 “接口.常量名” 的方式使用。
3. 對於用是用interface定義常量還是使用class定義常量,看個人喜好,個人覺得interface定義常量更爲優美, 代碼更簡潔,生成的class文件更小, JVM不要考慮類的附加特性, 如方法重載等, 因而更爲高效。雖然這是一種反模式的用法, 很多人不喜歡這種用法,如果我們知道它的優缺點,延長避短, 也是無可厚非的。還有一點是不要把這種用法用成”常量接口模式” , 個人覺得”常量接口模式”確實是一種對interface的”pool use”。
使用枚舉代替常量,簡化工作!
需求:有一個任務(Task),服務端定義了一個int status來標記任務的狀態,包括 未開始、進行中、已完成 這三個狀態,分別用 0、1、2 來標記。我們拿到這個status之後,需要根據狀態的不同,顯示不同的文本(需求仍在挖掘中)。需求很明確,一般我們會這麼做:
在全局的常量類中定義定義3個靜態常量表示狀態常量:
public class Constants {
public static final int STATUS_UN_START = 0;
public static final int STATUS_PROGRESSING = 1;
public static final int STATUS_COMPLETED = 2;
}
接下來在strings.xml定義狀態值:未開始、進行中、已完成
<resources>
<string name="status_un_start">未開始</string>
<string name="status_progressing">進行中</string>
<string name="status_completed">已完成</string>
</resources>
最後,在代碼中拿到status之後,我們會這麼做:
switch (status) {
case Constants.STATUS_UN_START:
textView.setText(getResources().getString(R.string.status_un_start));
break;
case Constants.STATUS_PROGRESSING:
textView.setText(getResources().getString(R.string.status_progressing));
break;
case Constants.STATUS_COMPLETED:
textView.setText(getResources().getString(R.string.status_completed));
break;
default:
break;
}
這樣寫,所有的狀態和狀態值都統一了,規範的很,效果也不錯,毫無違和感!
突然有一天,產品汪找到你:Hi,我們把文本前面的顏色設置成動態的吧,不同的狀態顏色不一樣,你根據效果圖改一下吧…”。還好這個簡單,分分鐘的事。在colors.xml中,加幾個色值:
<resources>
<color name="status_un_start">#3F51B5</color>
<color name="status_progressing">#303F9F</color>
<color name="status_completed">#FF4081</color>
</resources>
然後修改switch語句,這樣:
switch (status) {
case Constants.STATUS_UN_START:
view.setBackgroundResource(R.color.status_un_start);
textView.setText(getResources().getString(R.string.status_un_start));
break;
case Constants.STATUS_PROGRESSING:
view.setBackgroundResource(R.color.status_progressing);
textView.setText(getResources().getString(R.string.status_progressing));
break;
case Constants.STATUS_COMPLETED:
view.setBackgroundResource(R.color.status_completed);
textView.setText(getResources().getString(R.string.status_completed));
break;
default:
break;
}
So easy,很快解決了問題!慢慢的,隨着開發的深入,你發現許多地方需要這麼顯示,於是就封裝了一個自定義ViewGroup,一切都朝着好的方向發展。但除此之外,在其他地方,也需要判斷這個狀態值,以需要獲取該狀態下的屬性,萬一哪天產品汪跟你說:“我要多加一個狀態,未知(UN_KONW)。服務端和iOS已經同步了,你這邊趕緊吧”,意味着之前涉及狀態判斷的都需要找到加上,豈不傻眼了?
所以,大多數人還是會選擇將這一部分寫到全局的靜態方法中,這樣加字段、加狀態再也不怕了,在對應文件中加幾個常量【status、顏色、狀態文本】,改一下方法就搞定!由於這塊不是本文重點,就不貼代碼了。至此,我們的程序一步步完善了,一些基本的套路已經無法難倒我們了,完美!功能基本實現了,但是回過頭來想想,這樣做並不優雅!一方面需求變動時我們還是需要改很多地方,另一方面我們並不能很直觀的看出狀態值status與狀態文本、色值之間的關係,比如 0代表未開始,顏色爲黃色。當然,可以向上文一樣,依靠命名規範去解決這個問題。可是,在命名規範的基礎上,有沒有更好的辦法進一步強化他們之間的關係呢?當然有!接下來,就到了今天的主題,一起來藉助枚舉完成這一任務。
—————–枚舉登場—————————–
剛開始,我們可以定義一個枚舉類,這麼寫
public enum TaskStatus {
UN_START(0, "未開始"),
PROGRESSING(1, "進行中"),
COMPLETED(2, "已完成");
private int status;
private String desc;
TaskStatus(int status, String desc) {
this.status = status;
this.desc = desc;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static TaskStatus getStatus(int status) {
for (TaskStatus taskStatus : values()) {
if (status == taskStatus.status) {
return taskStatus;
}
}
return UN_START;
}
}
用的時候簡直不要太簡單(status是從服務端獲取的狀態值):
TaskStatus taskStatus = TaskStatus.getStatus(status);
textView.setText(taskStatus.getDesc());
然後後來產品說加一個色值,你很坦然就加上了。這麼寫:
public enum TaskStatus {
UN_START(0, "未開始", "#3F51B5"),
PROGRESSING(1, "進行中", "#303F9F"),
COMPLETED(2, "已完成", "#FF4081");
private int status;
private String desc;
private String color;
TaskStatus(int status, String desc, String color) {
this.status = status;
this.desc = desc;
this.color = color;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public static TaskStatus getStatus(int status) {
for (TaskStatus taskStatus : values()) {
if (status == taskStatus.status) {
return taskStatus;
}
}
return UN_START;
}
}
用的時候毫不影響:
TaskStatus taskStatus = TaskStatus.getStatus(status);
textView.setText(taskStatus.getDesc());
view.setBackgroundColor(Color.parseColor(taskStatus.getColor()));
重點來了,該到加未知(UN_KNOW)狀態的時候了,按照上文的做法,我們要改、加的東西很多,但現在只需要加一行代碼,是的,一行代碼:
public enum TaskStatus {
UN_KNOWN(-1, "未知", "#34D6B5"),
UN_START(0, "未開始", "#3F51B5"),
PROGRESSING(1, "進行中", "#303F9F"),
COMPLETED(2, "已完成", "#FF4081");
private int status;
private String desc;
private String color;
TaskStatus(int status, String desc, String color) {
this.status = status;
this.desc = desc;
this.color = color;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public static TaskStatus getStatus(int status) {
for (TaskStatus taskStatus : values()) {
if (status == taskStatus.status) {
return taskStatus;
}
}
return UN_KNOWN;
}
}
就是這麼簡單!同時,這麼寫的話,就不需要在 string.xml、colors.xml、常量類中加一堆值了,直接放在枚舉類中很直觀有木有?!當然,有時候我們需要在某個地方使用常量來判斷,下圖是常見的做法。現在我們把狀態值寫到枚舉裏面了,還能這麼直觀的用嗎?答案是肯定的!
//使用常量
if(status==Constants.STATUS_PROGRESSING){
//do something
}
//使用枚舉
if(status=TaskStatus.PROGRESSING.getStatus()){
//do something
}
聲明一點,我所說的使用枚舉替換常量,是針對類似於“常量之間存在關聯”的情況,如狀態值status與狀態文本、色值,此時使用枚舉能大大簡化我們的工作,並不是說以後所有常量都寫成枚舉,畢竟官方是不推薦使用枚舉的:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
但也只是儘量避免使用!所以在實際開發中,還需要根據實際使用場景去斟酌,杜絕濫用。像本文描述的場景,建議使用,戰鬥力翻倍!
Android使用枚舉註解代替枚舉
需求:我們僅僅需要根據一組狀態中的某一狀態執行相應的操作,不需要多個常量之間相互對應。你自然會想到枚舉的簡化版:
public enum TaskStatus {
UN_KNOW,
UN_START,
PROGRESSING,
COMPLETED
}
然後定義了一個任務輔助類:
import com.ruicb.enumdemo.TaskStatus;
public class TaskHelper {
public static void doSth(TaskStatus status){
switch (status){
case UN_KNOW:
//do something
break;
case UN_START:
break;
case PROGRESSING:
break;
case COMPLETED:
break;
}
}
}
然後如此調用:
TaskHelper.doSth(TaskStatus.COMPLETED);
這樣使用枚舉,十分優雅:保證了類型安全:調用者無法隨意傳一個 int 值;代碼可讀性非常高;但是…枚舉對於性能的損耗,事實上你也知道,官方是不建議使用枚舉的,這個在文章開頭簡單說了。想詳細瞭解的,可以參考該博文:http://blog.csdn.net/hp910315/article/details/48975655。總之,與靜態常量相比,枚舉會增大應用程序編譯後的 dex 文件,同時應用在運行時的內存佔用也會升高。在資源有限的移動設備上,大量的使用枚舉無疑是致命的。
替代方案
既然如此,我們不使用枚舉不就行了嗎?反正只是簡單的單一狀態,我單獨定義一個常量類,實現分組:
/**
* 任務狀態常量
*/
public class TsakContants {
public static final int UN_KNOW = -1;
public static final int UN_START = 0;
public static final int PROGRESSING = 1;
public static final int COMPLETED = 2;
}
然後修改 doSth() 方法:
public class TaskHelper {
public static void doSth(int status){
switch (status){
case TsakContants.UN_KNOW:
//do something
break;
case TsakContants.UN_START:
break;
case TsakContants.PROGRESSING:
break;
case TsakContants.COMPLETED:
break;
}
}
}
調用起來也沒有區別:
TaskHelper.doSth(TsakContants.COMPLETED);
其次代碼可讀性很差,IDE 只是提示傳入 int 類型的參數。此時如果不看方法體,調用者根本不知道該傳什麼值。那麼問題來了?在一組常量的情況下,我們使用枚舉太重,使用常量不安全、可讀性差,我們該怎麼辦?當然是使用註解了!哈哈,兜了一圈終於輪到註解,是時候爲其正名啦。
@IntDef(定義int型) @StringDef(定義String類型) 是Android提供的註解
@IntDef({
TaskStatus.UN_KNOW,
TaskStatus.UN_START,
TaskStatus.PROGRESSING,
TaskStatus.COMPLETED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TaskStatus {
int UN_KNOW = -1;
int UN_START = 0;
int PROGRESSING = 1;
int COMPLETED = 2;
}
修改 doSth() 方法:
public class TaskHelper {
public static void doSth(@TaskStatus int status){
switch (status){
case TaskStatus.UN_KNOW:
//do something
break;
case TaskStatus.UN_START:
break;
case TaskStatus.PROGRESSING:
break;
case TaskStatus.COMPLETED:
break;
}
}
}
可以看出,doSth() 方法的參數是 int 類型的,但是使用 @TaskStatus 進行了註解,這樣外界就無法傳遞 TaskStatus 之外的成員作爲參數了。
調用
TaskHelper.doSth(TaskStatus.UN_START);
在調用時,IDE 會提示 @TaskStatus int status,提醒我們傳入 TaskStatus 類型的值。同時,調用者如果再隨便傳入一個 int 值,雖然可以運行,但代碼會爆紅,lint 檢查將會給與警告。
總結
現在看完本篇文章之後再思考,你應該清楚什麼場景下該使用那種方案了吧。在類中定義常量可能更規範一點,但是書寫起來以及class文件大小也要考慮。在接口中定義常量更加方便,class文件也相對較小,但是避免寫成常量接口模式,要合理使用。所以兩者的取捨看個人喜好吧,沒有誰對誰錯。此外如果需要定義一組常量,如果我們使用枚舉,閱讀性很好,但是性能缺損。如果單純的使用常量,類型安全、可讀性差,這時候我們可以考慮Android中提供的枚舉註解,相當的優雅。此外如果定義多組常量,我們可以考慮枚舉定義,這樣閱讀性和擴展性也是非常好的,雖然有性能缺損,但是我們也要度之。在性能還行的情況下,考慮使用。
---------------------
作者:lilinjie_blog
來源:CSDN
原文:https://blog.csdn.net/lilinjie_blog/article/details/81396593
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!