Lambda表达式、Stream学习总结

在学习Lambda表达式和Stream之前,应该先学习了解Java8内置的4个函数式接口,即在java.util.function下的Function、Consumer、Supplier、Predict。 

这四个接口对应的含义分别是:

Function<T,R>

接收一个参数,返回一个结果

Consumer<T>

接收一个参数,无返回结果

Supplier<T>

空参数,返回一个结果

Predict<T>

接收一个参数,返回一个布尔值

分别对应里面的四个抽象方法:

  • Function<T,R>: R apply(T t);
  • Consumer<T>: void accept(T t);
  • Supplier<T>: T get( );
  • Predict<T>: boolean test(T t);

只对Function举例使用:

main{
    System.out.println(testFunction1(ele ->ele*2,5));
    System.out.println(testFunction2(
            ele -> ele.stream().mapToInt(item -> item.length() > 2 ? 1 : 0).sum()
            , Stream.of("asdd","da","gdfdf","s","fsdfsdds").collect(Collectors.toList())));
}

public static int testFunction1(Function<Integer,Integer> fun,Integer val){
    return fun.andThen(ele -> ele+3).apply(val);
    //fun2.apply(this.apply(val))
}

public  static int testFunction2(Function<List<String>,Integer> fun,List<String> list){
    return fun.apply(list);
}

其他的接口的使用方法相同,需要注意的就是,我们使用这种接口,利用Lambda表达式是将一个匿名内部类对象作为一个参数传递到使用的地方了!

而匿名内部类对象的特点就是: new Interface() | 父类构造器(实参列表),适用于使用一次的类对象,当创建了匿名内部类对象之后,该类的定义立即消失。而Lambda表达式就是帮我们创建函数式接口的匿名内部类对象,所以Lambda表达式匿名内部类的关系很紧密


学习完这四个函数是接口,下面介绍Lambda的基本使用:

1.Lambda表达式使用场景:

Lambda表达式可以取代大部分情况的匿名内部类,注意,正如上面说述,Lambda的应用就是为了取消大部分情况的匿名内部类。(只能替代用于只有一个抽象方法的接口创建匿名内部类的情况,因为需要让程序自己去确定实现的函数以及去创建实例。[只能是函数式接口,就算是只有一个抽象方法的抽象类也不行!],能够极大的优化代码结构,使代码更加紧凑。

2.Lambda表达式的基本使用:

基本语法:

(T ...) -> { ... },其中(T ...)是参数列表,{ ... }是方法体, -> 是Lambda运算符,读作 goes to。

Predicate<String> predict = (String val) -> {
    if(val.length()>5){
        return true;
    }else{
        return false;
    }
};

3.Lambda语法的简化:

(0)简化参数类型,在参数列表中可以省略参数类型(省略必须全部省略)

Predicate<String> predict = (val) -> {
    if(val.length()>5){
        return true;
    }else{
        return false;
    }
};

(1)当只有一个参数时,可以省略“( )”

Predicate<String> predict = val -> {
    if(val.length()>5){
        return true;
    }else{
        return false;
    }
};

(2)当描述方法体只有一句话时,可以省略“{ }”

Predicate<String> predict = val -> 
    return val.length()>5?true:false;

(3)当描述方法体只有一句话且是return语句时,可以省略“return”

Predicate<String> predict = val -> val.length()>5?true:false;

4.Lambda表达式的方法引用:

使用场景:有时候我们不必自己去实现接口中的方法,而是可以直接使用现成的别人写好的方法,这个时候就可以使用Lambda的方法引用。即:使用别人的方法作为函数式接口中未实现的方法,所以对应的要求必须是:参数列表相同,返回值相同,方法名不要求相同。

(1)基本语法:

方法归属者::方法名

即:静态方法的方法归属者是类名,实例方法的方法归属者是对象。

Consumer<String> consumer = System.out::println;

(2)构造方法引用:

当函数式接口是返回的某个实例,如果没有特别的功能,就可以直接使用构造方法引用,基本的语法是:  类名::new;

Supplier<Object> supplier = Object::new;

5.Lambda表达式的闭包问题:

匿名内部类在函数中存在的问题,在Lambda中肯定也存在。例如我们在方法中的匿名内部类使用了外部的变量,那么编译器会默认为这个变量加上final修饰,所以下面如果试图去修改这个变量,就会报错:该值是final类型,不能改变。


学习完四个函数式接口以及Lambda表达式,下面介绍Java8的Stream。

1.Stream的基本介绍:

Java8引入的Stream特性,使得代码结构更加紧凑,简洁易读;且并发模式使得代码执行速度更快。

2.Stream的构成:

Source、Intermediate、Terminal。

(1)流的构造(Source)

  • 利用Stream直接构造流:Stream.of(T ...)

Stream<String> stream = Stream.of("as", "dsd", "kojj", "fsdd");
  • 利用数组构造流:Arrays.Stream(T[] arr)

String[] strArr = {"as", "dsd", "kojj", "fsdd"};
Stream<String> stream = Arrays.stream(strArr);
  • 集合转换为流:Collection.stream()

String[] strArr = {"as", "dsd", "kojj", "fsdd"};
List<String> list = Arrays.asList(strArr);
Stream<String> stream = list.stream();

  • 文件读取转换为流:java.io.BufferedReader.lines()

List<String> output = reader.lines().
     flatMap(line -> Stream.of(line.split(REGEXP))).
     filter(word -> word.length() > 0).
     collect(Collectors.toList());
  • 自己构建流:Stream.generate(Supplier<T> supplier)

注意:Supplier实现的流是无限的,要么使用short-circuiting操作,要么使用limit限制流。

Random random = new Random();
Stream<Integer> stream = Stream.generate(random::nextInt);
stream.limit(10).forEach(System.out::println);
  • 利用Stream.iterator(T seed, Function<T> fun)构建流,无限流需要short-circuiting操作

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
Stream.iterate(0,val->val+3).limit(10).forEach(System.out::println);

注意:对于基本数值类型有对应的IntStream、LongStream以及DoubleStream,也可以使用泛型限定,但是boxing与Unboxing操作会耗时,应优先使用给定的三种数值流。

(2)转换操作(Intermediate)

常用的转换操作主要有:

map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、 skip、 parallel、sequential、unordered。

示例:

  • map(Function<T,R> fun) 一对一处理

List<String> output = wordList.stream().
    map(String::toUpperCase).
    collect(Collectors.toList());
  • flatMap(Function<T,Stream> fun) 层级结构扁平化,汇总成流

Stream<List<Integer>> inputStream = Stream.of(
     Arrays.asList(1),
     Arrays.asList(2, 3),Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
    flatMap((childList) -> childList.stream());
  • filter(Predict<T> predict) 将predict.test(T t)测试为true的留下形成新stream

List<String> output = reader.lines().
     flatMap(line -> Stream.of(line.split(REGEXP))).
     filter(word -> word.length() > 0).
     collect(Collectors.toList());

(3)终结操作(Terminal)

常见的终结操作主要有:

forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator。

示例:

  • forEach(Consumer<T> consumer)遍历操作元素

roster.stream()
 .filter(p -> p.getGender() == Person.Sex.MALE)
 .forEach(p -> System.out.println(p.getName()));

注意:由于Stream是Terminal操作,所以不可以在一个Stream中使用两次Terminal操作,例如如下错误示例:

stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));

相应的,有peek(Consumer<T> consumer)来进行类似操作,因为peek属于Intermediate操作,不会终止流。

Stream.of("one", "two", "three", "four")
 .filter(e -> e.length() > 3)
 .peek(e -> System.out.println("Filtered value: " + e))
 .map(String::toUpperCase)
 .peek(e -> System.out.println("Mapped value: " + e))
 .collect(Collectors.toList());

这里应该注意的是,程序的输出结果是:

Filtered value: three

Mapped value: THREE

Filtered value: four

Mapped value: FOUR

所以,从这里可以看到Stream对元素的执行顺序是每一个元素依次执行,而不是每一阶段的流向下整体执行

  • Option<T> findFirst()返回流中第一个元素,属于Terminal兼short-circuiting操作

public static int getNum(){
    Random random = new Random();
    Stream<Integer> stream3 = Stream.generate(random::nextInt);
    //stream3.findFirst().ifPresent(System.out::println);
    return stream3.limit(0).findFirst().orElse(-1);
}

这里需要注意的是,对于此类操作,返回的是Optional<T>包装类型,有对应的处理方法如orElse(T t)、ifPresent(Consumer<T> consumer)等。在程序逻辑中可以在一定程度上防止NPE问题。类似的操作还有:findAny、max/min、reduce等。

  • T reduce(T identity, BinaryOperator<T> accumulator) 将元素进行迭代结合,其实max/min、sum、average都属于封装的reduce操作

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
//注意加不加identity初始值的区别,很合理
Optional<Integer> reduce = stream3.limit(10).reduce(Integer::sum);
Integer sum = stream3.limit(10).reduce(0, Integer::sum);
  • limit/skip(Long n) 限定流条件

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
int num = stream3.limit(10).skip(3).collect(Collectors.toList()).size();
System.out.println(num);
  • sorted(Comparator<? super T> comparator)对元素进行排序

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
stream3.limit(10).sorted((o1, o2) -> o2-o1).forEach(System.out::println);
  • min/max/distinct()

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
stream3.limit(10).distinct().max((o1, o2) -> o1-o2).ifPresent(System.out::println);

br.lines().flatMap(line -> Stream.of(line.split(" ")))
    .filter(word -> word.length() > 0)
    .map(String::toLowerCase)
    .distinct()
    .sorted()
    .collect(Collectors.toList());
br.close();
System.out.println(words);

(4)短路操作(short-ciecuiting)

常见的短路操作有:

anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

示例:

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
boolean b = stream3.limit(10).noneMatch(val -> val > 0);
System.out.println(b);

(5)利用Collectors进行分组操作(groupingBy/partitioningBy

java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。

  • Collectors.groupingBy(Function<? super T, ? extends K> classifier)按照Function条件进行分组

Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
     limit(100).
     collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
     Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
     System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
 }
  • Collectors.partitioningBy(Predicate<? super T> predicate) 按照predict条件分为两组

Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
Map<Boolean, List<Integer>> collect = stream3.limit(10).collect(Collectors.partitioningBy(val -> val > 0));
Set<Boolean> keySet = collect.keySet();
keySet.forEach(item->collect.get(item).forEach(System.out::println));

以上,多线程对应的处理方法如parallelStream()进行处理,提高效率,但是并行处理不能保证元素有序,还应注意线程安全问题。

 

over.

以上参考博客:链接1链接2链接3.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值