只要Lambda表达式的声明形式与接口相一致,在不少状况下均可以替换接口。见以下代码html
Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("hi"); } }); t1.start(); Thread t2 = new Thread(() -> System.out.println("hi")); t2.start();
t1与t2完成相同的功能。t2中的Lambda表达式() -> System.out.println("hi")
与Runnable
接口中的方法public abstract void run();
的形式同样:java
下面一个例子中express
String[] arr = {"111","22","3"}; Arrays.sort(arr,new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.length()-o2.length(); } }); Arrays.sort(arr, (o1,o2)->o1.length()-o2.length());
(o1,o2)->o1.length()-o2.length()
的形式与Compartor中public int compare(String o1, String o2)
形式也同样:数组
Lambda表达式在用法上看起来很像匿名内部类,但其并非匿名内部类。好比,在如下代码中,在Lambda表达式中不能得到this。oracle
Thread t1 = new Thread(new Runnable() { public void run() { System.out.println(this);//打印匿名内部类 } }); t1.start(); Thread t2 = new Thread(()->{ //System.out.println(this);//没法编译经过 }); t2.start();
观察以下代码,会发现Lambda表达式中的this与所处环境有关,在这里this是对外部对象的引用。app
class Foo{ Runnable r1 = ()->{ System.out.println(this); }; Runnable r2 = ()->{ System.out.println(this); }; void test(){ r1.run(); r2.run(); } } //测试代码以下 Foo foo = new Foo(); System.out.println(foo); foo.test();
从输出能够看出,输出了三个对象其实是同一个对象。ide
Foo@87aac27 Foo@87aac27 Foo@87aac27
Java8中为Iterable引入了默认实现方法default void forEach(Consumer<? super T> action)
。用法以下:函数
List<String> strs = Arrays.asList("1","222","33"); //List接口间接继承了Iterable接口,因此strs也会有forEach方法。 strs.forEach(e->System.out.println(e)); //将strs中的每一个元素迭代输出
为何能够将Lambda表达式e->System.out.println(e)
做为Consumer<? super T> action
类型的参数。
先看一下Consumer的代码post
@FunctionalInterface public interface Consumer<T> { void accept(T t); //其余代码 }
能够看到void accept(T t)
与e->System.out.println(e)
形式上是一致的,因此能够将该Lambda表达式做为输入参数。
注意:这里使用了@FunctionalInterface
标注该结构为函数式接口。也能够本身建立函数式接口。但要注意函数接口只能有一个抽象方法。
以下代码能够经过:学习
@FunctionalInterface interface MyFuncInterface{ void test(); }
但以下代码却没法编译经过
@FunctionalInterface interface MyFuncInterface{ void test(); void test1(); }
JDK中大量使用了几个经常使用的标准函数接口。以下所示:
public interface Consumer<T> {//无返回值,消费传入的T。可接受e->System.out.println(e)或System.out::println void accept(T t); //其余代码 } public interface Function<T, R> {//将t转化为r。可接受e->Integer.parseInt或Integer::parseInt,将String类型转化为int R apply(T t); //其余代码 } public interface Predicate<T> {//根据传入t判断真假。可接受x->x>3或String::equals(与传入String对象比较,返回True或False) boolean test(T t); //其余代码 } public interface Supplier<T> {//无输入参数,直接获取T。可接受()->Arrays.asList("1","2","3"}或 T get(); }
前面出现的System.out::println
就是方法引用。下面的代码中,strs.forEach的入参类型为Consumer<? super T> action
,
前面已经提到可使用e->System.out.println(e)
做为入参,同时咱们知道System.out.println
方法签名中返回值为void、
无入参也符合要求,因此咱们可使用System.out::println
来替代e->System.out.println(e)
。注意:要使用::
来引用相关
的方法。
···
List
strs.forEach(e->System.out.println(e));
strs.forEach(System.out::println);
···
方法引用不只能够引用jdk中已有类的方法,还能够引用自定义类的相关方法。好比:
class Foo{ <T> void myPrintX(T t) { //必须建立Foo对象才能对非static进行方法引用 System.out.println("x="+t); } static <T> void myPrint(T t) { System.out.println("element="+t); } } //测试代码 List<String> strs = Arrays.asList("1","222","33"); strs.forEach(Foo::myPrint); strs.forEach(new Foo()::myPrintX);
输出结果为
element=1 element=222 element=33 x=1 x=222 x=33
从Java 8起,能够将集合中数据放入流并进行管道式操做。
管道式操做包含3部分:
中间操做产生的仍是流,那么经过filter获得的流还能够继续进行filter。
终端操做产生的就不是流了(多是一个List、Map或int等),对一个流进行终端操做后,就不能在进行任何其余中间操做。
对一个流一旦进行完终端操做,就不能再进行中间操做,运行以下代码
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> stream1 = intList.stream().filter(e->e>3); stream1.forEach(System.out::println); Stream<Integer> stream2 = stream1.filter(e->e>4); stream2.forEach(System.out::println);
会提示stream has already been operated upon or closed
。
Predicate接口(boolean test(T t))的做用是根据传入参数t判断真假。
Function接口(R apply(T t);)的做用是将T类型的t转换成R类型。
观察以下代码:
List<String> strs = Arrays.asList("1","222", null, "33"); Stream<Integer> stream = strs.stream().filter(e -> e != null).map(Integer::parseInt); stream.forEach(e -> System.out.println(1 + e));
输出
2 223 34
其中strs.stream().filter(e->e!=null)
的filter方法声明以下Stream<T> filter(Predicate<? super T> predicate);
,即这里须要一个
Predicate<? super T> predicate
类型的参数。前面能够看到Predicate接口中的方法为boolean test(T t);
,即接受一个t返回
boolean值。e->e!=null
符合这样的要求。
而strs.stream().filter(e->e!=null).map(Integer::parseInt);
中的map方法声明以下
Stream<R> map(Function<? super T, ? extends R> mapper)
即这里须要一个Function<? super T, ? extends R> mapper)
类型的参数。前面能够看到Function接口中的方法为R apply(T t);
,
即接受一个类型为T的元素,将其转换为元素R。在这里实际上就是将String类型元素转化成int类型元素。Integer::parseInt
恰好
符合这种要求。
从刚才的例子中,咱们能够看Function接口的做用能够将一个类型的转换成另一个类型。好比
Student s1 = new Student("zhang san"); String name = s1.getName(); //对应的方法引用是Student::getName()
中Student::getName()至关于Student类型转换成String类型。
以下代码中,一个Course有不少Student(stuList),每一个Student有均可以getName()。如今想要获取该Course中某个学生的姓名。
以往的代码若是使用course.getStuList().get(i).getName()
来获取某个学生的姓名,看起来代码风格当然流畅,然而却没有正确处理:
course1为null,get(i)为null,getName为null的状况。那么必须在整个处理过程编写大量的判断null的代码。
可使用Optional进行改进,即保持了流畅的编码风格,又能够正确处理null。
如下代码中:Optional.ofNullable方法能够将给定值转化为Optional类型(可包含表明给定值的Optional对象,也可包含表明null的Optional对象)
import java.util.ArrayList; import java.util.List; import java.util.Optional; class Student{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Student(String name) { this.name = name; } } class Course{//课程 private String name; private List<Student> stuList; public String getName() { return name; } public void setName(String name) { this.name = name; } public void addStudent(Student... stus) { for (Student student : stus) { stuList.add(student); } } public Student getStu(int i) { return stuList.get(i); } public List<Student> getStuList() { return stuList; } public Course(String name) { this.name = name; stuList = new ArrayList<>(); } } public class TestOptional { public static void main(String[] args) { Course course = new Course("数学"); Student s0 = new Student("s1"); Student s1 = new Student(null); Student s2 = null; course.addStudent(s0, s1, s2); String result = findStudent(course, 0);// orElse,当处理过程当中过程当中有一个null时,即返回orElse中的值 System.out.println("均不为空状况下姓名为:" + result); result = findStudent(course, 1); System.out.println("student的name为null的状况:" + result); result = findStudent(course, 2); System.out.println("student为null的状况:" + result); Course courseNull = null; result = findStudent(courseNull, 3); System.out.println("course为null的状况:" + result); } private static String findStudent(Course course, int i) { Optional<Course> courseNullable = Optional.ofNullable(course); String result = courseNullable.map(e -> e.getStu(i)).map(Student::getName).orElse("查询不到"); return result; } }
注意:
map(e->e.getStu(0))
与map(Student::getName)
形式都可执行。map(e->e.getStu(2)).map(Student::getName)
流畅的编写对应代码。List<String> strs = Arrays.asList("1", "222", null, "33"); IntStream intStream = strs1.stream().filter(e -> e != null).mapToInt(e -> e.length()); intStream.forEach(System.out::println);
mapToInt(e -> e.length())
的mapToInt方法参数类型为ToIntFunction<? super T> mapper
,查询源代码ToIntFunction
包含方法
int applyAsInt(T value);
,即须要一个方法接受T类型输入参数,而后将其转化为int。在这里,e -> e.length()
起到了这个做用。
代码的做用就是要将求得流中每一个非null的字符串的长度,而后放入intStream中。
int[] arr = {1,2,3,4,5}; int x = 0; for (int i = 0; i < arr.length; i++) { x = x + arr[i]; } System.out.println(x);
这段代码每回从数组中取出一个元素,而后与前一个元素相加。最后求的全部元素值的和。这类操做常用,可使用stream中的
reduce方法来简化实现。
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> intStream1 = intList.stream(); Optional<Integer> result = intStream1.reduce((a, b) -> a + b); Integer integer = result.get(); System.out.println(integer);// 15
intStream1的reduce方法入参为BinaryOperator
其继承自接口BiFunction
,内有一方法R apply(T t, U u);
,将传入参t与u进行运算,
而后返回一个结果。(a,b)->a+b就知足如此行事,其中a最开始为流中第1个元素,b为第2个元素,a+b之后再赋给a,而后b为第3个元
素,依次类推。
reduce方法还有另一种形式,能够指定初始值,以下述代码指定迭代开始的初始值为10,即a开始为10,b为流中第1个元素 。而后
将a+b放入a,b为流中第2个元素,而后依次类推。
Integer x = intList.stream().reduce(10, (a, b) -> a + b); System.out.println(x);
输出
25
其中的reduce(10, (a, b) -> a + b)
类型为T reduce(T identity, BinaryOperator<T> accumulator);
,能够看到该方法的返回值由identity的类型决定,
在这里由10来决定,即返回值类型应为Integer。
Supplier接口(提供者)的定义以下
@FunctionalInterface public interface Supplier<T> { T get(); }
能够看到,其经过get方法返回一个对象。咱们能够将Supplier看成一个生成某个对象的工厂。
为了对流进行一些管道操做的实验,且由于流不能反复操做,咱们须要不断生成内部元素彻底相同的流。
以下代码中,经过Supplier<Stream<Integer>> factory = () -> Stream.of(1, 2, 3, 4, 5);
声明Supplier类型变量
factory,经过该factory的get方法就能够不断生成流,实际上就是不断调用() -> Stream.of(1, 2, 3, 4, 5);
。而
这段() -> Stream.of(1, 2, 3, 4, 5);
Lambda表达式形式上是与Supplier标准函数式接口是一致:无入参,有一个返回值。
Supplier<Stream<Integer>> factory = () -> Stream.of(1, 2, 3, 4, 5); Stream<Integer> stream1 = factory.get(); Stream<Integer> stream2 = factory.get(); System.out.println(stream1 == stream2); // false
如何抽取二维数组Integer[][] arr1 = {{1,2},{2,3}}
每一个元素(排除掉重复的元素),即将一、二、3
抽取出来?
可使用flatmap方法。
Integer[][] arr1 = {{1,2},{2,3}}; Stream<Integer[]> t1 = Arrays.stream(arr1);//流中每一个元素是一行(一维数组) Stream<Integer> flatMap = t1.flatMap(Arrays::stream);//扁平化处理后,流中的每个元素是一个Integer flatMap.distinct().forEach(System.out::println); //distinct()排除掉重复的元素
不过这种方法对基本类型数组,如int[][]就不起做用。不知道为什么?
更多参考资料见:
本文使用了几个例子展现了Java 8中经常使用函数式接口在流的管道操做中的应用,Lambda表达式、方法引用与函数式接口之间的关系。但愿你们之后在使用流的管道操做时,能够知其然也知其因此然。
Java学习笔记(第8版) 林信良
Java Tutorial中的Lambda Expressions、Aggregate Operations
Java 8 Stream Tutorial