39、使用注解而不是通过命名规则分类
如果需要对定义class,property,或者method进行分类管理,推荐的做法是使用注解对其添加类别,而不是通过命名规则分类。这里以JUnit为例:
在JUnit 3中,如果要写测试的方法,该方法必须以test开头,否则不会跑该测试用例。这种方式存在以下问题:
- 以test开头容易出现拼写错误,例如“test“写成了“tset”;
- 无法确认该方法只是专门跑测试用例的方法,可能是某些原因需要方法以test开头;
- 没有办法灵活配置测试用例,例如抛出异常则认为测试通过,但是不用写try-catch代码;
为了解决这个问题,在JUnit 4引入了注解@Test,需要测试的方法使用注解标记即可,例如下面的代码,m2不会执行
// Program containing marker annotations
public class Sample {
@Test
public static void m1() { } // Test should pass
public static void m2() { } // no test.
@Test
public static void m3() {// Test should failthrow new RuntimeException("Boom");
}
如果期望测试方法抛出指定异常为通过,可以使用ExceptionTest:
// Program containing annotations with a parameter
public class Sample2 {@ExceptionTest(ArithmeticException.class)public static void m1() { // Test should passint i = 0;i = i / i;}@ExceptionTest(ArithmeticException.class)public static void m2() { // Should fail (wrong exception)int[] a = new int[0];int i = a[1];}@ExceptionTest(ArithmeticException.class)public static void m3() { } // Should fail (no exception)
}
40、坚持使用OVERRIDE注解
如果方法是从父类或者接口继承的方法,一定要使用OVERRIDE标记。使用OVERRIDE好处有以下几点:
- 方法分类,方便知道哪些方法是外部要求实现的,哪些是内部要求实现的;
- 方法检查,OVERRIDE标记的方法会自动检查方法是否满足要求,例如方法名,参数类型,返回值,避免拼写错误;
- 便于维护,无论是父类还是子类修改了OVERRIDE的方法都会有编译报错,提示开发者及时纠正方法变化带来的问题;
41、使用标记接口定义类型
标记接口内部无需要实现的方法,仅仅是通过添加该接口做类型的区分。例如Android SDK自带的Serializable接口,本身没有实现的方法,但是通过此接口标记该Class可以被序列化。除此之后我们也可以自定义标记接口,为Class打上标记。
标记接口的目的是为Class分类,快速识别Class具有哪些特征,实现了哪些功能。如果要为属性和方法添加标记,需要使用注解。
42、lambda和匿名类相比,优先使用lambda
lambda表达式是Java带来的新语法特性,它最大的优点是语法简洁:
// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {public int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}
});// Lambda expression as function object (replaces anonymous class)
Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));
lambda表达式虽然语法简洁,但是有一些不足:
- lambda语法没有明确参数的类型和返回值类型,造成代码的可读性变差;
- lambda表达式this引用的是调用方的对象,而不是匿名类的对象;
所以在以下两种情况下,还是要使用匿名类:
- 方法实现比较复杂,一般来说超过3行推荐使用匿名内部类;
- 需要使用匿名类的对象,例如this关键字,需要使用匿名内部类;
43、方法引用和lambda相比,推荐使用方法引用
在可以方法引用的情况下会比lambda表达更加简洁:
// lambda表达式
map.merge(key, 1, (count, incr) -> count + incr);
// 方法引用
map.merge(key, 1, Integer::sum);
lambda表达式中的第三个参数是把两个int相加,在这种情况下正好可以使用Integer的sum方法替换,代码更加简洁。除此之外,比较常见的情况还有执行Runnable接口:
// lambda表达式
service.execute(() -> action());
// 方法引用
service.execute(GoshThisClassNameIsHumongous::action)
以下是一张常用的方法引用和lambda表达式替换的情况:
总结:优先使用方法引用,其他情况使用lambda表达式。
44、多使用功能性interface
功能性interface指的是没有特定含义的接口,仅仅是完成某种功能的通用性interface,他的名称跟具体实现的需求无关,实现方法的名称也是无含义的。例如比较常用的Consumer:
List<String> list = new ArrayList<>();
list.forEach(new Consumer<String>() {@Overridepublic void accept(String str) {}
});
Consumer就是一个功能性接口,他的作用就是把功能的实现和对象分离。除此之外还有其他功能性接口:
Android提供了很多的功能性接口,如果这些不满足开发者的需求也可以自定义,自定义的interface建议使用@FunctionalInterface注解。
45、谨慎的使用Stream
在Java 8中提供了流式api:Stream。
Stream的特点:
- Stream管道是懒汉式的,直到调用了结束方法才会开始计算;
- Stream的语法是流式语法(类似Builder),使用方便;
Stream使用案例:
// Overuse of streams - don't do this!
public class Anagrams {public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);int minGroupSize = Integer.parseInt(args[1]);try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> word.chars().sorted().collect(StringBuilder::new,(sb, c) -> sb.append((char)StringBuilder::append).toString())).values().stream().filter(group -> group.size() >= minGroupSize).map(group -> group.size() + ": " + group).forEach(System.out::println);}}
}
从上面的案例可以看出过度使用Stream过导致代码的可读性变差,所以在使用中应当注意以下几点:
- 科学的命名lambda表达式中的参数名称,提高代码可读性;
- 使用有益的方法提高代码的可读性;
- 在认为有必要的时候使用Stream,而不是滥用;
在Stream中有事情是要注意的:
- 普通的代码块,可以使用本地变量,但是使用lambda变量必须是final的;
- 普通的代码块可以使用return,break,continue等关键字改变外部的方法逻辑,但是lambda表达不可以;
在以下情况推荐使用Stream:
- 简单的元素转换;
- 过滤元素;
- 对每一个元素只做简单的操作,例如修改,拼接,计算等等;
- 对元素添加到其他集合,例如分组;
- 对集合搜索出符合某条件的元素;