Java8 为容器新增一些有用的方法,这些方法有些是为完善原有功能,有些是为引入函数式编程(Lambda 表达式),学习和使用这些方法有助于我们写出更加简洁有效的代码.本文分别以 ArrayList 和 HashMap 为例,讲解 Java8 集合框架(Java Collections Framework)中新加入方法的使用.
我们先从最熟悉的开始说起。
为引入 Lambda 表达式,Java8 新增了
包,里面包含常用的函数接口,这是 Lambda 表达式的基础,Java 集合框架也新增部分接口,以便与 Lambda 表达式对接。
- java.util.funcion
首先回顾一下 Java 集合框架的接口继承结构:
上图中绿色标注的接口类,表示在 Java8 中加入了新的接口方法,当然由于继承关系,他们相应的子类也都会继承这些新方法。下表详细列举了这些方法。
接口名 | Java8 新加入的方法 |
---|---|
Collection | removeIf() spliterator() stream() parallelStream() forEach() |
List | replaceAll() sort() |
Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |
这些新加入的方法大部分要用到
包下的接口,这意味着这些方法大部分都跟 Lambda 表达式相关。我们将逐一学习这些方法。
- java.util.function
如上所示,接口
和
- Collection
新加入了一些方法,我们以是
- List
的子类
- List
为例来说明。了解,将有助于理解下文。
- ArrayList
该方法的签名为
,作用是对容器中的每个元素执行
- void forEach(Consumer<? super E> action)
指定的动作,其中
- action
是个函数接口,里面只有一个待实现方法
- Consumer
(后面我们会看到,这个方法叫什么根本不重要,你甚至不需要记忆它的名字)。
- void accept(T t)
需求:
Java7 及以前我们可以用增强的 for 循环实现:
- // 使用曾强for循环迭代
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- for(String str : list){
- if(str.length()>3)
- System.out.println(str);
- }
现在使用
方法结合匿名内部类,可以这样实现:
- forEach()
- // 使用forEach()结合匿名内部类迭代
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- list.forEach(new Consumer(){
- @Override
- public void accept(String str){
- if(str.length()>3)
- System.out.println(str);
- }
- });
上述代码调用
方法,并使用匿名内部类实现
- forEach()
接口。到目前为止我们没看到这种设计有什么好处,但是不要忘记 Lambda 表达式,使用 Lambda 表达式实现如下:
- Comsumer
- // 使用forEach()结合Lambda表达式迭代
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- list.forEach( str -> {
- if(str.length()>3)
- System.out.println(str);
- });
上述代码给
方法传入一个 Lambda 表达式,我们不需要知道
- forEach()
方法,也不需要知道
- accept()
接口,类型推导帮我们做了一切。
- Consumer
该方法签名为
,作用是删除容器中所有满足
- boolean removeIf(Predicate<? super E> filter)
指定条件的元素,其中
- filter
是一个函数接口,里面只有一个待实现方法
- Predicate
,同样的这个方法的名字根本不重要,因为用的时候不需要书写这个名字。
- boolean test(T t)
需求:
我们知道如果需要在迭代过程冲对容器进行删除操作必须使用迭代器,否则会抛出
,所以上述任务传统的写法是:
- ConcurrentModificationException
- // 使用迭代器删除列表元素
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- Iterator it = list.iterator();
- while(it.hasNext()){
- if(it.next().length()>3) // 删除长度大于3的元素
- it.remove();
- }
现在使用
方法结合匿名内部类,我们可是这样实现:
- removeIf()
- // 使用removeIf()结合匿名名内部类实现
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- list.removeIf(new Predicate(){ // 删除长度大于3的元素
- @Override
- public boolean test(String str){
- return str.length()>3;
- }
- });
上述代码使用
方法,并使用匿名内部类实现
- removeIf()
接口。相信你已经想到用 Lambda 表达式该怎么写了:
- Precicate
- // 使用removeIf()结合Lambda表达式实现
- ArrayList list = new ArrayList < >(Arrays.asList("I", "love", "you", "too"));
- list.removeIf(str - >str.length() > 3); // 删除长度大于3的元素
使用 Lambda 表达式不需要记忆
接口名,也不需要记忆
- Predicate
方法名,只需要知道此处需要一个返回布尔类型的 Lambda 表达式就行了。
- test()
该方法签名为
,作用是对每个元素执行
- void replaceAll(UnaryOperator<E> operator)
指定的操作,并用操作结果来替换原来的元素。其中
- operator
是一个函数接口,里面只有一个待实现函数
- UnaryOperator
。
- T apply(T t)
需求:
Java7 及之前似乎没有优雅的办法:
- // 使用下标实现元素替换
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- for(int i=0; isize(); i++){
- String str = list.get(i);
- if(str.length()>3)
- list.set(i, str.toUpperCase());
- }
使用
方法结合匿名内部类可以实现如下:
- replaceAll()
- // 使用匿名内部类实现
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- list.replaceAll(new UnaryOperator(){
- @Override
- public String apply(String str){
- if(str.length()>3)
- return str.toUpperCase();
- return str;
- }
- });
上述代码调用
方法,并使用匿名内部类实现
- replaceAll()
接口。我们知道可以用更为简洁的 Lambda 表达式实现:
- UnaryOperator
- // 使用Lambda表达式实现
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- list.replaceAll(str -> {
- if(str.length()>3)
- return str.toUpperCase();
- return str;
- });
该方法定义在
接口中,方法签名为
- List
,该方法根据
- void sort(Comparator<? super E> c)
指定的比较规则对容器元素进行排序。
- c
接口我们并不陌生,其中有一个方法
- Comparator
需要实现,显然该接口是个函数接口。
- int compare(T o1, T o2)
需求:
由于 Java7 以及之前
方法在
- sort()
工具类中,所以代码要这样写:
- Collections
- // Collections.sort()方法
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- Collections.sort(list, new Comparator(){
- @Override
- public int compare(String str1, String str2){
- return str1.length()-str2.length();
- }
- });
现在可以直接使用
,结合 Lambda 表达式,可以这样写:
- List.sort()方法
- // List.sort()方法结合Lambda表达式
- ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
- list.sort((str1, str2) -> str1.length()-str2.length());
方法签名为
,该方法返回容器的可拆分迭代器。从名字来看该方法跟
- Spliterator<E> spliterator()
方法有点像,我们知道
- iterator()
是用来迭代容器的,
- Iterator
也有类似作用,但二者有如下不同:
- Spliterator
既可以像
- Spliterator
那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。
- Iterator
是可拆分的,一个
- Spliterator
可以通过调用
- Spliterator
方法来尝试分成两个。一个是
- Spliterator<T> trySplit()
,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。
- this
可通过(多次)调用
方法来分解负载,以便多线程处理。
- Spliterator.trySplit()
和
- stream()
分别返回该容器的
- parallelStream()
视图表示,不同之处在于
- Stream
返回并行的
- parallelStream()
。
- Stream
是 Java 函数式编程的核心类,我们会在后面章节中学习。
- Stream
相比
,
- Collection
中加入了更多的方法,我们以
- Map
为例来逐一探秘。了解,将有助于理解下文。
- HashMap
该方法签名为
,作用是对
- void forEach(BiConsumer<? super K,? super V> action)
中的每个映射执行
- Map
指定的操作,其中
- action
是一个函数接口,里面有一个待实现方法
- BiConsumer
。
- void accept(T t, U u)
接口名字和
- BinConsumer
方法名字都不重要,请不要记忆他们。
- accept()
需求:
Java7 以及之前经典的代码如下:
- // Java7以及之前迭代Map
- HashMap map = new HashMap<>();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- for(Map.Entry entry : map.entrySet()){
- System.out.println(entry.getKey() + "=" + entry.getValue());
- }
使用
方法,结合匿名内部类,代码如下:
- Map.forEach()
- // 使用forEach()结合匿名内部类迭代Map
- HashMap map = new HashMap<>();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- map.forEach(new BiConsumer(){
- @Override
- public void accept(Integer k, String v){
- System.out.println(k + "=" + v);
- }
- });
上述代码调用
方法,并使用匿名内部类实现
- forEach()
接口。当然,实际场景中没人使用匿名内部类写法,因为有 Lambda 表达式:
- BiConsumer
- // 使用forEach()结合Lambda表达式迭代Map
- HashMap map = new HashMap<>();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- map.forEach((k, v) -> System.out.println(k + "=" + v));
- }
该方法跟 Lambda 表达式没关系,但是很有用。方法签名为
,作用是按照给定的
- V getOrDefault(Object key, V defaultValue)
查询
- key
中对应的
- Map
,如果没有找到则返回
- value
。使用该方法程序员可以省去查询指定键值是否存在的麻烦.
- defaultValue
需求;
- // 查询Map中指定的值,不存在时使用默认值
- HashMap map = new HashMap < >();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- // Java7以及之前做法
- if (map.containsKey(4)) { // 1
- System.out.println(map.get(4));
- } else {
- System.out.println("NoValue");
- }
- // Java8使用Map.getOrDefault()
- System.out.println(map.getOrDefault(4, "NoValue")); // 2
该方法跟 Lambda 表达式没关系,但是很有用。方法签名为
,作用是只有在不存在
- V putIfAbsent(K key, V value)
值的映射或映射值为
- key
时,才将
- null
指定的值放入到
- value
中,否则不对
- Map
做更改.该方法将条件判断和赋值合二为一,使用起来更加方便.
- Map
我们都知道
中有一个
- Map
方法,来根据指定
- remove(Object key)
值删除
- key
中的映射关系;Java8 新增了
- Map
方法,只有在当前
- remove(Object key, Object value)
中
- Map
正好映射到
- key
时才删除该映射,否则什么也不做.
- value
在 Java7 及以前,要想替换
中的映射关系可通过
- Map
方法实现,该方法总是会用新值替换原来的值.为了更精确的控制替换行为,Java8 在
- put(K key, V value)
中加入了两个
- Map
方法,分别如下:
- replace()
,只有在当前
- replace(K key, V value)
中
- Map
的映射存在时才用
- key
去替换原来的值,否则什么也不做.
- value
,只有在当前
- replace(K key, V oldValue, V newValue)
中
- Map
的映射存在且等于
- key
时才用
- oldValue
去替换原来的值,否则什么也不做.
- newValue
该方法签名为
,作用是对
- replaceAll(BiFunction<? super K,? super V,? extends V> function)
中的每个映射执行
- Map
指定的操作,并用
- function
的执行结果替换原来的
- function
,其中
- value
是一个函数接口,里面有一个待实现方法
- BiFunction
.不要被如此多的函数接口吓到,因为使用的时候根本不需要知道他们的名字.
- R apply(T t, U u)
需求:
Java7 以及之前经典的代码如下:
- // Java7以及之前替换所有Map中所有映射关系
- HashMap map = new HashMap<>();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- for(Map.Entry entry : map.entrySet()){
- entry.setValue(entry.getValue().toUpperCase());
- }
使用
方法结合匿名内部类,实现如下:
- replaceAll()
- // 使用replaceAll()结合匿名内部类实现
- HashMap map = new HashMap<>();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- map.replaceAll(new BiFunction(){
- @Override
- public String apply(Integer k, String v){
- return v.toUpperCase();
- }
- });
上述代码调用
方法,并使用匿名内部类实现
- replaceAll()
接口。更进一步的,使用 Lambda 表达式实现如下:
- BiFunction
- // 使用replaceAll()结合Lambda表达式实现
- HashMap map = new HashMap<>();
- map.put(1, "one");
- map.put(2, "two");
- map.put(3, "three");
- map.replaceAll((k, v) -> v.toUpperCase());
简洁到让人难以置信.
该方法签名为
,作用是:
- merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)
中
- Map
对应的映射不存在或者为
- key
,则将
- null
(不能是
- value
)关联到
- null
上;
- key
,如果执行结果非
- remappingFunction
则用该结果跟
- null
关联,否则在
- key
中删除
- Map
的映射.
- key
参数中
函数接口前面已经介绍过,里面有一个待实现方法
- BiFunction
.
- R apply(T t, U u)
方法虽然语义有些复杂,但该方法的用方式很明确,一个比较常见的场景是将新的错误信息拼接到原来的信息上,比如:
- merge()
- map.merge(key, newMsg, (v1, v2) -> v1+v2);
该方法签名为
,作用是把
- compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
的计算结果关联到
- remappingFunction
上,如果计算结果为
- key
,则在
- null
中删除
- Map
的映射.
- key
要实现上述
方法中错误信息拼接的例子,使用
- merge()
代码如下:
- compute()
- map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));
该方法签名为
,作用是:只有在当前
- V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
中不存在
- Map
值的映射或映射值为
- key
时,才调用
- null
,并在
- mappingFunction
执行结果非
- mappingFunction
时,将结果跟
- null
关联.
- key
是一个函数接口,里面有一个待实现方法
- Function
.
- R apply(T t)
常用来对
- computeIfAbsent()
的某个
- Map
值建立初始化映射.比如我们要实现一个多值映射,
- key
的定义可能是
- Map
,要向
- Map<K,Set<V>>
中放入新值,可通过如下代码实现:
- Map
- Map> map = new HashMap<>();
- // Java7及以前的实现方式
- if(map.containsKey(1)){
- map.get(1).add("one");
- }else{
- Set valueSet = new HashSet();
- valueSet.add("one");
- map.put(1, valueSet);
- }
- // Java8的实现方式
- map.computeIfAbsent(1, v -> new HashSet()).add("yi");
使用
将条件判断和添加操作合二为一,使代码更加简洁.
- computeIfAbsent()
该方法签名为
,作用跟
- V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
相反,即,只有在当前
- computeIfAbsent()
中存在
- Map
值的映射且非
- key
时,才调用
- null
,如果
- remappingFunction
执行结果为
- remappingFunction
,则删除
- null
的映射,否则使用该结果替换
- key
原来的映射.
- key
这个函数的功能跟如下代码是等效的:
- // Java7及以前跟computeIfPresent()等效的代码
- if (map.get(key) != null) {
- V oldValue = map.get(key);
- V newValue = remappingFunction.apply(key, oldValue);
- if (newValue != null)
- map.put(key, newValue);
- else
- map.remove(key);
- return newValue;
- }
- return null;
来源: http://www.cnblogs.com/CarpenterLee/p/6507161.html