java8 lambda表达式的简单介绍
2017-11-30 09:52
274 查看
Lambda表达式的简单介绍
前言
Java 是一流的面向对象语言,除了部分简单数据类型,Java 中的一切都是对象,即使数组也是一种对象,每个类创建的实例也是对象。在 Java 中定义的函数或方法不可能完全独立,也不能将方法作为参数或返回一个方法给实例。 我们总是通过匿名类给方法传递函数功能,以下是旧版的事件监听代码
someObject.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { //Event listener implementation goes here... } });
为了给 Mouse 监听器添加自定义代码,我们定义了一个匿名内部类 MouseAdapter 并创建了它的对象,通过这种方式,我们将一些函数功能传给 addMouseListener 方法。
匿名类型最大的问题就在于其冗余的语法。有人戏称匿名类型导致了“高度问题”(height problem):比如前面
MouseAdapter的例子里的五行代码中仅有一行在做实际工作。
为什么 Java 需要 Lambda 表达式?
在函数式语言中,我们只需要给函数分配变量,并将这个函数作为参数传递给其它函数就可实现特定的功能。而java如前言中所述,不能直接将方法当作一个参数传递。同时匿名内部类又存在诸多不便:语法过于冗余,匿名类中的this和变量名容易使人产生误解,类型载入和实例创建语义不够灵活,无法捕获非
final的局部变量等。
Lambda 表达式的出现为 Java 添加了缺失的函数式编程特点,使我们能将函数当做一等公民看待。
先说说函数式接口
我们将只包含一个抽象方法声明的接口称为函数式接口。(之前它们被称为SAM类型,即单抽象方法类型(Single Abstract Method))。Java SE7中就已存在函数式接口:java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.beans.PropertyChangeListener
@FunctionalInterface public interface Runnable { /** * When an object implemen 4000 ting interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
@FunctionalInterface注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。Java SE 8中增加了一个新的包
java.util.function,它里面包含了很多常用的函数式接口。
下面是stream流中常用到的一些新增的函数式接口:
@FunctionalInterface public interface Function<T, R> { R apply(T t); } @FunctionalInterface public interface Predicate<T> { boolean test(T t); } @FunctionalInterface public interface Consumer<T> { void accept(T t); }
Lambda 表达式
Lambda 表达式是一种匿名函数(虽然并不完全正确),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。它提供了轻量级的语法,从而解决了匿名内部类带来的“高度问题”。 Java 中的 Lambda 表达式通常使用
(argument) -> (body)语法书写,例如:
(arg1, arg2...) -> { body } (type1 arg1, type2 arg2...) -> { body }
以下是一些 Lambda 表达式的例子:
(int a, int b) -> { return a + b; } () -> System.out.println("Hello World"); (String s) -> { System.out.println(s); } () -> 42 () -> { return 3.1415 };
一个 Lambda 表达式可以有零个或多个参数
参数的类型既可以明确声明,也可以根据上下文来推断。例如:
(int a)与
(a)效果相同
所有参数需包含在圆括号内,参数之间用逗号相隔。例如:
(a, b)或
(int a, int b)或
(String a, int b, float c)
空圆括号代表参数集为空。例如:
() -> 42
当只有一个参数,且其类型可推导时,圆括号()可省略。例如:
a -> return a*a
Lambda 表达式的主体可包含零条或多条语句
如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的
返回类型与代码块的返回类型一致,若没有返回则为空
目标类型?
函数式接口的名称并不是lambda表达式的一部分。那么对于给定的lambda表达式,它的类型是什么?答案是:它的类型是由其上下文推导而来。 这就意味着同样的lambda表达式在不同上下文里可以拥有不同的类型:
FileFilter f = (t) -> true; Predicate p = (t) -> true;
第一个lambda表达式
(t) -> true是
FileFilter的实例,而第二个lambda表达式则是
Predicate的实例。
编译器负责推导lambda表达式的类型。它利用lambda表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。lambda表达式只能出现在目标类型为函数式接口的上下文中。
来看几个lambda例子
1.线程初始化 and 事件处理//1.1线程初始化 //旧方法: new Thread(new Runnable() { @Override public void run() { System.out.println("Hello from thread"); } }).start(); //新方法: new Thread(() -> System.out.println("Hello from thread")).start(); //1.2事件处理 //旧方法: button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("The button was clicked using old fashion code!"); } }); //新方法: button.addActionListener( (e) -> { System.out.println("The button was clicked. From Lambda expressions !"); });
2.数组打印
// forEach //旧方法: List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); for(Integer n: list) { System.out.println(n); } //新方法: List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); list.forEach(n -> System.out.println(n)); //使用方法引用 //使用 Java 8 全新的双冒号(::)操作符将一个常规方法转化为 Lambda 表达式 list.forEach(System.out::println);
3.stream流相关使用
//3.1 map //旧方法: List<Integer> list = Arrays.asList(1,2,3,4,5,6,7); for(Integer n : list) { int x = n * n; System.out.println(x); } //新方法: List<Integer> list = Arrays.asList(1,2,3,4,5,6,7); list.stream().map((x) -> x*x).forEach(System.out::println); //3.2 filter //3.3 reduce
方法引用
有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号”::”。 例如:
(n) -> System.out.println(n) System.out::println
方法引用分为4类,常用的是前两种。方法引用也受到访问控制权限的限制,可以通过在引用位置是否能够调用被引用方法来判断。具体分类信息如下:
引用静态方法
ContainingClass::staticMethodName
例子: String::valueOf,对应的Lambda:(s) -> String.valueOf(s)
比较容易理解,和静态方法调用相比,只是把.换为::
引用特定对象的实例方法
containingObject::instanceMethodName
例子: x::toString,对应的Lambda:() -> this.toString()
与引用静态方法相比,都换为实例的而已
引用特定类型的任意对象的实例方法
ContainingType::methodName
例子: String::toString,对应的Lambda:(s) -> s.toString()
太难以理解了。难以理解的东西,也难以维护。建议还是不要用该种方法引用。
实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
引用构造函数
ClassName::new
例子: String::new,对应的Lambda:() -> new String()
构造函数本质上是静态方法,只是方法名字比较特殊。
如何处理异常?
如果函数接口的方法本身没有定义可以被抛出的受检异常,那么在使用该接口时是无法处理可能存在的受检异常的,比如典型的IOException这类:public static void main(String[] args) { new Thread(() -> { // 会提示有未处理异常 throw new Exception(); }).start(); }
我们有两个选择:
1.在Lambda表达式内处理受检异常
2.捕获该受检异常并重新以非受检异常(如RuntimeException)的形式抛出
public void notThrowExce() { new Thread(() -> { try { throw new Exception(); } catch (Exception e) { // 1 内部处理 // 2 抛出不受检异常 throw new RuntimeException(); } }).start(); }
如果函数接口的方法本身定义了可以被抛出的受检异常
@FunctionalInterface public interface WorkerInterface { void doSomeWork() throws Exception; } public class Worker implements WorkerInterface{ private WorkerInterface workerInterface; public Worker(WorkerInterface workerInterface) { this.workerInterface = workerInterface; } @Override public void doSomeWork() throws Exception { workerInterface.doSomeWork(); } }
public void throwExce() throws Exception { // 可以将异常抛出 new Worker(() -> { throw new Exception(); }).doSomeWork(); }
Lambda 表达式与匿名类的区别
1.对于匿名类,关键词this解读为匿名类,而对于 Lambda 表达式,关键词
this解读为写就 Lambda 的外部类。
2.Java 编译器编译 Lambda 表达式时将他们转化为类里面的私有函数
相关文章推荐
- Java 8 之 lambda 表达式简单使用入门实例代码
- [Java 8 Lambda] java.util.stream 简单介绍
- 介绍java lambda表达式
- Java8 Lambda表达式介绍
- Java Lambda 表达式介绍
- java正则表达式简单介绍
- Java之Lambda表达式和Stream类简单例子
- Java8学习教程之lambda表达式语法介绍
- Java 8 之 lambda 表达式简单使用入门实例代码。
- java数据区的一些简单介绍
- oracle的正则表达式(regular expression)简单介绍
- 最简单的lambda 表达式
- oracle的正则表达式(regular expression)简单介绍从oarcle10g开始支持
- java开发工具简单介绍
- JAVA 简单的正则表达式应用
- 简单入门正则表达式 - 第十一章 Java与.Net中的正则表达式应用
- Java的开源项目:简单介绍Log4J的使用
- oracle的正则表达式(regular expression)简单介绍
- 简单介绍Java语言中内存管理的几个技巧
- [转]Java的开源项目:简单介绍Log4J的使用