Lambda 表达式式Java8的新特性,它支持了Java简单的“函数式编程”。根据 官方文档表示,当一个匿名类有且只有一个抽象方法时,就可以使用Lambda表达式。而可以作为一个匿名类的前提是存在这样的一个接口或者抽象类,但是该表达式只能够针对有 “@FunctionalInterface” 注解的接口,所以抽象类并不能够使用该表达式。在java中,Lambda表达式是SAM(singleabstract method)类型,SAM类型是一个具有单一抽象方法的接口。
基本语法:(parameters) -> expression 或 (parameters) -> {statements ;}
(参数) -> 带返回值的表达式 / 无返回值的操作(有无返回值都可以按照后面的方式进行表达,两种表达方式的区别在于表达式有多少条语句,当且只有一条时才可以用第一种方式)
注意:
如果没有返回值。只需要在{}写执行语句即可
如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可
如果执行语句只有一行,可以省略{},但是如果有返回值时,情况特殊。
如果函数式接口的方法有返回值,必须给定返回值,如果执行语句只有一句,还可以简写,即省去大括号和return以及最后的“;”号。
形参列表的数据类型会自动推断,只需要参数名称。
举例如下:
例如返回两个值的和:
#第一种方式:表示返回值为 x + y 的值,但是不用写return (x ,y ) -> x + y #第二种方式: (x ,y ) -> {return x + y;}
该表达式只能替代那些 有且只有一个抽象方法并有FunctionalInterface注解的接口。比如Runnable、Comparator等。
再举个例子,使用Lambda替换Runnable匿名内部类
//表达式 “() -> System.out.println("I am running")” 替换了new Runnable(){}这个匿名内部类 new Thread(() -> System.out.println("I am running")).start();
Lambda 表达可以作为参数也可以作为返回值,如果参数或者返回值是一个ASM,则可以使用Lambda表达式替代。
在Java8中,新增了一个包 java.util.function,这个包里有一些专门给新增的API使用的函数接口,例如:
Consumer
Supplier
Predicate
Function
一共有四种类型的方法引用:
类型 | 示例 |
---|---|
类静态方法引用 | ContainingClass::staticMethodName,例如:Person::getName |
某个对象的方法引用 | ContainingObject::instanceMethodName,例如:person::getName |
特定类的任意对象的方法引用 | ContainingType::methodName ,例如:String:: compareToIgnoreCase |
构造方法引用 | ClassName::new ,例如:HashSet::new |
那什么时候可以使用方法引用呢?
就是在可以使用Lambda表达式的地方并且当前有现成的方法能够表达出Lambda表达式中的expression,则可以使用方法引用。它们(Lambda中的expression & 对应的方法)应该具有相同的特征:形参相同,语句表达的功能相同。也就是说,可以使用方法引用来替代FunctionalInterface。以下例子只是用来说明,并没有实质性作用。
public class Test { public static void main(String[] args){ MyLambdaNum myLambdaNum = new MyLambdaNum(MyLambdaNum::add); } } /**SAM **/ interface MyLambda{ int add(int x , int y ); } class MyLambdaNum{ private int myLambdaNum ; public MyLambdaNum(MyLambda myLambda){ myLambdaNum = myLambda.add(2,4); } //目标引用方法 public static int add (int x ,int y ){ return x + y ; } }
Java8 中,在接口中引入了default方法。而default方法就是在接口中用default修饰的方法,并且含有方法体。那为什么需要default方法呢?default方法是为了解决在不破坏java现有实现架构的情况下能往接口里增加新方法,即优化接口的同时,避免跟现有实现架构的兼容问题。
public interface MyDefault{ default void doSomething(){ System.out.println("myDefault"); } } public class MyImplement implements MyDefault{ }
实现了含有default方法接口的类不用再实现default方法了,那如果一个类实现了两个接口,并且这两个接口里含有相同的default方法,那实现类在调用的时候,会调用哪一个类呢?答案是:这种情况是编译不通过的,为了解决这种问题,则实现类必须对相同的方法进行重写。在进行调用的时候,则会调用实现类复写的方法。如果在实现类中需要调用指定父接口的方法,则可以用 指定父接口名.super.方法名 进行调用。
stream就是 Java8 提供给我们的对于元素集合统一、快速、并行操作的一种方式,它支持顺序和并行对元素集合进行批量操作。它提供了一种操作大数据接口,让数据操作更容易和更快 。使用stream,我们能够对collection的元素进行过滤、映射、排序、去重等许多操作。
在Java8中,新增了一个包java.util.stramjava.util.stream,使我们能够使用Java8集合类库执行类似filter/map/reduce的批量操作。批量数据操作有串行(在当前线程上)和并行(使用多线程)两种操作模式,因此我们可以更加高效地利用底层平台的并行特性。
//串行操作 Stream stream = lists.stream(); //并行操作 Stream parallelStream=persons.parallelStream();
1.通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法)
Random random=new Random(); Stream randomNumbers=Stream.generate(random::nextInt);
2.通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法)–stream(),把一个Collection对象转换成Stream。如:list.stream();则可以获得一个Stream对象。
一般情况下,我们都使用Collection接口的 .stream()方法得到stream.
Stream具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法, “流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话, 必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值, 如果是Stream则是中间方法,否则是终点方法
中间操作
中间操作用来描述在数据流之上执行的转换操作(可以理解为一种映射操作)。filter() 和 map()是不错的中间操作的例子,它们的返回值是Stream类型,因此可以允许链式执行多个中间操作。
filter 排除所有不满足条件的元素,具体条件通过Predicate接口来定义;
map 执行元素的映射转换,具体的映射方式使用Function接口定义;
flatMap 通过另外一种 Stream接口将每个流元素转换成零个或者更多流元素
peek 对遇到的每个流元素执行一些操作。
distinct 根据流元素的equals(..)结果排除所有重复的元素
sorted 使后续操作中的流元素强制按Comparator定义的比较逻辑排列。
limit 使后续操作只能看到有限数量的元素。
substream 使后续操作只能看到某个范围内的元素(使用索引)。
//filter Stream personsOver18 =persons.stream().filter(p ->p.getAge()>18); //map 转换 Stream map = persons.stream().filter(p -> p.getAge() > 18).map(person -> new Adult(person));
终结操作
中间方法得到的是一个stream,若想把它转为新的集合,则需要使用终点方法。数据流的处理过程包含以下几个步骤:
从某个数据源头获取到数据流;
执行像filter,map等等这样的一个或者多个中间操作;
执行一个终结操作.
注意:终结操作必须是最后一个在数据流上执行的操作。一旦执行了终结操作,数据流就“消耗完了”,不可再用了。
reducers ,如reduce(..), count(..), findAny(..), findFirst(..),可以终结数据流处理过程。根据意图,终结操作可以是“短路”操作(不用完整的遍历所有数据流)。例如,findFirst(..)在一遇到匹配的元素就会马上终结数据流的处理过程。
collectors,就像其名字表示的,用来把处理过的元素收集到一个结果集中。
forEach 对数据流中的每一个元素执行某个操作。
iterators ,如果上面的操作都不能满足我们的需求,那么还是采用iterators这种传统的集合操作方式。
count,可使流的结果最终统计,返回int
collect,收集最终结果。
收集器
虽然抽象数据流本质上是连续的,而且我们可以定义数据流上的操作,但是要获得最终的结果,我们需要以某种方式收集到数据。数据流API提供了一些所谓的“终结”操作,而collect()方法就是终结操作的其中一个,它使我们能够收集结果数据。
Stream stream = ...; List<T> lists = stream.collect(T,List(){...}); /************************分界线**************************/ //由于已经提供了Collectors工具类,则不需要自己实现一个Collector了 List<T> lists = stream.collect(Collectors.toList()); //或者 List<T> lists = stream.collect(Collectors.toCollection(ArrayList::new));
并行和串行
其实并行的原理就是把数据分成几个部分,然后各个部分新起一个线程进行处理,最终再把结果合在一起,并且不需要我们来处理并发问题。如果是多核机器,理论上会更快
参考资料:
https://blog.csdn.net/wwwsssaaaddd/article/details/24212693
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
https://blog.csdn.net/zymx14/article/details/70175746