《Android编程实战》学习笔记二——在Android上编写高效的Java(1)

一、安卓上的类型安全枚举

public class Machine {
    public static final int STOPPED = 10;
    public static final int INITIALIZING = 20;
    public static final int STARTING = 30;
    public static final int RUNNING = 40;
    public static final int STOPPING = 50;
    public static final int CRASHED = 60;
    private int mState;
    public Machine() {
        mState = STOPPED;
    }
    public int getState() {
        return mState;
    }
    public void setState(int state) {
        mState = state;
    }
}

问题:虽然这些常量是期望的,但是没有方法保证setState()方法接收其他的值。如果
要在设置方法中添加检查,那么一旦得到的是非预期值,开发者就需要处理错误。开发者所需要的是在编译时检查非法赋值。类型安全的枚举解决了这个问题,如下所示:

public class Machine {
    public enum State {
        STOPPED, INITIALIZING, STARTING, RUNNING, STOPPING, CRASHED
    }
    private State mState;
    public Machine() {
        mState = State.STOPPED;
    }
    public State getState() {
        return mState;
    }
    public void setState(State state) {
        mState = state;
    }
}

注意在声明不同类型安全值的地方新加的内部枚举类。这在编译时就会解决非法赋值的问题,所以代码更不容易出错。
Android早期的版本中不建议使用枚举类型,因为和使用整型常量相比,这种设计带来的内存和性能损失更大。如今有了更强的JIT编译器以及一个不断改进的Dalvik虚拟机,开发者不必再担心这个问题,放心大胆地使用类型安全枚举即可。

二、增强for循环

void loopOne(String[] names){
    int size = names.length;
    for(int i=0;i<size;i++){
         printName(names[i]); 
    }
}

void loopTwo(String[] names){
    for(String name:names){
        printName(name);
    }
}

void loopThree(Collection<String> names){
    for(String name:names){
        printName(name);
    }  
}

void loopFour(Collection<String> names){
    Iterator<String> iterator = names.iterator();
    while(iterator.hasNext()){
        printName(iterator.next());
    }
}

// 不要在ArrayList上使用增强版的for循环
void loopFive(ArrayList<String> names){
    int size = names.size();
    for(int i=0;i<size;i++){
        printName(names[i]);
    }
}

如果只是读取元素的话,可以放心地对数组使用增强版 for 循环。对 Collection 对象来说,增强版 for 循环和使用迭代器遍历元素有着相同的性能。 ArrayList 对象应避免使用增强版 for 循环。
如果不仅需要遍历元素,而且需要元素的位置,就一定要使用数组或者 ArrayList ,因为所有其他 Collection 类在这些情况下会更慢。
一般情况下,如果在读取元素几乎不变的数据集时对性能要求很高,建议使用常规数组。

三、队列、同步和锁

1. 更智能的队列

LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>();

上面的一行代码能提供阻塞队列,甚至能提供额外的线程安全操作。java.util.concurrent包含许多可选的队列以及并发映射类,所以,一般情况下,建议使用它们。

2.更智能的锁

Java提供的 synchronized 关键字允许开发者创建线程安全的方法和代码块。synchronized关键字易于使用,也很容易滥用,对性能造成负面影响。当需要区分读数据和写数据时,synchronized 关键字并不是最有效的。幸好,java.util.concurrent.locks包中的工具类对这种情况提供了很好的支持。

public class ReadWriteLockDemo(){
    private final ReentrantReadWriteLock mLock;
    private String mName;
    private int mAge;
    private String mAddress;

    public ReadWriteLockDemo(){
        mLock = new ReentrantReadWriteLock();
    }

    public void setPersonData(String name, int age, String address){
        ReentrantReadWriteLock.WriteLock writeLock = mLock.writeLock();
        try{
            writeLock.lock();
            mName = name;
            mAge = age;
            mAddress = address;
        }finally{
            writeLock.unlock();
        }
    }

    public String getName() {
        ReentrantReadWriteLock.ReadLock readLock = mLock.readLock();
        try {
            readLock.lock();
            return mName;
        } finally {
            readLock.unlock();
        }
    }
// 重复代码不再赘述
}

上面的代码展示了在什么地方使用 ReentrantReadWriteLock ,它允许多个并发线程对数据进行只读访问,并确保同一时间只有一个线程写入相同的数据。
在代码中使用 synchronized 关键字仍然是处理锁问题的有效方法,但无论何种情况下,都要考虑 ReentrantReadWriteLock 是否是更有效的解决方案。

四、管理和分配内存

1. 应尽可能避免在循环中分配对象

2. 有时候无法避免在循环中创建对象,所以还需要采用某种方法处理这种情况。本书的解决方案是使用一个静态工厂方法按需分配对象,Joshua Bloch在《Effective Java中文版》一书的第一条中详细地描述了该方法。

public final class Pair{
    public int firstValue;
    public int secondValue;

    //引用对象池中的另一个对象
    private Pair next;

    //同步锁
    private static final Object sPoolSync = new Object();
    //对象池中第一个可用对象
    private static Pair sPool;

    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;

    /**
    * 只能用obtain()方法获取对象
    */
    private Pair() { }

    /**
    * 返回回收的对象或者当对象池为空时创建一个新对象
    */
    public static Pair obtain(){
        synchronized(sPoolSync){
            if(sPool != null){
                Pair m = sPool;
                sPool.next = m.next;
                m.next = null;
                sPoolSize --;
                return m;
            }
        }
        return new Pair();
    }

    /**
    * 回收该对象。调用该方法后需要释放所有对该实例的引用
    */
    public void recycle(){
        synchronized(sPoolSync){
            if(sPoolSize < MAX_POOL_SIZE){
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

注意,本例增加了多个字段,有静态的也有非静态的。可使用这些字段实现传统的 Pair 对象链表。通过使用私有构造函数来防止在类外面创建对象,只能通过 obtain 方法创建该类的对象。 obtain 方法首先会检查对象池中是否包含任何存在的对象,并删除列表中的第一个元素然后返回它。如果对象池为空, obtain 方法会创建一个新的对象。要把对象重新放回池中,需要在使用完该对象时,对它调用 recycle 方法。这时,不能再有对该对象的引用。
另外,由于 obtain 和 recycle 是线程安全的,可以在多个并发线程中安全地使用这两个方法。唯一的缺点是,必须记住要手动调用 recycle 方法,不过这是一个很小的代价。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容