java8新特性-Lambda表达式

Posted Vitality

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java8新特性-Lambda表达式相关的知识,希望对你有一定的参考价值。

Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。

Collections中的常用函数接口

Java集合框架的接口继承结构:

 

 

 上图中蓝色标记和橙色标记的接口类,表示在Java8中加入了新的接口方法,由于继承关系,他们相应的子类也会继承这些方法。

下面用一张表列举这些方法

接口名 Java8新加入的方法
Collection removeIf() spliterator() stream() parallelStream() forEach()
List replaceAll() sort()
Map getOrDefault() forEach() replaceAll() putIfAbsent() replace() computeIfAbsent() computeIfPresent() compute() merge

这些新加入的方法大部分要用到java.util.function包下的接口,这就表明这些方法大部分都和Lambda表达式相关

Collections中的新方法

forEach()

该方法名为void forEach(Consumer action),作用是对容器中的每个元素执行action指定的动作,其中Consumer是个函数接口,里面只有一个待实现方法void accept(T t)

匿名内部类实现:

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(3,6,9,10));
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                if (integer%3==0){
                    System.out.println(integer);
                }
            }
        });

Lambda表达式实现:

//Lambda表达式实现
list.forEach(s->{
     if (s%3==0){
           System.out.println(s);
      }
});         

运行结果都是 :

 

 removeIf()

该方法签名为boolean removeIf(Predicate filter),作用是删除容器中所有满足filter指定条件的元素,其中Predicate是一个函数接口,里面只有一个待实现方法boolean test(T t)。

匿名内部类以及Lambda表达式实现:

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(3,6,9,10));
//匿名内部类实现
list.removeIf(new Predicate<Integer>() {
    @Override
    public boolean test(Integer integer) {

        return integer%3==0;
    }
});
System.out.println(list);

//Lambda表达式实现
list.removeIf(s->s%3==0);
System.out.println(list);

运行结果:

replaceAll()

该方法签名为void replaceAll(UnaryOperator<E> operator),作用是对每个元素执行operator指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数T apply(T t)。

匿名内部类以及Lambda表达式实现:

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(3,6,9,10));
//匿名内部类实现
list.replaceAll(new UnaryOperator<Integer>() {
    @Override
    public Integer apply(Integer integer) {
        if(integer%3==0){
            return ++integer;
        }
        return --integer;
    }
});
System.out.println(list);

//Lambda表达式实现
list.replaceAll(s->{
    if(s%3==0){
      return ++s;
    }
    return --s;
});
System.out.println(list);

运行结果:

sort()

该方法定义在List接口中,方法签名为void sort(Comparator c),该方法根据c指定的比较规则对容器元素进行排序。Comparator接口中有一个方法int compare(T o1, T o2)需要实现,该接口是个函数接口。

匿名内部类以及Lambda表达式实现:

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(10,9,6,3));
//匿名内部类实现
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1-o2;
    }
});
System.out.println(list);

//Lambda表达式实现
Collections.sort(list,(first,second)->first-second);
System.out.println(list);

运行结果:

spliterator()

方法签名为Spliterator<E> spliterator(),该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()方法有点像,我们知道Iterator是用来迭代容器的,Spliterator也有类似作用,但二者有如下不同:

Spliterator既可以像Iterator那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。

Spliterator是可拆分的,一个Spliterator可以通过调用Spliterator trySplit()方法来尝试分成两个。一个是this,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。
可通过(多次)调用Spliterator.trySplit()方法来分解负载,以便多线程处理。

stream()和parallelStream()

stream()和parallelStream()分别返回该容器的Stream视图表示,不同之处在于parallelStream()返回并行的Stream。Stream是Java函数式编程的核心类,具体内容后面单独介绍。

Map中的新方法

forEach()

该方法签名为void forEach(BiConsumer action),作用是对Map中的每个映射执行action指定的操作,其中BiConsumer是一个函数接口,里面有一个待实现方法void accept(T t, U u)。

Map<Integer,String> map = new HashMap<>();
map.put(1,"我");
map.put(2,"拒绝");
map.put(3,"996");
//匿名内部类实现
map.forEach(new BiConsumer<Integer, String>() {
    @Override
    public void accept(Integer integer, String s) {
        System.out.println(integer+"="+s);
    }
});

//Lambda表达式实现
map.forEach((key,value)-> System.out.println(key+"="+value));

运行结果:

该方法跟Lambda表达式虽然没关系,但是很有用。

方法签名为V getOrDefault(Object key, V defaultValue),作用是按照给定的key查询Map中对应的value,如果没有找到则返回defaultValue。使用该方法可以省去查询指定键值是否存在的麻烦。

匿名内部类实现方式以及Lambda表达式实现方式:

//Java7以前的做法
if (map.containsKey(4)) {
    System.out.println(map.get(4));
} else {
    System.out.println("noValue");
}
//Java8使用Map.getOrDefault()
System.out.println(map.getOrDefault(4,"noValue"));

运行结果:

 

putIfAbsent()

 

该方法跟Lambda表达式没关系,但是很有用。方法签名为V putIfAbsent(K key, V value),作用是只有在不存在key值的映射或映射值为null时,才将value指定的值放入到Map中,否则不对Map做更改.该方法将条件判断和赋值合二为一,使用起来更加方便。

 

remove()

 

我们都知道Map中有一个remove(Object key)方法,来根据指定key值删除Map中的映射关系;Java8新增了remove(Object key, Object value)方法,只有在当前Map中key正好映射到value时才删除该映射,否则什么也不做。

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去替换原来的值,否则什么也不做。

replaceAll()

该方法签名为replaceAll(BiFunction function),作用是对Map中的每个映射执行function指定的操作,并用function的执行结果替换原来的value,其中BiFunction是一个函数接口,里面有一个待实现方法R apply(T t, U u)。

匿名内部类实现方式以及Lambda表达式实现方式:

Map<Integer,String> map = new HashMap<>();
map.put(1,"我");
map.put(2,"拒绝");
map.put(3,"996");
//匿名内部类实现方式
map.replaceAll(new BiFunction<Integer, String, String>() {
    @Override
    public String apply(Integer integer, String s) {
        if(s.equals("我")){
            s = "他";
        }
        return s.toUpperCase();
    }
});
map.forEach((key,value)-> System.out.println(key+"="+value));
//Lambda表达式实现方式
map.replaceAll((key,value)->{
    if (value.equals("拒绝")) {
        value = "Like";
    }
    return value.toUpperCase();
});
map.forEach((key,value)-> System.out.println(key+"="+value));

运行结果:

merge()

该方法签名为merge(K key, V value, BiFunction remappingFunction)。

作用是:

  • 如果Map中key对应的映射不存在或者为null,则将value(不能是null)关联到key上;

  • 否则执行remappingFunction,如果执行结果非null则用该结果跟key关联,否则在Map中删除key的映射。

    参数中BiFunction函数接口前面已经介绍过,里面有一个待实现方法R apply(T t, U u)。

merge()方法虽然语义有些复杂,但该方法的用方式很明确,一个比较常见的场景是将新的错误信息拼接到原来的信息上,比如:

Map<Integer,String> map = new HashMap<>();
map.put(1,"我");
map.put(2,"拒绝");
map.put(3,"996");
map.forEach((key, value) -> System.out.println(key + "=" + value));
map.merge(1, "和你", (v1, v2) -> v1+v2);
map.forEach((key, value) -> System.out.println(key + "=" + value));

运行结果:

compute()

该方法签名为compute(K key, BiFunction remappingFunction),作用是把remappingFunction的计算结果关联到key上,如果计算结果为null,则在Map中删除key的映射。

Map<Integer,String> map = new HashMap<>();
map.put(1,"我");
map.put(2,"拒绝");
map.put(3,"996");
map.forEach((key, value) -> System.out.println(key + "=" + value));
//计算结果不是null
System.out.println("---------------------------");
map.compute(1, (k,v) -> v == null ? "值为空" : v.concat("和你"));
map.forEach((key, value) -> System.out.println(key + "=" + value));
//计算结果为null
System.out.println("---------------------------");
map.compute(4, (k,v) -> v == null ? "值为空" : v.concat("和你"));
map.forEach((key, value) -> System.out.println(key + "=" + value));

运行结果:

computeIfAbsent()

该方法签名为V computeIfAbsent(K key, Function mappingFunction)

作用是:只有在当前Map中不存在key值的映射或映射值为null时,才调用mappingFunction,并在mappingFunction执行结果非null时,将结果跟key关联。

Function是一个函数接口,里面有一个待实现方法R apply(T t)。

computeIfAbsent()常用来对Map的某个key值建立初始化映射.比如我们要实现一个多值映射,Map的定义可能是Map<K,Set<V>>,要向Map中放入新值,可通过如下代码实现:

Map<Integer, Set<String>> map = new HashMap<>();
// Java7及以前的实现方式
if(map.containsKey(1)){
    map.get(1).add("123");
}else{
    Set<String> valueSet = new HashSet<String>();
    valueSet.add("123");
    map.put(1, valueSet);
}
// Java8的实现方式
map.computeIfAbsent(1, v -> new HashSet<String>()).add("345");
map.forEach((key, value) -> System.out.println(key + "=" + value));

运行结果:

使用computeIfAbsent()将条件判断和添加操作合二为一,使代码更加简洁。

computeIfPresent()

该方法签名为V computeIfPresent(K key, BiFunction remappingFunction),作用跟computeIfAbsent()相反。即只有在当前Map中存在key值的映射且非null时,才调用remappingFunction,如果remappingFunction执行结果为null,则删除key的映射,否则使用该结果替换key原来的映射。

Stream

对于Java 7来说stream完全是个陌生东西,stream并不是某种数据结构,它只是数据源的一种视图。这里的数据源可以是一个数组,Java容器或I/O channel等。

常见的stream接口继承关系如下:

BaseStream(IntStream,LongStream,DoubleSream,Stream)

其中IntStream、LongStream、DoubleStream对应三种基本类型(int,Long,Double)注意:是基本类型不是包装类型,Stream对应所有剩余类型的stream视图。

 

虽然大部分情况下stream是容器调用Collection.stream()方法得到的,但stream和collections有以下不同:

 

  • 无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。

  • 为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。

  • 惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。

  • 可消费性。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

 

Stream接口部分常见方法

操作类型 操作方法
中间操作 concat() distinct() filter() limit() map() peek() skip() sorted() parallel() sequential() unordered()
结束操作 allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

区分中间操作和结束操作最简单的方法,就是看方法的返回值,返回值为stream的大都是中间操作,否则是结束操作。

stream方法使用:

forEach()

方法签名为void forEach(Consumer action),作用是对容器中的每个元素执行action指定的动作,也就是对元素进行遍历。

Stream<String> stream = Stream.of("I","love"," ","you","java","you");
stream.forEach(str-> System.out.println(str));

运行结果:

由于forEach()是结束方法,上述代码会立即执行,输出所有字符串,即上面所示

filter()

函数原型为Stream<T> filter(Predicate predicate),作用是返回一个只包含满足predicate条件元素的Stream。

Stream<String> stream = Stream.of("I","love"," ","you","java","you");
stream.filter(str->str.length()>=3).forEach(str-> System.out.println(str));

上述代码将输出为长度大于等于3的字符串love和Java。注意,由于filter()是个中间操作,如果只调用filter()不会有实际计算,因此也不会输出任何信息。

运行结果:

 

 

distinct()

函数原型为Stream<T> distinct(),作用是返回一个去除重复元素之后的Stream。

Stream<String> stream = Stream.of("I","love"," ","you","java","you");
stream.distinct().forEach(str-> System.out.println(str));

运行结果

sorted()

排序函数有两个,一个是用自然顺序排序,一个是使用自定义比较器排序,函数原型分别为Stream<T> sorted()和Stream<T> sorted(Comparator comparator)。

// 排序
Stream<String> stream1 = Stream.of("I", "love", "you", "too", "Java");
stream1.sorted((str1, str2) -> str1.length() - str2.length()).forEach(str -> System.out.println(str));

运行结果:

map()

函数原型为<R> Stream<R> map(Function mapper),作用是返回一个对当前所有元素执行mapper之后的结果组成的Stream。直观的说,就是对每个元素按照某种操作进行转换,转换前后Stream中元素的个数不会改变,但元素的类型取决于转换之后的类型。

Stream<String> stream = Stream.of("i", "love", "java", "too");
stream.map(str -> str.toUpperCase()).forEach(str -> System.out.println(str));

运行结果:

flatMap()

函数原型为<R> Stream<R> flatMap(Function> mapper),作用是对每个元素执行mapper指定的操作,并用所有mapper返回的Stream中的元素组成一个新的Stream作为最终返回结果。说起来太拗口,通俗的讲flatMap()的作用就相当于把原stream中的所有元素都”摊平”之后组成的Stream,转换前后元素的个数和类型都可能会改变。

// 将两个集合中大于等于2的数重新组成Stream,然后输出
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
stream.flatMap(list -> list.stream().filter(integer -> integer >= 2)).forEach(i -> System.out.println(i));

运行结果:

流的规约操作

规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作。Stream类库有两个通用的规约操作reduce()和collect(),也有一些为简化书写而设计的专用规约操作,比如sum()、max()、min()、count()等。

reduce()

reduce操作可以实现从一组元素中生成一个值,sum()、max()、min()、count()等都是reduce操作,将他们单独设为函数只是因为常用。

reduce()的方法定义有三种重写形式:

  • Optional<T> reduce(BinaryOperator<T> accumulator)

  • T reduce(T identity, BinaryOperator<T> accumulator)

  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

具体实例:

// 找出最长的单词
Stream<String> stream1 = Stream.of("I", "like", "you", "too");
//方式1
Optional<String> longest = stream1.reduce((s1, s2) -> s1.length() >= s2.length() ? s1 : s2);
System.out.println(longest.get());

//方式2
Optional<String> longest1 = stream1.max((s1, s2) -> s1.length() - s2.length());
System.out.println(longest1.get());

运行结果:

 

 上述代码会选出最长的单词like,其中Optional是(一个)值的容器,使用它可以避免null值的麻烦。当然可以使用Stream.max(Comparator comparator)方法来达到同等效果

// 求单词长度之和
// (参数1)初始值
// (参数2)累加器
// (参数3)部分和拼接器,并行执行时才会用到
Stream<String> stream2 = Stream.of("I", "love", "you", "too");
//方式1
Integer lengthSum = stream2.reduce(0, (sum, str) -> sum + str.length(), (a, b) -> a + b);
System.out.println(lengthSum);
//方式2
int lengthSum1 = stream2.mapToInt(str -> str.length()).sum();
System.out.println(lengthSum1);

参数2处:

  1. 字符串映射成长度。

  2. 并和当前累加和相加。

这显然是两步操作,使用reduce()函数将这两步合二为一,更有助于提升性能。如果想要使用map()和sum()组合来达到上述目的,也是可以的。

如果想要从Stream生成一个集合或者Map等复杂的对象就要使用collect()方法了

Stream<String> stream = Stream.of("I", "love", "you", "too");
// 转换成list集合
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
//转换成set集合
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set);
//转换成map集合
Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
System.out.println(map);

运行结果:

 

 

 

 

以上是关于java8新特性-Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章

Java8新特性之一:Lambda表达式

Java8新特性--Lambda表达式

Java8新特性-Lambda表达式

Java8新特性-Lambda表达式

java8新特性之Lambda表达式入门

java8新特性之——lambda表达式的使用