在学习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表达式可以取代大部分情况的匿名内部类,注意,正如上面说述,Lambda的应用就是为了取消大部分情况的匿名内部类。(只能替代用于只有一个抽象方法的接口创建匿名内部类的情况,因为需要让程序自己去确定实现的函数以及去创建实例。)[只能是函数式接口,就算是只有一个抽象方法的抽象类也不行!],能够极大的优化代码结构,使代码更加紧凑。
(T ...) -> { ... },其中(T ...)是参数列表,{ ... }是方法体, -> 是Lambda运算符,读作 goes to。
Predicate<String> predict = (String val) -> {
if(val.length()>5){
return true;
}else{
return false;
}
};
(0)简化参数类型,在参数列表中可以省略参数类型(省略必须全部省略)
Predicate<String> predict = (val) -> {
if(val.length()>5){
return true;
}else{
return false;
}
};
Predicate<String> predict = val -> {
if(val.length()>5){
return true;
}else{
return false;
}
};
Predicate<String> predict = val ->
return val.length()>5?true:false;
(3)当描述方法体只有一句话且是return语句时,可以省略“return”
Predicate<String> predict = val -> val.length()>5?true:false;
使用场景:有时候我们不必自己去实现接口中的方法,而是可以直接使用现成的别人写好的方法,这个时候就可以使用Lambda的方法引用。即:使用别人的方法作为函数式接口中未实现的方法,所以对应的要求必须是:参数列表相同,返回值相同,方法名不要求相同。
即:静态方法的方法归属者是类名,实例方法的方法归属者是对象。
Consumer<String> consumer = System.out::println;
当函数式接口是返回的某个实例,如果没有特别的功能,就可以直接使用构造方法引用,基本的语法是: 类名::new;
Supplier<Object> supplier = Object::new;
匿名内部类在函数中存在的问题,在Lambda中肯定也存在。例如我们在方法中的匿名内部类使用了外部的变量,那么编译器会默认为这个变量加上final修饰,所以下面如果试图去修改这个变量,就会报错:该值是final类型,不能改变。
学习完四个函数式接口以及Lambda表达式,下面介绍Java8的Stream。
Java8引入的Stream特性,使得代码结构更加紧凑,简洁易读;且并发模式使得代码执行速度更快。
- 利用Stream直接构造流:Stream.of(T ...)
Stream<String> stream = Stream.of("as", "dsd", "kojj", "fsdd");
String[] strArr = {"as", "dsd", "kojj", "fsdd"};
Stream<String> stream = Arrays.stream(strArr);
String[] strArr = {"as", "dsd", "kojj", "fsdd"};
List<String> list = Arrays.asList(strArr);
Stream<String> stream = list.stream();
List<String> output = reader.lines().
flatMap(line -> Stream.of(line.split(REGEXP))).
filter(word -> word.length() > 0).
collect(Collectors.toList());
注意:Supplier实现的流是无限的,要么使用short-circuiting操作,要么使用limit限制流。
Random random = new Random();
Stream<Integer> stream = Stream.generate(random::nextInt);
stream.limit(10).forEach(System.out::println);
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操作会耗时,应优先使用给定的三种数值流。
map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、 skip、 parallel、sequential、unordered。
List<String> output = wordList.stream().
map(String::toUpperCase).
collect(Collectors.toList());
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());
List<String> output = reader.lines().
flatMap(line -> Stream.of(line.split(REGEXP))).
filter(word -> word.length() > 0).
collect(Collectors.toList());
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator。
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());
Mapped value: THREE
所以,从这里可以看到Stream对元素的执行顺序是每一个元素依次执行,而不是每一阶段的流向下整体执行。
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等。
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);
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);
Random random = new Random();
Stream<Integer> stream3 = Stream.generate(random::nextInt);
stream3.limit(10).sorted((o1, o2) -> o2-o1).forEach(System.out::println);
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);
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 元素进行归组。
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());
}
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()进行处理,提高效率,但是并行处理不能保证元素有序,还应注意线程安全问题。