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
}

但至少把问题缩小化了,也可以加上注释来说明。就不必导致使用者来猜方法的用法和含义了。

时间: 2024-11-01 20:55:02

Android开发笔记之:用Enum(枚举类型)取代整数集的应用详解_Android的相关文章

Android开发笔记之:用Enum(枚举类型)取代整数集的应用详解

在Android的API中可以发现有很多用整数集来作为参数的地方,先来看一下实例. LinearLayout是大家所熟知的一个UI基本元素,它里面有一个方向的属性,可以通过以下方法来设置: 复制代码 代码如下: LinearLayout.setOrientation(int); 使用的时候,通常都是这样: 复制代码 代码如下: LinearLayout.setOrientation(LinearLayout.HORIZONTAL); LinearLayout.setOrientation(Lin

Android开发之无痕过渡下拉刷新控件的实现思路详解_Android

相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下拉刷新控件普遍缺陷演示 以直播吧APP为例: 第1种情况: 滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动. 原因: 下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件

Android开发之无痕过渡下拉刷新控件的实现思路详解

相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下拉刷新控件普遍缺陷演示 以直播吧APP为例: 第1种情况: 滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动. 原因: 下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件

Android 学习笔记 Contacts ContentResolver query、add、update、delete 参数详解

1.获取联系人姓名 一个简单的例子,这个函数获取设备上所有的联系人ID和联系人NAME. public void fetchAllContacts() { ContentResolver contentResolver = this.getContentResolver(); Cursor cursor = contentResolver.query(android.provider.ContactsContract.Contacts.CONTENT_URI, null, null, null,

Android笔记之:App应用之发布各广告平台版本的详解_Android

Android的广告平台是很多的,各市场对各平台的接受程度是不一样的,Android的开发者如果想集成广告基本要考虑下面两个问题:(1)集成什么广告,会赚钱?(2)集成什么广告,不会被市场拒绝?最终的结果往往是折中的.第一个问题是广告平台的判断问题,我没有发言权去评论,本文主要是针对第二个问题展开.解决方案就是打包应用的不同广告平台版本,本文接下来逐一展开相关话题. 1. 基础本文其实是针对<Android笔记之:App模块化及工程扩展的应用>和<Android笔记之:App自动化之使用

深入Android中BroadcastReceiver的两种注册方式(静态和动态)详解_Android

今天我们一起来探讨下安卓中BroadcastReceiver组件以及详细分析下它的两种注册方式. BroadcastReceiver也就是"广播接收者"的意思,顾名思义,它就是用来接收来自系统和应用中的广播.在Android系统中,广播体现在方方面面,例如当开机完成后系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能:当网络状态改变时系统会产生一条广播,接收到这条广播就能及时地做出提示和保存数据等操作:当电池电量改变时,系统会产生一条广播,接收到这条广播就能在电量低时告知用户

Android APP瘦身(清除工程中没用到的资源)详解_Android

清除Android工程中没用到的资源 项目需求一改再改,UI一调再调,结果就是项目中一堆已经用不到但却没有清理的垃圾资源,不说工程大小问题,对新进入项目的人或看其他模块的代码的人来说,这些没清理的资源可能也可能会带来困扰,所以最好还是清理掉这些垃圾,对于一个稍微大一点的工程来说,手工清理明显是不现实的,这就需要一个方法做这些事情. 清理资源文件 要清理没用的资源,首要的工作当然是找到他们,我们知道Anroid SDK中有一个工具叫lint,可以帮助我们查看工程中存在的问题,其中有一项功能就是查找

深入理解Android组件间通信机制对面向对象特性的影响详解_Android

组件的特点对于Android的四大组件Activity, Service, ContentProvider和Service,不能有Setter和Getter,也不能给组件添加接口.原因是组件都是给系统框架调用的,开发者只能实现其规定的回调接口,组件的创建与销毁都是由系统框架控制的,开发者不能强行干预,更没有办法获取组件的对象.比如Activity,Service,BroadcastReceiver,你没有办法去创建一个Activity,Service或BroadcastReceiver,然后像使

Android中dip、dp、sp、pt和px的区别详解_Android

1.概述过去,程序员通常以像素为单位设计计算机用户界面.例如:图片大小为80×32像素.这样处理的问题在于,如果在一个每英寸点数(dpi)更高的新显示器上运行该程序,则用户界面会显得很小.在有些情况下,用户界面可能会小到难以看清内容.由此我们采用与分辨率无关的度量单位来开发程序就能够解决这个问题.Android应用开发支持不同的度量单位. 2.度量单位含义dip: device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持