java8 lambda表达式的简单介绍

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 implementing 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) -> trueFileFilter的实例,而第二个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 表达式时将他们转化为类里面的私有函数

猜你喜欢

转载自blog.csdn.net/wyj_0926/article/details/78672538
今日推荐