参考oracle官网:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
目录
函数式编程、lambda
双冒号
双冒号调用静态方法
双冒号调用一个对象的实例方法
双冒号调用 指定类型的对象 的实例方法。
双冒号调用构造方法
"><?extends T>
Stream
先看一些接口
生成流
中间 *** 作
收集 *** 作
reduce和并行流
函数式编程、lambda
函数式编程,可以将函数作为方法参数传入另一个函数,也能作为函数的返回值返回。
以List.foreach为例
foreach方法参数是Consumer接口,可以用匿名内部类作参数。
Listlist = new ArrayList(); list.forEach(new Consumer () { public void accept(String s) { System.out.println(s); } });
官网说法,如果您的匿名类的实现非常简单,例如仅包含一种方法的接口,那么匿名类的语法似乎笨拙且不清楚。在这些情况下,您通常会尝试将功能传递为另一个方法的参数,例如在某人点击按钮时应采取的 *** 作。lambda表达式使您可以执行此 *** 作,以将功能视为方法参数,或代码为数据。匿名类,向您展示了如何在不给出名称的情况下实现基类。虽然这通常比命名类更简洁,但对于只有一种方法的课程,即使是一个匿名的课程似乎有点过度和繁琐。Lambda表达式让您更加紧凑地表达单方法类的实例。
lambda简化了只有一个抽象方法的接口。当一个接口只有一个方法,可以用lambda表达式实现,代码更简洁。
Listlist = new ArrayList(); //(参数)->{} list.forEach(a-> {System.out.println(a);}); //只有一个语句,省去{} list.forEach(a-> System.out.println(a));
如果接口方法需要返回值
FutureTaskfutureTask = new FutureTask(()->{return "";}); FutureTask futureTask2 = new FutureTask(() -> ""); new Thread(futureTask).start();
在Lambda表达式中能访问域外的局部非final变量、但不能修改Lambda域外的局部非final变量。因为在Lambda表达式中,Lambda域外的局部非final变量会在编译的时候,会被隐式地当做final变量来处理。
访问lambda外变量可以。
String a = ""; list.forEach(l -> System.out.println(a));
修改lambda外变量不行,这样编译错误,提示把变量改成final数组 或Automic ,然后再修改
String a = ""; list.forEach(l -> { a="12"; System.out.println(a); });双冒号
参考官网,方法引用,https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
当lambda表达式中的内容,仅仅是调用另一个线程的方法,可以用双冒号继续简化代码。有四种用法。但注意,一旦使用双冒号,lambda中的参数一定要被使用、并且只能被用一次,否则编译错误。
定义一个类试一试。
static class Fruit { String name; public Fruit(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void hi(Fruit fruit) { System.out.println(fruit.getName()); } public static void hello() { } }双冒号调用静态方法
- 注意静态方法 hi(Fruit f) 是有参数的,foreach把遍历的元素作参数 传给hi()。等同于 list.forEach(a->Fruit.hi(a)); 如果hi方法没有参数、或参数不是一个Fruit,会编译错误
- 有意思的地方,list.forEach(Fruit::hello)编译错误,list.forEach(a->Fruit.hello())编译通过,这说明双冒号调用静态方法,必须要把接口参数 传入静态方法,不能不使用接口参数。但lamda表达式很明显用不用参数不影响
Fruit fruit1 = new Fruit("1"); Fruit fruit2 = new Fruit("2"); Fruit fruit3 = new Fruit("3"); List双冒号调用一个对象的实例方法list = new ArrayList () {{ add(fruit1); add(fruit2); add(fruit3); }}; list.forEach(Fruit::hi);//编译通过 list.forEach(Fruit::hello);//编译错误 list.forEach(a->Fruit.hello());//编译通过
把list每个元素元素打印。
//双冒号,调用实例方法 list.forEach(System.out::print);
out是System类的类变量,是个对象,print实际上是调用out对象的实例方法。
双冒号调用 指定类型的对象 的实例方法。
- 一开始说了,使用双冒号,lambda接口参数必须要被使用。getName方法虽然没参数,但实际上接 把接口参数作为 调用方,lambda接口参数是 list中的一个元素,调用这个元素对象的 getName方法。如果这里是setName肯定错误,因为setName方法的参数没有,接口参数只能用一次,被用做了调用方
list.forEach(Fruit::getName); list.forEach(a->a.getName());//相当于
因为foreach只能消费一个参数,再举个消费两个参数 例子。
加一个静态方法,意思是执行BiFunction,把参数a,b传入BiFunction,得到一个结果。
public static String print(String a, String b, BiFunctionbiFunction){ String apply = biFunction.apply(a, b); return apply; }
BiFunction接口方法参数有两个
public interface BiFunction{ R apply(T t, U u);
执行结果打印出“HelloWorld”,String的concat实例方法参数只有一个,BiFunction接口方法参数有两个,为什么能使用?
Fruit.print("Hello","World",String::concat);
上面语句等价于,看到这应该明白了,(a,b)->a.concat(b) 简写成了 String::concat。接口参数a和b,第一个参数a作为了函数调用方,a调用自己concat,第二个接口参数作为concat的参数。
Fruit.print("hello","world",(a,b)->a.concat(b));
所以在用 双冒号 调用 对象的实例方法时,一定要理解 第一个lambda接口参数被用做了 实例方法调用方,其他接口参数 会被用作 实例方法的参数。
再举个例子,跟上面意思一样
Stream双冒号调用构造方法stream = Stream.of("a", "b", "c"); stream.reduce((a,b)->a.concat(b)); stream.reduce(String::concat);
构造方法也一样,一定要使用接口参数,
假设Fruit构造方法传参是Fruit
public Fruit(Fruit name) { }
就能这么用
Fruit fruit1 = new Fruit("1"); Fruit fruit2 = new Fruit("2"); Fruit fruit3 = new Fruit("3"); List<?extends T> super T>list = new ArrayList () {{ add(fruit1); add(fruit2); add(fruit3); }}; list.forEach(Fruit::new);
参考 中 super 怎么 理解?与 extends 有何不同? - 知乎">Java 泛型 super T> 中 super 怎么 理解?与 extends 有何不同? - 知乎
假设Fruit是基类,Apple、Banana是子类。
如果没有 extends Fruit>, List
List extends Fruit> list 代表list中的元素是Fruit的子类,好处是让list既可以指向 apple集合,也能指向Banana集合。
List extends Fruit> list = new ArrayList();
list = new ArrayList
extends Furit>的副作用,list不能add元素,因为你不知道list指向的是apple集合还是banana。
extends Furit>只能从中get元素,拿到的是Fruit类型或者Fruit的父类。
List super Fruit> list,代表集合中的元素是Fruit的父类,副作用是list只能add Fruit及其子类,get只能拿到Object类型。
Stream参考 java8 Stream的实现原理 (从零开始实现一个stream流) - 小熊餐馆 - 博客园
Java8的Stream流详解_长风-CSDN博客_java stream流 *** 作
stream更方便的处理合数据。
先看一些接口只能举一些例子。
Function,入参一个泛型,出参另一个泛型,类似于 y = F(x)
@FunctionalInterface public interface Function{ R apply(T t); }
BiFunction 类似于 z = F(x,y)
@FunctionalInterface public interface BiFunction{ R apply(T t, U u);
条件判断
@FunctionalInterface public interface Predicate{ boolean test(T t);
数据容器
public final class Optional{ private static final Optional> EMPTY = new Optional<>(); private final T value;
stream接口定义挺清晰,Stream接口继承baseStream,baseStream还有其他子类,IntStream、LongStream、DoubleStream都差不多,Stream流中的元素是泛型,IntStream里元素是Integer。
生成流Collection接口的stream()或parallelStream()方法
静态的Stream.of()、Stream.empty()方法
Arrays.stream(array, from, to)
静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
Pattern接口的splitAsStream(input)方法
静态的Files.lines(path)、Files.lines(path, charSet)方法
静态的Stream.concat()方法将两个流连接起来
filter(Predicate)将结果为false的元素过滤掉
map(fun)转换元素的值,可以用方法引元或者lambda表达式
flatMap(fun)若元素是流,将流摊平为正常元素,再进行元素转换
limit(n)保留前n个元素
skip(n)跳过前n个元素
distinct()剔除重复元素
sorted()将Comparable元素的流排序
sorted(Comparator)将流元素按Comparator排序
peek(fun)流不变,但会把每个元素传入fun执行,可以用作调试
iterator()
forEach(fun)
forEachOrdered(fun)
可以应用在并行流上以保持元素顺序
toArray()
toArray(T[] :: new)
返回正确的元素类型
collect(Collector)
collect(fun1, fun2, fun3)
fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来
Optional
Streamstream = Stream.of(3, 6, 5, 8, 7, 4); Optional reduce = stream.reduce((a, b) -> a + b);
T reduce(T identity, BinaryOperator
Streamstream = Stream.of(3, 6, 5, 8, 7, 4); Integer reduce = stream.reduce(1, (a, b) -> a + b);
最后, U reduce(U identity,
BiFunction accumulator,
BinaryOperator combiner);需要用并行流才有效果。
流可以通过stream.parallel()设置并行,sequential设置串行。设置为并行后,会用多线程去执行。
流分为多个线程执行,就涉及到了多个线程结果的合并。
Streamstream = Stream.of(3, 6, 5, 8, 7, 4); stream.parallel().reduce(0, (a, b) -> a + b, (c, d) -> c + d);
第三个参数就是 combiner 怎么合并两个线程的结果,上面的代码是把线程结果相加,当然根据业务的需要对 两个线程结果合并,比如要求 两个线程要乘2,stream.parallel().reduce(0, (a, b) -> a + b, (c, d) -> 2 * (c + d)); 这样会把两个线程结果相加后成2,再继续和剩下的线程结果计算。
并行流的代码简单debug了下,大概是跟ForkJoinTask有关。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)