7.1 Efficient Java中的性能要点
Effective Java是一本被广泛认可的著作,它指明了在写Java代码时兼顾可维护性与效率的方式。Android也是使用Java来开发的,书中的部分建议可能不适用,因为并非所有Java特性都有针对Android优化(比如说枚举,序列化等等),或者是因为移动设备的局限(例如Dalvik/ART)。不管怎样,书中的大部分规范是稍微修改下甚至不修改就可以直接用的,以便构建更鲁棒,简洁且更可维护的代码库。
7.1.1 强制不可实例化
只包含静态方法的工具类,我们不希望它被外部通过关键字new来创建,那么强制让它的构造方法私有。这对单例的工具类也是一样的道理。
class MovieUtils {
private MovieUtils() {}
static String titleAndYear(Movie movie) {
// ...
}
}
7.1.2 静态工厂方法
尽可能不要使用 new 关键字和构造方法创建对象,而应当使用静态工厂方法(和私有构造方法)。这些工厂方法具有名字,不需要每次返回一个新的对象实例,它们可以依据需求返回不同的子类型对象。
其中Android Studio自动生成的Fragment模板就是一个典型。
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment MainFragment.
*/
public static MainFragment newInstance(String param1, String param2) {
MainFragment fragment = new MainFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
7.1.3 创建者模式
当对象的构造方法参数不小于3个时,可以考虑创建者模式。这可能需要更多行的代码,但拓展性和可读性会很好。如果使用AS开发,创建一个Builder模式的实体类,可以采用InnerBuilder插件来减少编码工作。
class Movie {
static Builder newBuilder() {
return new Builder();
}
static class Builder {
String title;
Builder withTitle(String title) {
this.title = title;
return this;
}
Movie build() {
return new Movie(title);
}
}
private Movie(String title) {
// ...
}
}
// Use like this:
Movie matrix = Movie.newBuilder().withTitle("The Matrix").build();
7.1.4 静态成员类
如果定义了一个不依赖外部类的内部类,不要忘记将其定义为静态的。否则将会导致每一个内部类对象都会持有对外部类的引用。
class Outter {
//...
static class Inner {
//...
}
}
7.1.5 泛型 (几乎) 无处不在
Java 的泛型限定提供了类型检查,尽量避免使用无类型或 Object 类型。泛型机制,大多数情况下保障了编译时的类型检查,编译期若发生类型转换警告,必须检查和修正,否则运行时强转就可能是ClassCastException。
// 不要这样做
List movies = new ArrayList();
movies.add("Hello!");
//...
String movie = (String) movies.get(0);
// 这样做
List<String> movies = new ArrayList<>();
movies.add("Hello!");
//...
String movie = movies.get(0);
不要忘记你能在方法中对参数和返回值使用泛型。
// 不要这样做
List sort(List input) {
//...
}
// 这样做
<T> List<T> sort(List<T> input) {
//...
}
7.1.6 避免返回空对象
当方法的返回类型为list/collecion时,返回空值时要避免返回null。返回一个空的集合类型,这会使得接口简单易用(没有必要写文档来声明方法返回值为null),并且避免空指针异常。
示例,使用Collection的.emptyList()接口返回集合的空值,作为方法返回值。
public List<Movie> latestMovies() {
boolean empty = db.query().isEmpty();
if (!empty) {
return db.query();
}
return Collections.emptyList();
}
7.1.7 检查Bean类的toString方法使用StringBuilder拼接
我们习惯性会打印Bean的数据进行调试,但大多数Bean实体类在IDE中自动生成的toString方法,都使用了“+”来拼接字段,性能较差,这种大量字符串拼接场景,要求必须采用StringBuilder进行拼接。
7.1.8 尽量使用移位来代替'a/b',‘a*b’的操作
乘除运算是一个代价很高的操作,使用移位的操作将会更快和更有效。
int num = a / 4;
int num = a * 4;
相应的修改为:
int num = a >> 2;
int num = a << 2;
7.1.9 尽量减小synchronize的范围
实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。实现方式主要有synchronize方法和synchronize代码块。
synchronize方法被调用时,直接会把当前对象锁 了,在方法执行完之前其他线程无法调用当前对象的其他方法,所以synchronize的方法尽量小。
在考虑性能方面,最好使用同步块来减少锁定范围提高并发效率,同时考虑Java5以后的推荐的lock锁方式,其提供了更优的特性,包括:
l 可以主动控制锁的释放,避免IO阻塞等无谓的等待;
l 提供可重入的读操作,使得在复杂读写的情况下,并发读成为更简单的实现。
示例:
public class Test {
private Lock lock = new ReentrantLock(); //declare lock
final Test test = new Test();
public void test () {
new Thread(){
public void run () {
test.insert(Thread.currentThread());
}
}.start();
new Thread() {
public void run() {
test.insert(Thread.currentThread());
}
}.start();
}
public void insert(Thread thread) {
lock.lock();
try {
System.out.println(thread.getName()+"得到了锁");
//...
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
}
}
7.1.10 慎用异常
当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。
因此,尽管捕获异常在IDE中实现起来已经比较简易,但是考虑到创建异常的性能开销,避免随意抛出异常,仅在必要提示异常的场景中使用异常,同时考虑接下来的一条异常使用规范。
7.1.11 底层接口主动抛出可恢复异常
Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
Java编译器会对"被检查的异常"进行检查,而对"运行时异常"不会检查。也就是说,对于被检查的异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。而对于运行时异常,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。
这里主要是针对数据层接口的异常设计而言,如果有可恢复的条件异常,通过定义一个可恢复的条件异常及早进行抛出,由上层调用者进行捕获。
如此,使上层调用者可以及早关注到该处逻辑可能会有什么样的异常。
7.2 遵守Android Lint性能规范
Android Lint内置了很多lint规则,到现在为止是220项检查,总共可以分为以下几类:
l Correctness 正确性
l Security 安全性
l Performance 性能
l Usability 可用性
l Accessibility 可访问性
l Internationalization 国际化
以上各项通过IDE自带插件等方式,可以自动化检查,一般要求遵守其所有规范,如有特别例外项,可通过修改检查项进行排除。
具体的检查细节项解释,参考官方文档:Android Lint Checks