一、java8的四类函数式接口
Consumer<T> 消费型
void accept(T t); 接收一个输入参数,无返回值。
Supplier<T> 供给型
T get(); 无参数,返回一个结果。
Function<T,R> 文档型
R apply(T t); 接收一个输入参数,返回一个结果。
Predicate<T> 断言型
boolean test(T t); 接收一个输入参数,返回一个布尔值结果。
二、Stream创建流的几种方式
空Stream(Empty Stream)
方法empty()被用于创建一个Empty Stream:
Stream<String> streamEmpty = Stream.empty();
上述代码段创建的Empty Stream通常被用于避免null对象或零元素对象的streams(streams
with no element)返回结果为null。
public Stream<String> streamOf(List<String> list){
return list == null || list.isEmpty() ? Stream.empty() : list.streams();
}
集合Steram(Stream of Collection)
可以创建任意Collection接口衍生类(Collection->List、Set、Queue)的Streams:
Collections<String> collection = Arrays.asList("a", "b", "c");
Stream<Stirng> streamOfCollection = collection.stream();
数组Stream(Stream of Array)
创建数组Stream:
Stream<String> streamOfArray = Stream.of("a", "b", "c");
可以选择Stream中包含的元素数量:
String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
备注:截取的是(1,3] ----> 输出的是b和c
构建器(Stream.builder())
当builder被用于指定参数类型时,应被额外标识在声明右侧,否则方法build()将创建一个Stream(Object)实例:
Stream<String> streamBuilder = Stream.<String>builder()
.add("a").add("b").add("c")
.build();
生成器(Stream.generator())
方法generator()接受一个供应器Supplier<T>用于元素生成。由于生产流(resulting
stream)被定义之后属于无限流(即无止境地不断生产),开发者必须指定stream拥有流的目标大小,否则方法generator()将持续生产直到jvm内存到达顶值(memory limit):
Stream<String> streamOfGenerated = Stream.generate( () -> "element").limit(10);
上述代码将创建十个内容为“element”的生成流。
迭代器(Stream.iterate())
另一种创建无限流的方法是通过调用方法iterate(),同样的它也需要使用方法limit()对目标流的元素大小进行限制:
Stream<Integer> streamItreated = Stream.iterate(40, n -> n + 2).limit(20);
迭代流即采用迭代的方法作为元素生产方式,类似于高中数学中的f(x),f(f(x)),etc。上述例子中,生成流的第一个元素是迭代器iterate()中的第一个元素40,从第二个元素开始的每个新元素都与上个元素有关,在此例中,生成流中的元素为:40、42、44 ... 76、78。
基元流(Stream of Primitives)
Java8提供了创建三大基础数据类型(int、long、double)stream的方式。由于Stream<T>是一个类接口,我们无法采用泛型传参的方式声明基础数据类型的stream,因此三个特殊的接口就被创造出来了:IntStream、LongStream、DoubleStream。使用它们能够避免不必要的自动装箱,以提高生产效率。
①IntStream
IntStream intStream = IntStream.range(1, 3);
方法range(int startInclusive, int
endInclusive)创建了一个有序流(从startInclusive到endInclusive)。它使后面的值每个增加1,但却不包括最后一个参数,即[),最终结果是1,2。
②LongStream
LongStream longStream = LongStream.rangeClosed(5, 300);
方法rangeClosed(int startInclusive, int endInclusive)与range()大致相同,它包括最后一个参数,即(),最终结果是5,6,7.....299,300
③DoubleStream
Random random = new Random();
DoubleStream doubleStream = random.doubles(3);
Java8之后,类Random也提供了拓展方法用于生成基础数据类型的stream,上述代码创建了一个含有3个随机数的DoubleStream。
字符串流(Stream of String)
String类型也可以作为生成stream的源,这得益于方法chars()的帮助,此外由于JDK中没有CharStream接口,IntStream也被用来表示字符流(stream
of chars)
IntStream streamOfChars = "2a".chars();
下例中通过正则表达式将a,b,c截取为字符串流。
Stream<String> streamOfString = Pattern.compile(",").spitAsStream("a,b,c");
文件流(Stream of File)
Java NIO类文件允许通过方法lines()生成文本文件的Stream<String>,文本的每一行都会变成stream的一个元素:
Path path = Paths.get("C:\file.txt");
Stream<String> streamOfString = Files.lines(path);
Stream<String> streamWithCharset = Files.lines(path, Charset.forName("utf-8"));
在方法lines()中也可以通过Charset设置文件编码。
引用Stream(Referencing a Stream)
只要调用生成操作(中间操作)就会实例化一个stream并生成一个可获取的引用,但执行终端操作会使得stream无法访问。
以下代码如果不考虑冗长的话将是有效的:
Stream<String> stream = Stream.of("a", "b", "c")
.filter(t -> t.contains("b"));
Optional<String> anyElement = stream.findAny();
但是倘若我们在执行终端操作后重新使用相同的引用,则会不可避免的触发IllegalStateException。
Optional<String> firstElement = stream.findFirst();
IllegalStateException是一个运行时异常(RuntimeException),即编译器将不会提示此错误。因此必须记得JAVA8不允许重复使用stream。因为stream从设计上是用于操作数据源(集合、数组)所生成的元素序列,而不是存储元素。
所以想让上面的代码正常工作就得改为(List可以存储元素):
List<String> elements = Stream.of("a", "b", "c")
.filter(t -> t.contains("b"))
.collect(Collectors.toList());
Optional<String> anyElement = elements.stream().findAny();
Optional<String> firstElement = elements.stream().findFirst();
三、中间操作
Filter(过滤)
过滤流中的某些元素
Stream<T> filter(Predicate<? super T> predicate);
案例
String[] dd = { "a", "b", "c" };
Stream<String> stream = Arrays.stream(dd);
stream.filter(str -> str.equals("a"))
.forEach(System.out::println);//返回字符串为a的值
Map(映射)
接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
案例
//利用map把int转string
Integer[] dd = {1, 2, 3};
Stream<Integer> stream = Arrays.stream(dd);
stream.map(str -> Integer.toString(str))
.forEach(str -> {
System.out.println(str);//1,2,3
});
//利用map把对象里的name参数提取成一个List<String>
List<Emp> list = Arrays.asList(new Emp("a"), new Emp("b"));
List<String> list1 = list.stream()
.map(Emp::getName)
.collect(Collectors.toList());
flatMap
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
案例
List<String> list = Arrays.asList("a,b,c", "1,2,3");
Stream<String> newStream = list.stream().flatMap(s -> {
String[] split = s.split(",");
return Arrays.stream(split);
});
newStream.forEach(System.out::print);//abc123
distinct,sorted,peek,limit,skip
//去重
Stream<T> distinct();
//排序
Stream<T> sorted();
//定制排序,自定义Comparator排序器
Stream<T> sorted(Comparator<? super T> comparator);
//如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
Stream<T> peek(Consumer<? super T> action);
//获取maxSize个元素
Stream<T> limit(long maxSize);
//跳过n个元素
Stream<T> skip(long n);
案例
List<Emp> empList = new ArrayList<>();
empList.add(new Emp("张三", 45, 9000.0));
empList.add(new Emp("李四", 55, 10000.0));
empList.add(new Emp("王五", 20, 1500.0));
empList.add(new Emp("赵六", 25, 2500.0));
empList.add(new Emp("田七", 30, 4000.0));
//获取3个年龄>25岁的员工信息,根据工资排序
List<Emp> emps1 = empList.stream().filter(t -> t.getAge() > 25)
.sorted(Comparator.comparing(Emp::getSalary))
.distinct()
.limit(3)
.collect(Collectors.toList());
emps1.forEach(System.out::println);
//把年龄>30岁的员工工资 x 1.5倍
List<Emp> emps2 = empList.stream().filter(t -> t.getAge() > 30)
.peek(emp -> {
emp.setSalary(emp.getSalary() * 1.5);
})
.collect(Collectors.toList());
emps2.forEach(System.out::println);
//数字从1开始迭代,跳过前5个数再往后获取10个数
List<Integer> nums = Stream.iterate(1, i -> i + 1)
.skip(5)
.limit(10)
.collect(Collectors.toList());
nums.forEach(System.out::println);//输出6,7,8.....14,15
四、终端操作
forEachOrdered和forEach
forEachOrdered按指定的顺序处理流元素,而不管流是顺序的还是并行的。
forEach在并行流中不仅以非确定顺序执行,还可以在不同线程中针对不同元素同时执行。
void forEach(Consumer<? super T> action);
void forEachOrdered(Consumer<? super T> action);
案例
List<String> list = Arrays.asList("a", "b", "c");
//顺序流时都是按顺序操作
list.stream().forEachOrdered(System.out::print);//abc
list.stream().forEach(System.out::print);//abc
//并行流时forEachOrdered能按顺序操作,而forEach不能
list.parallelStream().forEachOrdered(System.out::print);//abc
list.parallelStream().forEach(System.out::print);//不确定
toArray
public final <A> A[] toArray(IntFunction<A[]> generator)
案例
List<String> strs = Arrays.asList("a", "b", "c");
String[] str1 = strs.stream().toArray(str -> new String[strs.size()]);
String[] str2 = strs.stream().toArray(String[]::new);
String[] str3 = strs.toArray(new String[strs.size()]);
Object[] obj1 = strs.stream().toArray();
Object[] obj2 = strs.toArray();
count,max,min
//返回流中元素的总个数
long count();
//返回流中元素最大值
Optional<T> max(Comparator<? super T> comparator);
//返回流中元素最小值
Optional<T> min(Comparator<? super T> comparator);
findFirst,findAny
//返回流中第一个元素
Optional<T> findFirst();
//返回流中的任意元素
Optional<T> findAny();
案例
List<String> list = Arrays.asList("a", "b", "c", "d");
String findFirst = list.stream().findFirst().get();
String findAny = list.stream().findAny().get();
anyMatch,allMatch,noneMatch
//只要流中有一个元素满足该断言则返回true
boolean anyMatch(Predicate<? super T> predicate);
//当流中每个元素都符合该断言时才返回true
boolean allMatch(Predicate<? super T> predicate);
//当流中每个元素都不符合该断言时才返回true
boolean noneMatch(Predicate<? super T> predicate);
案例
List<String> strs = Arrays.asList("a", "a", "a", "a", "b");
boolean aa = strs.stream()
.anyMatch(str -> str.equals("a"));//true
boolean bb = strs.stream()
.allMatch(str -> str.equals("a"));//false
boolean cc = strs.stream()
.noneMatch(str -> str.equals("a"));//false
reduce
reduce
是一种归约操作,将流归约成一个值的操作叫做归约操作,用函数式编程语言的术语来说,这种称为折叠(fold)
①第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
Optional<T> reduce(BinaryOperator<T> accumulator);
案例
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce((x1, x2) -> x1 + x2).get();
等同于 list.stream().reduce(Integer::sum).get();
System.out.println(sum); // 55
②流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
T reduce(T identity, BinaryOperator<T> accumulator);
案例
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println(sum); //65
③在顺序流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。
在并行流(parallelStream)中,我们知道流被fork
join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
顺序流案例:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer i1 = list.stream().reduce(0,
(a1, a2) -> {
System.out.println("stream accumulator: a1:" + a1 + " a2:" + a2);
return a1 - a2;
},
(a1, a2) -> {
System.out.println("stream combiner: b1:" + a1 + " b2:" + a2);
return a1 * a2;
});//顺序流中,粉色部分不会执行
System.out.println(i1); //-55
并行流案例
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer i2 = list.parallelStream().reduce(0,
(a1, a2) -> {
System.out.println("parallelStream accumulator: a1:" + a1 + " a2:" + a2);
return a1 - a2;
},
(a1, a2) -> {
System.out.println("parallelStream combiner: b1:" + a1 + " b2:" + a2);
return a1 * a2;
});//并行流中,粉色部分会执行
System.out.println(i2); //3628800
collect
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);
案例
List<Emp> list = new ArrayList<>();
list.add(new Emp("张三", 45, 9000.1));
list.add(new Emp("李四", 55, 10000.0));
list.add(new Emp("王五", 20, 1500.0));
list.add(new Emp("赵六", 25, 2550.0));
list.add(new Emp("田七", 30, 4100.0));
// 转list
List<String> names1 = list.stream().map(Emp::getName).collect(Collectors.toList());
// 转set
Set<String> names2 = list.stream().map(Emp::getName).collect(Collectors.toSet());
// 转map,需要指定key和value,Function.identity()表示当前的Emp对象本身
Map<String, Emp> map = list.stream().collect(Collectors.toMap(Emp::getName, Function.identity()));
// 计算元素中的个数
Long count = list.stream().collect(Collectors.counting());
Long count1 = (long) list.size();
// 数据求和 summingInt summingLong,summingDouble
Integer sumAges = list.stream().collect(Collectors.summingInt(Emp::getAge));
// 平均值 averagingInt,averagingLong,averagingDouble
Integer aveAge = list.stream().collect(Collectors.averagingInt(Emp::getAge)).intValue();
Double aveSalary = list.stream().collect(Collectors.averagingDouble(Emp::getSalary));
// 综合处理类(包含count,sum,min,max,average)
// summarizingInt,summarizingLong,summarizingDouble
IntSummaryStatistics intSummary = list.stream().collect(Collectors.summarizingInt(Emp::getAge));
// 连接字符串,可以使用重载的方法,加上一些前缀,后缀和中间分隔符
String strEmp1 = list.stream().map(Emp::getName).collect(Collectors.joining(","));
//张三,李四,王五,赵六
String strEmp2 = list.stream().map(Emp::getName).collect(Collectors.joining("-中间的分隔符-", "最前缀*", "&最后缀"));
// maxBy 按照比较器中的比较结果刷选最大值
Optional<Integer> maxAge = list.stream().map(Emp::getAge)
.collect(Collectors.maxBy(Comparator.comparing(Function.identity())));
// 最小值
Optional<Integer> minAge = list.stream().map(Emp::getAge)
.collect(Collectors.minBy(Comparator.comparing(Function.identity())));
System.out.println("max:" + maxAge);
System.out.println("min:" + minAge);
// 归约操作
list.stream().map(Emp::getAge).collect(Collectors.reducing((x, y) -> x + y));
list.stream().map(Emp::getAge).collect(Collectors.reducing(0, (x, y) -> x + y));
// 分操作 groupingBy 根据地址,把原list进行分组
Map<Integer, List<Emp>> mapGroup = list.stream().collect(Collectors.groupingBy(Emp::getAge));
// partitioningBy 分区操作 需要根据类型指定判断分区
Map<Boolean, List<Integer>> partitioningMap = list.stream().map(emp -> emp.getAge())
.collect(Collectors.partitioningBy(emp -> emp > 20));
五、其他
Optional静态类
在java8中,很多的stream的终端操作,都返回了一个Optional<T>对象,这个对象,是用来解决空指针的问题,而产生的一个类。
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
1.get()直接取,如果为null,就返回异常。
2.orElse(T
other)在取这个对象的时候,设置一个默认对象(默认值);如果当前对象为null的时候,就返回默认对象。
3.orElseGet(Supplier<? extends T>
other)跟第二个是一样的,区别只是参数,传入了一个函数式参数。
4.orElseThrow(Supplier<? extends X>
exceptionSupplier)第四个,跟上面表达的是一样的,为null的时候,返回一个特定的异常。
5. isPresent()是对当前的value进行null判断。
案例
Emp emp = new Emp("张三", "上海", "22");
Optional<Emp> op = Optional.ofNullable(emp);
System.out.println(op.get().getAddress());//上海
Optional<Emp> op1 = Optional.ofNullable(null);
System.out.println(op1.orElse(emp).getAddress());//上海
//这里指定了一个默认对象emp,为先创建的一个emp对象,emp对象里的成员变量还没有赋值,所以输出为null
System.out.println(op1.orElseGet(Emp::new).getAddress());
// java.lang.RuntimeException
try {
System.out.println(op1.orElseThrow(RuntimeException::new));
} catch (Exception e) {
e.printStackTrace();
}