Lambda表达式
Lambda表达式形式
Java 中表达式一共有五中基本形式 , 具体如下:
public class LambdaExpression { public static void express() { // 1---------------------------------------------- Runnable runnable = () -> System.out.println("Hello World"); // 2---------------------------------------------- ActionListener oneArgument = event -> System.out.println("button clicked"); // 3---------------------------------------------- Runnable multiStatement = () -> { System.out.print("Hello"); System.out.println(" World"); }; // 4---------------------------------------------- BinaryOperatoradd = (x , y) -> x + y; new BinaryOperator () { @Override public Long apply(Long aLong, Long aLong2) { return null; } }; // 5---------------------------------------------- BinaryOperator addOper = (Long x , Long y) -> x + y; }}
- 如1所示Lambda表达式中不含参数,使用空括号()标识没有参数。该Lambda 表达式实现了Runnable接口,该接口也只有一个run方法 , 没有参数,且返回类型为void类型。
- 如2所示的lambda表达式包含且只有一个参数,可以省略参数的括号,lambda表达式主题不仅可以是一个表达式,也可以是一段代码块,使用({})大括号将代码块括起来如3所示
- 如4所示,这是就有必要思考怎样去阅读该Lambda表达式。这行代码并不是将两数字相加,而是创建了一个函数,用来计算两个数字相加的结果,变量add的类型是BinaryOperator类型。
记住一点很重要,Lambda表达式都可以扩写为原始的匿名类形式,所以当你觉得这个Lambda表达式很负责且不易理解的时候,不妨把它扩写为 匿名类 形式来看
闭包
也许之前遇到过这样的问题,当匿名内部类需要所在方法里的变量的时候,必须把变量声明为 final
, 如下所示:
final int a = 1; new Runnable(){ @Override public void run() { System.out.println(a); } };
java8 放松了这一限制,可以不必再把变量声明为 final
,但其实该变量实际上仍然为final
的。索然无需将变量声明为final , 但在lambda表达式中,也无法用作非终态变量, 如果坚持用作非终态变量(即改变变量的值),编译器就会报错。
函数式接口
上面例子中提到了 ActionListener
接口 , 我们看下它的代码:
public interface ActionListener extends EventListener { /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e);}
ActionListener 只有一个抽象方法:actionPerformed,被用来表示行为:接受一个参数,返回空。记住,由于 actionPerformed 定义在一个接口里,因此 abstract 关键字不是必需的。该接口也继承自一个不具有任何方法的父接口:EventListener。
我们把这种接口就叫做函数接口。 JDK 8 中提供了一组常用的核心函数接口:
接口 | 参数 | 返回类型 | 描述 |
---|---|---|---|
Predicate<T> | T | boolean | 用于判别一个对象。比如求一个人是否为男性 |
Consumer<T> | T | void | 用于接收一个对象进行处理但没有返回,比如接收一个人并打印他的名字 |
Function<T, R> | T | R | 转换一个对象为不同类型的对象 |
Supplier<T> | None | T | 提供一个对象 |
UnaryOperator<T> | T | T | 接收对象并返回同类型的对象 |
BinaryOperator<T> | (T, T) | T | 接收两个同类型的对象,并返回一个原类型对象 |
其中 Consumer
与 Supplier
对应,是一个消费者与一个提供者。 Predicate
用于判断对象是否符合某个条件,经常被用来过滤对象。 Function
是将一个对象转换为另一个对象,比如说要装箱或拆箱某个对象。 UnaryOperator
接收和返回同类型对象,一般用于对对象属性进行修改。 BinaryOperator
可以理解为合并对象
集合处理
Stream
Java8 中引入流的概念, 这里的流和我们之前使用的IO流并不一样。 所有继承自Collection的接口都可以转换为Stream 。 假设我们有一个List中存放 Person对象,对象中有 名称 name , 年龄 age 两个字段, 现在求列表中年龄大于20的人数。 不用Stream 我们会这么写:
int count = 0; for (Person person : persons) { if(person.getAge() > 20) { count ++; } }
如果用stream的话,则会简单很多:
long count2 = persons.stream() .filter(person -> person.getAge() > 20) .count();
实际filter中参数转换为了如下函数进行的调用:
Predicate predicate = new Predicate() { @Override public boolean test(Person o) { return o.getAge() > 20 ; } }; long count1 = persons.stream() .filter(predicate) .count();
这是stream的很简单的一个用法,链式调用方法算是一个主流, 这样也更利于理解。
Stream的常用操作
Stream的方法分为两类, 一类叫惰性求值,一类叫及早求值。 判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值 , 如果返回值是Stream 就是惰性求值。 如果调用惰性求值方法,Stream会记录下这个惰性求值方法的过程 , 并没有去计算, 等到及早求值方法后,就会联通一系列惰性求值方法顺序进行计算 , 返回结果。 通用形式为: Stream.惰性求值.惰性求值....惰性求值.及早求值。 整个过程和建造者模式有共通之处, 建造者模式使用一系列的操作配置属性和配置, 最后调用build方法创建对象。
collect(Collectors.toList())
collect(Collectors.toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。可以理解为 Stream 向 Collection 的转换。 注意这边的Collectors.toList() 其实采用了静态导入,看起来十分简洁。
Listcollect = Stream.of("a", "b", "c").collect(Collectors.toList());
map
如果一个函数可以将一种类型的值转换成另外一种类型, map操作就可以使用该函数, 将一个流中的值转换为一个新的流
ListupperCollect = Stream.of("a", "b", "hello").map(item -> item.toUpperCase()).collect(Collectors.toList());
map方法就是接受一个 Function 的匿名函数进行转换:
Functionfunction = new Function () { @Override public String apply(String o) { return o.toUpperCase(); } }; List funcCollect = Stream.of("a", "b", "hello").map(function).collect(Collectors.toList());
filter
遍历集合数据并过滤其中的元素 , 可尝试使用Stream 中提供的新方法 filter
long count2 = persons.stream() .filter(person -> person.getAge() > 20) .count();
filter方法就是接收一个Predicate 的匿名函数类,判断对象是否符合条件 , 符合条件才保留下来。
Predicate predicate = new Predicate() { @Override public boolean test(Person o) { return o.getAge() > 20 ; } }; long count1 = persons.stream() .filter(predicate) .count();
flatMap
flatMap可用Stream替换 , 然后将多个Stream链接成一个Stream。
Listcollect = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4)) .flatMap(item -> item.stream()).collect(Collectors.toList());
flatMap常用的操作就是合并多个 Collection。
max 和 min
Stream 上常用的操作之一是求最大值和最小值, Stream API中的max 和 min操作足以解决这一问题。
Listlist = Arrays.asList(2,4,6,3,9,4); Integer maxInt = list.stream().max(Integer::compareTo).get(); Integer minInt = list.stream().min(Integer::compareTo).get(); System.out.println(maxInt); System.out.println(minInt);
这里有两点需要注意: 1.max 和 min 方法返回的是一个 Optional 对象 , (类似Google Guava里的 Optional对象一样) 。 Optional 对象封装的就是实际的值,可能为空 , 所以保险起见 , 可以先用 isPresent() 方法判断一下, Optional的引入就是为了解决方法返回 null 的问题。 2.Integer::compareTo 也是属于 Java8 引入的新特性 , 叫作 方法引入 (Method References) ,在这边就是 (int1,int2) -> int1.compareTo(int2) 的简写 。可以自己查阅,不做赘述。
reduce
reduce 操作可以实现从一组值中生成一个值。 在上述例子中用到的 count , min 和 max 方法 , 因为常用而被纳入标准库中 。 事实上 这些方法都是reduce 操作。 如下是一个累加的过程:
long l = Stream.of(1, 2, 3, 4 ).reduce(0, (acc, element) -> acc + element).longValue();
注意reduce的第一个参数 , 这是一个初始值 , 0 + 1 + 2+ 3 + 4 = 10; 实际第二个参数是BinaryOperator<T>的匿名内部类 如果累乘为如下形式
BinaryOperatoroperator = new BinaryOperator () { @Override public Integer apply(Integer integer, Integer integer2) { return integer * integer2; } }; long l1 = Stream.of(1, 2, 3, 4).reduce(1, operator).longValue();
结果为1 * 1 * 2 * 3 * 4 = 24;