函数式接口
什么是函数式接口?
函数式接口,@FunctionalInterface,简称FI,简单的说,FI就是指仅含有一个抽象方法的接口,以@Functionalnterface标注,该注解标注与否对函数式接口没有实际的影响, 不过一般还是推荐使用该注解,就像使用@Override
注解一样。
注意,这里的抽象方法指的是该接口自己特有的抽象方法,而不包含它从其上级继承过来的抽象方法,例如:
@FunctionalInterface Interface FI{ abstract judge(int a); abstract equals(); }
上面这个接口尽管含有两个抽象方法,但是它仍然是一个FI,因为equals抽象方法是其从超类Object中继承的(当然这里的“接口继承超类Object”的说法很有争议,但是不妨碍咱们这里拿来理解FI这个概念)
详情扩展阅读:JDK8新特性:函数式接口@FunctionalInterface的使用说明
Java SE 7 中已经存在的函数式接口:
除此之外,Java SE 8中增加了一个新的包:java.util.function
,它里面包含了常用的函数式接口,例如:
Predicate<T>
——接收T
并返回boolean
详情介绍Consumer<T>
——接收T
,不返回值 详情介绍Function<T, R>
——接收T
,返回R
详情介绍Supplier<T>
——提供T
对象(例如工厂),不接收值 详情介绍UnaryOperator<T>
——接收T
对象,返回T
详情介绍BinaryOperator<T>
——接收两个T
,返回T
详情介绍
除了上面的这些基本的函数式接口,我们还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如 IntSupplier
和 LongBinaryOperator
。(我们只为 int
、long
和 double
提供了特化函数式接口,如果需要使用其它原始类型则需要进行类型转换)同样的我们也提供了一些针对多个参数的函数式接口,例如 BiFunction<T, U, R>
,它接收 T
对象和 U
对象,返回 R
对象。
JDK1.8中提供了一些函数式接口如下:
函数式接口 | 函数描述符 | 原始类型特化 |
---|---|---|
Predicate<T> | T -> boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer<T> | T -> void | IntConsumer, LongConsumer, DoubleConsumer |
Function<T,R> | T -> R | IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T> |
Supplier<T> | () -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator<T> | T -> T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | (T,T) -> T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L,R> | (L,R) -> boolean | |
BiConsumer<T,U> | (T,U) -> void | ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> |
BiFunction<T,U,R> | (T,U) -> R | ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U> |
上表中的原始类型特化指的是为了消除自动装箱和拆箱的性能开销,JDK1.8提供的针对基本类型的
Lambda表达式和方法引用
有了函数式接口之后,就可以使用Lambda表达式和方法引用了。其实函数式接口的表中的函数描述符就是Lambda表达式,在函数式接口中Lambda表达式相当于匿名内部类的效果。 举个简单的例子:
public class TestLambda { public static void execute(Runnable runnable) { runnable.run(); } public static void main(String[] args) { //Java8之前 execute(new Runnable() { @Override public void run() { System.out.println("run"); } }); //使用Lambda表达式 execute(() -> System.out.println("run")); } }
可以看到,相比于使用匿名内部类的方式,Lambda表达式可以使用更少的代码但是有更清晰的表述。注意,Lambda表达式也不是完全等价于匿名内部类的, 两者的不同点在于this的指向和本地变量的屏蔽上。
Lambda表达式还可以复合,把几个Lambda表达式串起来使用:
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150).or(a -> “green”.equals(a.getColor()));
上面这行代码把两个Lambda表达式串了起来,含义是选择重量大于150或者绿色的苹果。
Lambda表达式与方法引用和构造器引用
方法引用可以看作Lambda表达式的更简洁的一种表达形式,使用::操作符
静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)
任意类型实例方法的方法引用(例如String的length方法,写作String::length)
现有对象的实例方法的方法引用(例如假设你有一个本地变量localVariable用于存放Variable类型的对象,它支持实例方法getValue,那么可以写成localVariable::getValue)
构造器引用(例如String::new)
举个方法引用的简单的例子:
Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s); //使用方法引用 Function<String, Integer> stringToInteger = Integer::parseInt;
方法引用中还有一种特殊的形式,构造函数引用,假设一个类有一个默认的构造函数,那么使用方法引用的形式为:
Supplier<SomeClass> c1 = SomeClass::new; SomeClass s1 = c1.get(); //等价于 Supplier<SomeClass> c1 = () -> new SomeClass(); SomeClass s1 = c1.get();
如果是构造函数有一个参数的情况:
Function<Integer, SomeClass> c1 = SomeClass::new; SomeClass s1 = c1.apply(100); //等价于 Function<Integer, SomeClass> c1 = i -> new SomeClass(i); SomeClass s1 = c1.apply(100);
详情阅读扩展:Java中Lambda表达式与方法引用和构造器引用
Stream
什么是流?
Stream是java8中新增加的一个特性,被java猿统称为流.
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。Java 的并行 API 演变历程基本如下:
1.0-1.4 中的 java.lang.Thread 5.0 中的 java.util.concurrent 6.0 中的 Phasers 等 7.0 中的 Fork/Join 框架 8.0 中的 Lambda
Stream 的另外一大特点是,数据源本身可以是无限的。
Stream可以分成串行流和并行流,并行流是基于Java7中提供的ForkJoinPool
来进行任务的调度,达到并行的处理的目的。 集合是我们平时在进行Java编程时非常常用的API,使用Stream可以帮助更好的来操作集合。
Stream提供了非常丰富的操作,包括筛选、切片、映射、查找、匹配、归约等等, 这些操作又可以分为中间操作和终端操作,中间操作会返回一个流,因此我们可以使用多个中间操作来作链式的调用,当使用了终端操作之后,那么这个流就被认为是被消费了, 每个流只能有一个终端操作。
Collection的流方法以及流接口中的工厂方法
java.util.Collection<T>:该接口中的默认方法也许是最常使用的生成流的方式
stream() Stream<T>
parallelStream() Stream<T>
java.util.stream.Stream<T>:该接口公开了大量的静态工厂方法,并且带有默认实现。
//筛选后收集到一个List中 List<Apple> vegetarianMenu = apples.stream().filter(Apple::isRed).collect(Collectors.toList()); //筛选加去重 List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4); numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
以上都是一些简单的例子,Stream提供的API非常丰富,可以很好的满足我们的需求。
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中间 | Stream<T> | ||
skip | 中间 | Stream<T> | long | |
limit | 中间 | Stream<T> | long | |
map | 中间 | Stream<R> | Function<T,R> | T -> R |
flatMap | 中间 | Stream<R> | Function<T, Stream<R>> | T -> Stream<R> |
sorted | 中间 | Stream<R> | Comparator<T> | (T,T) -> int |
anyMatch | 终端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 终端 | boolean | Predicate<T> | T -> boolean |
allMatch | 终端 | boolean | Predicate<T> | T -> boolean |
findAny | 终端 | Optional<T> | ||
findFirst | 终端 | Optional<T> | ||
forEach | 终端 | void | Consumer<T> | T -> void |
collect | 终端 | R | Collector<T,A,R> | |
reduce | 终端 | Optional<T> | BinaryOperator<T> | (T,T) -> T |
count | 终端 | long |
与函数式接口类似,Stream也提供了原始类型特化的流,比如说IntStream
等:
//maoToInt转化为一个IntStream int count = list.stream().mapToInt(list::getNumber).sum();
并行流与串行流的区别就在于将stream改成parallelStream,并行流会将流的操作拆分,放到线程池中去执行,但是并不是说使用并行流的性能一定好于串行的流, 恰恰相反,可能大多数时候使用串行流会有更好的性能,这是因为将任务提交到线程池,执行完之后再合并,这些本身都是有不小的开销的。关于并行流其实还有非常多的细节, 这里做一个抛砖引玉,有兴趣的同学可以在网上自行查找一些资料来学习。
parallelStream
parallelStream是什么
parallelStream其实就是一个并行执行的流.它通过默认的ForkJoinPool,可能提高你的多线程任务的速度.
parallelStream的作用
Stream具有平行处理能力,处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作,因此像以下的程式片段:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.parallelStream() .forEach(out::println);
你得到的展示顺序不一定会是1、2、3、4、5、6、7、8、9,而可能是任意的顺序,就forEach()这个操作來讲,如果平行处理时,希望最后顺序是按照原来Stream的数据顺序,那可以调用forEachOrdered()。例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.parallelStream() .forEachOrdered(out::println);
注意:如果forEachOrdered()中间有其他如filter()的中介操作,会试着平行化处理,然后最终forEachOrdered()会以原数据顺序处理,因此,使用forEachOrdered()这类的有序处理,可能会(或完全失去)失去平行化的一些优势,实际上中介操作亦有可能如此,例如sorted()方法。
parallelStream于Stream的区别?
Stream:有序
parallelStream
1、无序的
2、并不是并行流一定性能好,如果简单处理,并行流处理再加上合并时间,未必比stream效果好
3、并行流,有可能会造成数据的丢失,相见如下解释
流可以是无限的、有状态的,可以是顺序的,也可以是并行的。在使用流的时候,你首先需要从一些来源中获取一个流,执行一个或者多个中间操作,然后执行一个最终操作。中间操作包括filter、map、flatMap、peel、distinct、sorted、limit和substream。终止操作包括forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和findAny。 java.util.stream.Collectors是一个非常有用的实用类。该类实现了很多归约操作,例如将流转换成集合和聚合元素。 使其对集合操作更加灵活。
Java8中 Parallel Streams 的陷阱 [译]
JAVA使用并行流(ParallelStream)时要注意的一些问题
小结
以上只是对Java8的新特性进行了一个非常简单的介绍,由于近年来函数式编程很火,Java8也受函数式编程思想的影响,吸收了函数式编程好的地方, 很多新特性都是按照函数式编程来设计的。关于Java8还有非常多的细节没有提到,这些需要我们自行去学习,推荐一本学习Java8非常好的书籍——《Java8实战》, 看完这本书对Java8的使用可以有一个比较清楚的了解。
现在已经是2017年了,据说今年会推出Java9,Java9会推出什么新特性,让我们拭目以待吧。
阅读参考
http://lucida.me/blog/java-8-lambdas-insideout-language-features/
http://www.blogjava.net/landon/archive/2014/11/18/420263.html
未完待补充。。。
未经允许请勿转载:程序喵 » Java8 Lambda 学习笔记