作用:
lambda表达式主要用于简化某些匿名内部类的写法,但它带来的变化不仅仅是有表面上的代码量的减少这一个变化。
其实lambda和匿名内部类的在编译时也是有不同的。
对于匿名内部类,在编译时,其实是把它当作一个类去编译的,所以当有一个匿名内部类的java文件编译时会生成两个class文件,一个是本类的class文件,另一个是匿名内部类的class文件;
对于lambda表达式,在编译时会把它当成本类得一个私有方法去编译,所以在lambda表达式内部的this引用和表达式外部相同。
使用的前提条件:
lambda表达式的实现通过invokedynamic指令实现,书写Lambda表达式不会产生新的类。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的在这里插入代码片
类型,而不需要显式指名。能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口)。这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。
lambda用于容器类:
为引入Lambda表达式,Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。
对与容器类,可以看下面的这个表来使用lambda表达式:
Collection:forEach() removeIf() spliterator() stream() parallelStream()
List: replaceAll() sort()
Map: getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()
Collection:
1.forEach()结合lamda表达式的用法:
forEach的使用效果与增强 for相同
// 使用曾强for循环迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list){
if(str.length()>3)
System.out.println(str);
}
现在使用forEach()方法结合匿名内部类,可以这样实现:
// 使用forEach()结合匿名内部类迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){
@Override
public void accept(String str){
if(str.length()>3)
System.out.println(str);
}
});
使用Lambda表达式实现如下:
// 使用forEach()结合Lambda表达式迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach( str -> {
if(str.length()>3)
System.out.println(str);
});
上述代码给forEach()方法传入一个Lambda表达式,我们不需要知道accept()方法,也不需要知道Consumer接口,类型推导帮我们做了一切。(虽然这里看起来还是增强for最优)
2.removeIf()
该方法签名为boolean removeIf(Predicate super E> filter),作用是删除容器中所有满足filter指定条件的元素,其中Predicate是一个函数接口,里面只有一个待实现方法boolean test(T t),同样的这个方法的名字根本不重要,因为用的时候不需要书写这个名字。
需求:假设有一个字符串列表,需要删除其中所有长度大于3的字符串。
我们知道如果需要在迭代过程冲对容器进行删除操作必须使用迭代器,否则会抛出ConcurrentModificationException,所以上述任务传统的写法是:
// 使用迭代器删除列表元素
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Iterator<String> it = list.iterator();
while(it.hasNext()){
if(it.next().length()>3) // 删除长度大于3的元素
it.remove();
}
现在使用removeIf()方法结合匿名内部类,我们可是这样实现:
// 使用removeIf()结合匿名名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){
// 删除长度大于3的元素
@Override
public boolean test(String str){
return str.length()>3;
}
});
使用lambda表达式:
// 使用removeIf()结合Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length()>3); // 删除长度大于3的元素
这里就可以看出lambda表达式的好处了
该方法签名为void replaceAll(UnaryOperator operator),作用是对每个元素执行operator指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数Tapply(T t)。
需求:假设有一个字符串列表,将其中所有长度大于3的元素转换成大写,其余元素不变。
Java7及之前似乎没有优雅的办法:
// 使用下标实现元素替换
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(int i=0; i<list.size(); i++){
String str = list.get(i);
if(str.length()>3)
list.set(i, str.toUpperCase());
}
使用replaceAll()方法结合匿名内部类可以实现如下:
// 使用匿名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator<String>(){
@Override
public String apply(String str){
if(str.length()>3)
return str.toUpperCase();
return str;
}
});
用更为简洁的Lambda表达式实现:
// 使用Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(str -> {
if(str.length()>3)
return str.toUpperCase();
return str;
});
4.sort()
该方法定义在List接口中,方法签名为void sort(Comparator super E> c),该方法根据c指定的比较规则对容器元素进行排序。Comparator接口我们并不陌生,其中有一个方法int compare(T o1, T o2)需要实现,显然该接口是个函数接口。
需求:假设有一个字符串列表,按照字符串长度增序对元素排序。
由于Java7以及之前sort()方法在Collections工具类中,所以代码要这样写:
// Collections.sort()方法
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator<String>(){
@Override
public int compare(String str1, String str2){
return str1.length()-str2.length();
}
});
现在可以直接使用List.sort()方法,结合Lambda表达式,可以这样写:
// List.sort()方法结合Lambda表达式
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());
1)可以和iterator()一样迭代(逐个迭代),又可以批量迭代,批量迭代的时候可以降低迭代开销;
2)可以一个线程变为现实两个线程(split),通过调用Spliterator trySplit()方法来尝试分成两个。一个是this,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。相当于一个迭代器变为两个迭代器,两个互不影响,他们都可以再去分裂;
Map:
forEach()
该方法签名为void forEach(BiConsumer super K,? super V> action),作用是对Map中的每个映射执行action指定的操作,其中BiConsumer是一个函数接口,里面有一个待实现方法void accept(T t, U u)。BinConsumer接口名字和accept()方法名字都不重要,请不要记忆他们。
需求:假设有一个数字到对应英文单词的Map,请输出Map中的所有映射关系.
需求:假设有一个数字到对应英文单词的Map,请输出Map中的所有映射关系.
Java7以及之前经典的代码如下:
// Java7以及之前迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
使用Map.forEach()方法,结合匿名内部类,代码如下
// 使用forEach()结合匿名内部类迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach(new BiConsumer<Integer, String>(){
@Override
public void accept(Integer k, String v){
System.out.println(k + "=" + v);
}
});
上述代码调用forEach()方法,并使用匿名内部类实现BiConsumer接口。当然,实际场景中没人使用匿名内部类写法,因为有Lambda表达式:
// 使用forEach()结合Lambda表达式迭代Map
HashMap<Integer, String> 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,输出4对应的英文单词,如果不存在则输出NoValue
// 查询Map中指定的值,不存在时使用默认值
HashMap<Integer, String> 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做更改.该方法将条件判断和赋值合二为一,使用起来更加方便.
4. remove()
我们都知道Map中有一个remove(Object key)方法,来根据指定key值删除Map中的映射关系;Java8新增了remove(Object key, Object value)方法,只有在当前Map中key正好映射到value时才删除该映射,否则什么也不做.
5. replace()
在Java7及以前,要想替换Map中的映射关系可通过put(K key, V value)方法实现,该方法总是会用新值替换原来的值.为了更精确的控制替换行为,Java8在Map中加入了两个replace()方法,分别如下:
replace(K key, V value),只有在当前Map中key的映射存在时才用value去替换原来的值,否则什么也不做.
replace(K key, V oldValue, V newValue),只有在当前Map中key的映射存在且等于oldValue时才用newValue去替换原来的值,否则什么也不做
6. replaceAll()
该方法签名为replaceAll(BiFunction super K,? super V,? extends V> function),作用是对Map中的每个映射执行function指定的操作,并用function的执行结果替换原来的value,其中BiFunction是一个函数接口,里面有一个待实现方法R apply(T t, U u).不要被如此多的函数接口吓到,因为使用的时候根本不需要知道他们的名字.
需求:假设有一个数字到对应英文单词的Map,请将原来映射关系中的单词都转换成大写.
Java7以及之前经典的代码如下:
// Java7以及之前替换所有Map中所有映射关系
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){
entry.setValue(entry.getValue().toUpperCase());
}
使用Lambda表达式实现如下:
// 使用replaceAll()结合Lambda表达式实现
HashMap<Integer, String> 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));
9.computeIfAbsent()
该方法签名为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
Map<Integer, Set<String>> map = new HashMap<>();
// Java7及以前的实现方式
if(map.containsKey(1)){
map.get(1).add("one");
}else{
Set<String> valueSet = new HashSet<String>();
valueSet.add("one");
map.put(1, valueSet);
}
// Java8的实现方式
map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");
computeIfPresent()
该方法签名为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;
Java8为容器新增一些有用的方法,这些方法有些是为完善原有功能,有些是为引入函数式编程,学习和使用这些方法有助于我们写出更加简洁有效的代码.
函数接口虽然很多,但绝大多数时候我们根本不需要知道它们的名字,书写Lambda表达式时类型推断帮我们做了一切.