最经在学习Java8的新特性,其中就涉及到了lambad表达式和Stream流,用过之后才发现以前需要多行代码才能搞定的功能,现在只需一行,非常的干净和整洁,但我感觉也有一个缺点,当比较复杂的功能用lambad实现后,出现问题后很难调试,特此,请诸位斟酌使用!
(一)初体验
public class Demo1 {
public static void main(String[] args) {
int[] nums = {2,3,4,5,6,7,1,8,9,10};
// 这里是先把int数组转成一个流在进行操作
int asInt = IntStream.of(nums).min().getAsInt();
System.out.println(asInt);
// Lambda实现启动线程
new Thread(() -> System.out.println(12)).start();
Runnable runnable = () -> System.out.println(12);
new A(() -> System.out.println(12));
// 返回的是一个函数接口
G g = () -> System.out.println(1);
g.a();
}
}
interface G {
void a();
}
class A {
public A(G g) {
}
}
// Lambda是一个匿名函数,返回一个实现了指定接口的对象实例
Consumer<String> consumer = s -> System.out.println(12);// s是参数,右边是执行体
public class Demo2 {
public static void main(String[] args) {
ADemo2 x1 = i -> i * 1;
System.out.println(x1.add(2));
ADemo2 x2 = (i) -> i * 2;
System.out.println(x2.add(2));
ADemo2 x3 = (int i) -> i * 3;
System.out.println(x3.add(2));
ADemo2 x4 = (i) -> {
return i * 4;
};
System.out.println(x4.add(2));
int add = x4.add(1, 2);
System.out.println(add);
}
}
@FunctionalInterface // 此注解表示这个接口是一个函数接口,只能有一个方法
interface ADemo2{
int add(int i);
/** Java8后的新特性, 实现此接口的类不用重写此方法。案例:List接口*/
default int add(int i , int b) {
return i * b;
}
}
(二)函数式编程
1、函数接口
就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。且Java8新增了很多功能不一的函数接口。
Function<T, R> 、Consumer<T>、Supplier<T>、Predicate<T>
public class Demo3 {
public static void main(String[] args) {
// Lambda表达式只需要知道输入是什么,输出是什么这里是输入是Integer,输出是String
Function<Integer,String> func1 = i -> (i * 2) + " ";
// i -> (i * 2) + " "就相当于apply方法的具体实现
String apply = func1.apply(10000);
// 输出 20000
System.out.println(apply);
Function<Integer,Integer> func2 = i -> i * 10;
Function<Integer,String> func3 = i -> i - 1 + " ";
// 先处理参数,再对返回值使用操作after进行处理.func3即after
String apply1 = func2.andThen(func3).apply(5);
// 输出 49
System.out.println(apply1);
Function<Integer,Integer> func4 = i -> i * 10 - 1;
// 和andThen刚好相反:先使用操作before处理参数,再对返回值进行处理。func4即before
Integer apply2 = func2.compose(func4).apply(5);
// 输出 490
System.out.println(apply2);
// 只有入参,没有出参
Consumer<String> consumer= p -> System.out.println(p);
consumer.accept("ZX");
// 只有出参,没有入参
Supplier<String> supplier = () -> "ZX";
System.out.println(supplier.get());
// 有入参和出参,出参为Boolean
Predicate<Integer> predicate = e -> e > 100;
System.out.println(predicate.test(1000));
}
}
Java8的其他函数
接口 | 输入参数 | 返回类型 | 说明 |
Predicate<T> | T | boolean | 断言 |
Consumer<T> | T | / | 消费一个数据 |
Function<T,R> | T | R | 输入T输出R的函数 |
Supplier<T> | / | T | 提供一个数据 |
UnaryOperator<T> | T | T | 一元函数(输入输出类型相同) |
BiFunction<T,U,R> | (T,U) | R | 2个输入的函数 |
BinaryOperator<T> | (T,T) | T | 二元函数(输入输出类型相同) |
2、方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式。
public class Demo4 {
public static void main(String[] args) {
// s是参数,右边是执行体
Consumer<String> consumer1 = s -> System.out.println(12);
// 当只有一个函数调用,并且使用的参数名是一样的话,可缩写成方法引用的方式
Consumer<String> consumer2 = System.out::println;
// 静态调用(调用static方法)
Function<String,String> function1 = ADemo4::add;
function1.apply("ADemo4::add");
// 实例调用
ADemo4 aDemo4 = new ADemo4();
Function<Integer,Integer> function2 = aDemo4::add;
function2.apply(2);
// 构造调用
Supplier<ADemo4> supplier = ADemo4::new;
supplier.get();
}
}
class ADemo4{
public ADemo4() {
}
public int add(int i) {
System.out.println("测试");
return i;
}
public static String add(String s) {
System.out.println(s);
return "测试";
}
private String name;
/** 默认会把当前实例传入到非静态方法,参数名为this,位置是第一个 */
public void getName(ADemo4 this,String name){
this.name = name;
}
}
3、类型推断
可通过调用方法的上下文来推断类型参数的目标类型。
public class Demo6 {
public static void main(String[] args) {
ADemo6 aDemo6 = (x, y) -> x + y;
System.out.println(aDemo6.add(1, 2));
List<String> list = Arrays.asList("zhan", "san", "li", "s");
Collections.sort(list, (e1, e2) -> e1.length() - e2.length() );
// 另一种写法
Collections.sort(list, Comparator.comparingInt(String::length));
}
}
interface ADemo6 {
int add (int x, int y);
}
4、级联表达式和柯里化
/**
* 级联表达式和柯里化
* 柯里化:把多个参数的函数转为只有一个函数的参数
* 柯里化的目的:函数标准化
*/
public class Demo5 {
public static void main(String[] args) {
// 实现了x+y的级联表达式
Function<Integer,Function<Integer,Integer>> fun1 = x -> y -> x+y;
System.out.println(fun1.apply(3).apply(4));
Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x -> y -> z -> x+y+z;
System.out.println(fun2.apply(4).apply(5).apply(6));
}
}
(三)Stream流编程
Stream是一个高级的迭代器,不是一个集合,不会存储数据,它关注的是如何高效的处理数据。
/**
* 外部迭代和内部迭代
* 中间操作/终止操作和惰性求值
*/
public class Demo1 {
public static void main(String[] args) {
int[] i = {1,2,3,4};
int temp = 0;
// 外部迭代
for (int s : i) {
temp += s;
}
System.out.println(temp);
// 使用Stream的内部迭代
// map就是中间操作(返回Stream操作)
// sum就是终止操作
int sum = IntStream.of(i).map(s -> s * 2).sum();
System.out.println(sum);
System.out.println("惰性求值就是终止操作没有调用的情况下,中间操作不会执行");
IntStream.of(i).map(Demo1::doubleNum);
}
public static int doubleNum(int i) {
System.out.println("✖️2");
return i * 2;
}
}
1、Stream流 - 创建
集合:Collection.stream/parallelStream
数组:Arrays.stream
数字Stream:IntStream/LongStream.range/rangeCloed、Random.ints/longs/doubles
自己创建:Stream.generate/iterate
public class Demo2 {
public static void main(String[] args) {
// 集合创建
List<Integer> list = new ArrayList<>();
list.stream();
list.parallelStream(); // 并行流,可能产生线程安全问题
// 数组创建
Arrays.stream(new int[]{1, 2, 3});
// 数字流创建
IntStream.of(1, 2, 3);
// - 不包含10
IntStream.range(1, 10);
// - 包含最10
IntStream.rangeClosed(1, 10);
// Random创建
new Random().ints().limit(10);
// 自己创建
Random random = new Random();
Stream.generate(() -> random.nextInt()).limit(20);
}
}
2、Stream流 - 中间操作
无状态操作:map/mapToxxx、flatMap/flatMapToxxx、filter、peek
有状态操作:distinct、sorted、limit/skip
注:有状态是需要依赖其他元素,无状态则不需要
public class Demo3 {
public static void main(String[] args) {
String str = "my name is 001";
// 输出每个单词的长度
// map,参数为一个函数,在这个函数中,处理集合中的元素,并将处理后的结果返回。
Stream.of(str.split(" ")).map(s -> s.length()).forEach(System.out::println);
// flatMap,处理完后必须返回Stream
// intStream/longStream 并不是Stream的子类,所以要进行装箱,转成Stream
Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed()).forEach(System.out::println);
// filter,对list中的数据进行过滤
List<Integer> list1 = Lists.newArrayList(1, 2, 3, 4);
list1.stream().filter(s -> s > 2).forEach(System.out::println);
// peek用于debug
Stream.of(str.split(" ")).peek(System.out::println).forEach(System.out::println);
// distinct,对list2去重
List<Integer> list2 = Lists.newArrayList(1, 2, 2, 4);
list2.stream().distinct().forEach(System.out::println);
// sorted,排序
List<Integer> list3 = Lists.newArrayList(2, 1, 3, 4);
list3 = list3.stream().sorted().collect(Collectors.toList());
list3 = list3.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
list3 = list3.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());
list3 = list3.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());
// limit,打印1 - 4
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.limit(4)
.forEach(i -> System.out.print(i + " "));
// skip,打印3 - 10
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.skip(2)
.forEach(i -> System.out.print(i + " "));
}
}
Stream peek 与 map的区别
首先看定义
Stream<T> peek(Consumer<? super T> action);
peek方法接收一个Consumer函数。Consumer无返回值,该方法返回类型为void。
Consumer<Integer> c = i -> System.out.println("hello" + i);
而map方法的入参为 Function。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Function 既有入参也有出参。
Function<Integer,String> f = x -> {return "hello" + i;};
总结:peek接收一个没有返回值Consumer函数,可以做一些输出,外部处理等。map接收一个有返回值的Function,之后Stream的泛型类型将转换为map参数表达式返回的类型。
3、Stream流 - 终止操作
非短路操作:forEach/forEachOrdered、collect/toArray、reduce、min/max/ount
短路操作:findFirst/findny、allMatch/anyMatch/noneMatch
短路操作,得到任何一个数据流就结束了。
public class Demo4 {
public static void main(String[] args) {
String str = "my name is 007";
// 使用并行流
str.chars().parallel().forEach(i -> System.out.print((char) i));
System.out.println();
// 使用 forEachOrdered 保证顺序
str.chars().parallel().forEachOrdered(i -> System.out.print((char) i));
// 转成list
List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
System.out.println(list);
// 使用 reduce 拼接字符串
Optional<String> letters = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "|" + s2);
System.out.println(letters.orElse(""));
// 带初始化值的reduce
String reduce = Stream.of(str.split(" ")).reduce("", (s1, s2) -> s1 + "|" + s2);
System.out.println(reduce);
// 计算所有单词总长度
Integer length = Stream.of(str.split(" ")).map(s -> s.length()).reduce(0, (s1, s2) -> s1 + s2);
System.out.println(length);
// max 的使用
Optional<String> max = Stream.of(str.split(" ")).max((s1, s2) -> s1.length() - s2.length());
System.out.println(max.get());
List<String> strs = Arrays.asList("d", "b", "a", "c", "a");
// 返回集合的第一个对象
Optional<String> findFirst = strs.parallelStream().filter(e -> !e.equals("a")).findFirst();
// 返回这个集合中,取到的任何一个对象
Optional<String> findAny = strs.parallelStream().filter(e -> !e.equals("a")).findAny();
// 全部满足条件才返回true,否则返回false
boolean allMatch = Stream.of(1, 2, 3, 4).allMatch(e -> e > 0);
// 只要有一个满足条件则返回true,否则返回false。
boolean anyMatch = Stream.of(1, 2, 3, 4).anyMatch(e -> e > 3);
// 全部不满足条件则返回true,否则返回false
boolean noneMatch = Stream.of(1, 2, 3, 4, 5).noneMatch(e -> e > 10);
}
}
4、并行流
public class Demo5 {
public static void main(String[] args) {
// 调用parallel 产生一个并行流
IntStream.range(1, 100).parallel().peek(Demo5::debug).count();
// 现在要实现一个这样的效果: 先并行,再串行
// 多次调用 parallel / sequential, 以最后一次调用为准.
IntStream.range(1, 100)
// 调用parallel产生并行流
.parallel().peek(Demo5::debug)
// 调用sequential 产生串行流
.sequential().peek(Demo5::debug2)
.count();
// 并行流使用的线程池: ForkJoinPool.commonPool
// 默认的线程数是 当前机器的cpu个数
// 使用这个属性可以修改默认的线程数
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
IntStream.range(1, 100).parallel().peek(Demo5::debug).count();
// 使用自己的线程池, 不使用默认线程池, 防止任务被阻塞
// 线程名字 : ForkJoinPool-1
ForkJoinPool pool = new ForkJoinPool(20);
pool.submit(() -> IntStream.range(1, 100).parallel().peek(Demo5::debug).count());
pool.shutdown();
synchronized (pool) {
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void debug(int i) {
System.out.println(Thread.currentThread().getName() + " debug " + i);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void debug2(int i) {
System.err.println("debug2 " + i);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5、收集器
public class Demo {
public static void main(String[] args) {
// 返回list或set
List<Integer> collect = students.stream().map(Student::getAge).collect(Collectors.toList());
Set<Integer> collect1 = students.stream().map(Student::getAge).collect(Collectors.toSet());
// 返回指定的
TreeSet<Integer> collect2 = students.stream().map(Student::getAge).collect(Collectors.toCollection(TreeSet::new));
// 将所有的name拼接成字符串,以逗号分割
String joined = students.stream().map(Student::getName).collect(Collectors.joining(", "));
// 统计年龄
int total = students.stream().collect(Collectors.summingInt(Student::getAge));
// 根据boolean值进行分组
Map<Boolean, List<Student>> collect3 = students.stream().collect(Collectors.partitioningBy(s -> s.getGender() == Gender.MALE));
// 按班级分组
Map<Grade, List<Student>> collect4 = students.stream().collect(Collectors.groupingBy(Student::getGrade));
// 统计每个班级学生个数
Map<Grade, Long> collect5 = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
// 统计每个班级学生年龄 summingXXX、平均值 averagingXXX
Map<Grade, Long> collect6 = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.summingLong(Student::getAge)));
// 两个List合并成一个List
List<Student> studentList = collect.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
}
6、运行机制
/**
* 验证stream运行机制
* 1. 所有操作是链式调用, 一个元素只迭代一次
* 2. 每一个中间操作返回一个新的流. 流里面有一个属性sourceStage指向同一个 地方,就是Head
* 3. Head->nextStage(peek)->nextStage(filter)->... -> null
* 4. 有状态操作会把无状态操作阶段,单独处理
* 5. 并行环境下, 有状态的中间操作不一定能并行操作.
* 6. parallel/ sequetial 这2个操作也是中间操作(也是返回stream),但是他们不创建流, 他们只修改 Head的并行标志
*/
public static void main(String[] args) {
Random random = new Random();
// 随机产生数据
Stream<Integer> stream = Stream.generate(() -> random.nextInt())
// 产生500个 ( 无限流需要短路操作. )
.limit(500)
// 第1个无状态操作
.peek(s -> print("peek: " + s))
// 第2个无状态操作
.filter(s -> {print("filter: " + s);return s > 1000000;})
// 有状态操作
.sorted((i1, i2) -> {print("排序: " + i1 + ", " + i2);return i1.compareTo(i2);})
// 又一个无状态操作
.peek(s -> {print("peek2: " + s);}).parallel();
// 终止操作
stream.count();
}
参考:
java8 stream接口 终端操作 min,max,findFirst,findAny操作_葵花下的獾的博客-优快云博客_stream().max