这里针对Java 8在API方面的增强及相关使用方式做一些简单的介绍
Collection removeIf方法 众所周知,对于List、Set等集合而言,如果期望删除某些元素,其实是一件非常麻烦的事情。例如下面的示例,删除列表中长度大于2的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void test1 () { List<String> list = new LinkedList <>(); list.add("乒乓球" ); list.add("足球" ); list.add("羽毛球" ); list.add("篮球" ); list.forEach( e- > { if ( e.length()>2 ) { list.remove(e); } }); System.out.println("list: " + list); }
结果显然易见,如下所示,删除失败。原因自然是不言自明。在迭代器遍历过程中,通过list.remove方法进行删除是不允许的。除非我们显式使用迭代器进行遍历、删除,显然这样非常繁琐
为此Java8在Collection中提供removeIf默认方法,其接收一个谓词参数。大大方便了我们对集合进行删除的操作,示例如下所示
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test2 () { List<String> list = new LinkedList <>(); list.add("乒乓球" ); list.add("足球" ); list.add("羽毛球" ); list.add("篮球" ); list.removeIf( e -> e.length()>2 ); System.out.println("list: " + list); }
测试结果如下,符合预期
Map 计算模式 在Map中可以根据Key存在与否的情况,按条件执行相关操作并将计算结果作为Value存储到Map中。具体有以下方法:
compute:使用指定的Key计算新值,并将计算结果作为Value存储到Map中
computeIfAbsent:如果指定Key在Map中没有对应的值(Key不存在 或 其值为null), 则计算该Key的新值并存储到Map中
computeIfPresent:如果指定Key在Map中有对应的值(Key存在 且 其值不为null),则计算该Key的新值并存储到Map中
compute方法 比如下面的代码,是一个统计单词次数的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void test1 () { Map<String, Integer> map = new HashMap <>(); String doc = "I am Aaron I like Aaron" ; String[] words = doc.split(" " ); for (String word : words) { Integer count = map.get(word); if ( count==null ) { count = 0 ; } count++; map.put(word, count); } System.out.println("map : " + map); }
如果采用compute方法则可以大大简化代码,如下所示。利用Key及其Value进行计算,并将计算结果作为该Key的新值存储到Map中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void test2 () { Map<String, Integer> map = new HashMap <>(); String doc = "I am Aaron I like Aaron" ; String[] words = doc.split(" " ); for (String word : words) { map.compute( word, (key, oldValue) ->{ if ( oldValue==null ) { oldValue = 0 ; } oldValue++; return oldValue; }); } System.out.println("<Test 2> map : " + map); }
两个方法测试结果如下,符合预期
computeIfAbsent方法 下面是一个在Map中保存各人所喜欢的书单的方法。每次在向value中添加书时,都需要判断该value是否为空,非常繁琐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void test1 () { Map<String, List<String>> map = new HashMap <>(); String name = "Amy" ; String bookName = "老人与海" ; List list = map.get(name); if ( list==null ) { list = new LinkedList <>(); map.put(name, list); } list.add(bookName); System.out.println("<Test 1> map : " + map); }
幸运的是,在有了computeIfAbsent方法后,我们实现起来就简洁很多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void test2 () { Map<String, List<String>> map = new HashMap <>(); String name = "Amy" ; String bookName = "老人与海" ; List<String> list = map.computeIfAbsent( name, key -> new LinkedList <>() ); list.add(bookName); System.out.println("<Test 2> map : " + map); }
可以看到,computeIfAbsent方法非常适用于value为集合类型、且需要向该集合中添加元素的场景。测试结果如下,符合预期
与此同时,computeIfAbsent方法也适用于缓存信息的场景。在下面的示例中,对于已有签名数据作为Value的Key,显然没有必要重复计算、浪费资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Test public void test3 () { Map<String, Integer> map = new HashMap <>(); System.out.println("\n-------------------- Test 1: Start --------------------" ); map.computeIfAbsent("China" , this ::calcSign); System.out.println("-------------------- Test 1: End ----------------------" ); System.out.println("\n-------------------- Test 2: Start --------------------" ); map.computeIfAbsent("USA" , this ::calcSign); System.out.println("-------------------- Test 2: End ----------------------" ); System.out.println("\n-------------------- Test 3: Start --------------------" ); map.computeIfAbsent("China" , this ::calcSign); System.out.println("-------------------- Test 3: End ----------------------" ); System.out.println("\nmap: " + map); } private Integer calcSign (String str) { System.out.println( "Start Calc Sign, str: " + str); Integer result = str.hashCode(); return result; }
测试结果如下,符合预期
computeIfPresent方法 下面是一个在Map中保存各人所喜欢的书单的方法。每次从value中移除书时,都需要判断该value是否为空,非常繁琐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void test1 () { Map<String, List<String>> map = new HashMap (); map.put("Amy" , new ArrayList (Arrays.asList("资治通鉴" , "金瓶梅" , "山海经" )) ); String name = "Amy" ; String bookName = "金瓶梅" ; List<String> list = map.get(name); if ( list!=null ) { list.remove( bookName ); } System.out.println("<Test 1> map : " + map); }
结果符合预期,如下所示
幸运的是,在有了computeIfPresent方法后,我们实现起来就简洁很多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Test public void test2 () { Map<String, List<String>> map = new HashMap <>(); map.put("Amy" , new ArrayList (Arrays.asList("资治通鉴" , "金瓶梅" , "山海经" )) ); map.put("Bob" , new ArrayList (Arrays.asList("三年高考五年模拟" )) ); map.put("Tony" , new ArrayList (Arrays.asList("21天入门理发" )) ); String name = "Amy" ; String bookName1 = "金瓶梅" ; map.computeIfPresent(name, (key, value) -> { value.remove( bookName1 ); return value; } ); name = "Bob" ; String bookName2 = "三年高考五年模拟" ; map.computeIfPresent(name, (key, value) -> { value.remove( bookName2 ); return value; } ); name = "Tony" ; map.computeIfPresent(name, (key, value) -> { List newValue = null ; return newValue; } ); System.out.println("<Test 2> map : " + map); }
可以看到,computeIfPresent方法非常适用于value为集合类型、且需要从集合中移除元素的场景。测试结果如下,符合预期。值得一提的是在computeIfPresent方法中,如果该key计算的新值为null, 则该映射会被移除
merge方法 Map接口提供的默认方法merge,签名如下。其第一、二个参数就是我们期望存储到Map的key、newValue。但如果该key在map已经存在且其值(这里记为oldValue)不为空,则就需要通过 第三个参数(BiFunction类型),其定义了合并oldValue、newValue这两个值的计算规则
1 2 merge(K key, V value, BiFunction<? super V, ? super V, ? extends V > remappingFunction) {
通过下面的例子,可以更好的帮助大家理解
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test1 () { Map<String, String> map = new HashMap <>(); map.put("Aaron" , "篮球" ); map.merge("Bob" , "足球" , (oldValue, newValue) -> oldValue+"&" +newValue ); System.out.println("map 1: " + map); map.merge("Aaron" , "乒乓球" , (oldValue, newValue) -> oldValue+"&" +newValue ); System.out.println("map 2: " + map); }
测试结果如下,符合预期
可以看到,由于merge方法对于欲插入的key是否在map中存在不为null的value,实际上是提供了两种不同的计算路径。故对于上文通过compute方法实现次数统计的例子而言,我们如果使用merge方法实现会更加简单,示例代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void test2 () { Map<String, Integer> map = new HashMap <>(); String doc = "can Aaron can Aaron like I can like Aaron can" ; String[] words = doc.split(" " ); for (String word : words) { map.merge(word, 1 , (oldValue, newValue)->oldValue+1 ); } System.out.println("<Test 2> map : " + map); }
测试结果如下,符合预期
Optional orElse与orElseGet 二者都是用于在option实例中value值不存在时,提供一个默认值。但orElse方法是每次均会计算默认值,无论option实例中value值是否存在;而orElseGet方法则是延迟计算,即只有在option实例中value值不存在时,才会去计算默认值。故如果对于默认值的计算、获取过程比较昂贵,推荐使用orElseGet方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Test public void test1 () { Optional<String> optional1 = Optional.ofNullable( "Aaron" ); Optional<String> optional2 = Optional.ofNullable( null ); System.out.println("\n-------------------- Test 1: Start --------------------" ); String s1 = optional1.orElse( getDefault() ); System.out.println("s1: " + s1); System.out.println("-------------------- Test 1: End ----------------------" ); System.out.println("\n-------------------- Test 2: Start --------------------" ); String s2 = optional2.orElse( getDefault() ); System.out.println("s2: " + s2); System.out.println("-------------------- Test 2: End ----------------------" ); System.out.println("\n-------------------- Test 3: Start --------------------" ); s1 = optional1.orElseGet( () -> getDefault() ); System.out.println("s1: " + s1); System.out.println("-------------------- Test 3: End ----------------------" ); System.out.println("\n-------------------- Test 4: Start --------------------" ); s2 = optional2.orElseGet( () -> getDefault() ); System.out.println("s2: " + s2); System.out.println("-------------------- Test 4: End ----------------------" ); } public String getDefault () { System.out.println("call getDefault method" ); return "Tony" ; }
测试结果如下,符合预期
map与flatMap Optional的初衷就是为了解决NPE而设计的。比如在传统的代码中,为了防止出现NPE,开发者需要层层判空。示例代码如下所示,这样显然非常繁琐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class OptionalTest { @Test public void test2 () { Person person = getPerson(); String addr = null ; if (person!=null ) { Person.Company company = person.getCompany(); if ( company!=null ) { addr = company.getAddr(); } } String tel = null ; if (person!=null ) { Person.Company company = person.getCompany(); if ( company!=null ) { tel = company.getTel(); } } System.out.println("addr: " + addr); System.out.println("tel: " + tel); } public Person getPerson () { Person.Family family = Person.Family.builder() .mother("Amy" ) .build(); Person person = Person.builder() .name("Tony" ) .company( Person.Company.builder() .type("外贸" ) .addr("广东省" ) .build()) .family( Optional.ofNullable(family) ) .build(); return person; } @Data @AllArgsConstructor @NoArgsConstructor @Builder public static class Person { private String name; private Integer age; private Company company; private Optional<Family> family; @Data @AllArgsConstructor @NoArgsConstructor @Builder public static class Company { private String type; private String addr; private String tel; } @Data @AllArgsConstructor @NoArgsConstructor @Builder public static class Family { private String father; private String mother; } } }
测试结果如下,符合预期
而自从有了Optional后,情况就大不一样了。我们可以使用map、flatMap实现层层转化。在整个链式调用过程中一旦某个Optional的value为null,则会返回一个空Optional,继续执行。以免出现NPE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void test3 () { Person person = getPerson(); Optional<Person> optionalPerson = Optional.ofNullable(person); String addr = optionalPerson.map( Person::getCompany ) .map( Person.Company::getAddr ) .orElse(null ); String tel = optionalPerson.map( Person::getCompany ) .map( Person.Company::getTel ) .orElse(null ); String mother = optionalPerson.flatMap( Person::getFamily ) .map( Person.Family::getMother ) .orElse(null ); String father = optionalPerson.flatMap( Person::getFamily ) .map( Person.Family::getFather ) .orElse(null ); System.out.println("addr: " + addr); System.out.println("tel: " + tel); System.out.println("mother: " + mother); System.out.println("father: " + father); }
测试结果如下,符合预期
Note
对于Map接口提供的putIfAbsent默认方法而言,其与computeIfAbsent方法在功能上虽然类似。但有一些不同的地方。首先,computeIfAbsent方法对于value的计算是延迟计算,即只有在key不存在 或 该key在Map中的value为null 时,其才会计算value。而putIfAbsent无论最终是否需要设置该键值对,都会去计算value。所以value的计算如果是一个昂贵的过程,推荐使用延迟计算特性的computeIfAbsent方法;其次,二者返回值不同。computeIfAbsent方法总是会返回该key在map中相应的value值,而putIfAbsent方法,如果 key不存在 或 该key在Map中的value为null 时,会返回null。否则返回该key在map中相应的value值
参考文献
Java实战·第2版 拉乌尔-加布里埃尔·乌尔玛、马里奥·富斯科、艾伦·米克罗夫特著