zoukankan      html  css  js  c++  java
  • java语言中的匿名类与lambda表达式介绍与总结 (Anonymous Classes and Lambda Expressions)

    2017/6/30

    转载写明出处:http://www.cnblogs.com/daren-lin/p/anonymous-classes-and-lambda-expressions-in-java.html

    本来在查看官方文档中的collection介绍,介绍到如何遍历(traverse)一个容器时提到一个方法是聚合操作(Aggregate Operations),感觉这个写法比较简洁,而且从来没接触过,于是进一步了解。而为了了解Aggregate Operations,必须复习总结一下本文提到的两点:匿名类(Anonymous Classes) 以及 lambda表达式(Lambda Expressions)。官方文档是英文的,为了方便一些关键字直接使用英文原文。

    匿名类 (Anonymous class)

    首先要知道 匿名类anonymous class是表达式(expression),局部类local class是申明(declaration)

    什么时候用:如果一个类只用一次,而且不想取名字的时候

    Anonymous class很像的local class,除了没有名字,他们都能够同时定义与实例化。通常如果只需要使用一个类一次,就可以使用anonymous class。

    关于anonymous class的定义,和local class也有些不同,local class的定义是一个类的定义,而anonymous class的定义是表达式,所以可以直接在表达式里定义这个类。下面的例子很好的说明了。

     1 HelloWorld frenchGreeting = new HelloWorld() {
     2     String name = "tout le monde";
     3     public void greet() {
     4         greetSomeone("tout le monde");
     5     }
     6     public void greetSomeone(String someone) {
     7         name = someone;
     8         System.out.println("Salut " + name);
     9     }
    10 };

    anonymous class的表达式由一下几点组成

    1. new操作符
    2. 这个类extend的class或者implement的interface的名字,这个例子里,类Implement了一个叫 HelloWorld的interface
    3. 一对括号,里面包含了构造函数的参数,由于这里是实现了一个接口,所以没有构造函数,也就没有参数
    4. 一个body

    需要注意的一点,由于anonymous class是表达式,所以最后要加上分号 ;

    Lambda表达式(Lambda Expressions)

    lambda表达式也是表达式!

    什么时候用:如果一个类只有一个方法,而且不想取任何名字的时候

    可以看到使用anonymous class比起重新定义一个class来说,方便了很多。然后如果一个class里只有一个方法的话,这样子的定义看起来还是有那么一点繁琐,lambda表达式便可以让这种情况更加的简洁。

    下面是一个完整的例子来说明anonymous class 和 lambda表达式的用处(省略了文档中的部分approach)

    假设我们有本花名册(roaster)里面存了各种人(Person),有个函数需要筛选年龄大于某个阈值的人,然后输出他们的信息。可以写在如下的函数里:

    1 // approach 1
    2 public static void printPersonsOlderThan(List<Person> roster, int age) {
    3     for (Person p : roster) {
    4         if (p.getAge() >= age) {
    5             p.printPerson();
    6         }
    7     }
    8 }

    但是如果突然产品经理一拍脑袋说,光大于一个年龄不行啊,还得限制他得小于某个年龄,这样就得修改之前写的那个函数了,修改已写过的代码是最蛋疼的事情,因为至少有三件麻烦的事情,1. 以前写的代码不能继续用了 2. 以前写的测试白写了 3. 又要给新写的函数写新的测试了,即使代码和以前的代码基本一样。

    于是可以尝试着把判断年龄这块分离出来,因为可能以后产品经理还会一拍脑袋说光年龄不够,再加点别的东西,改改吧很快的对吧!为了防止这种情况带来的修改,我们可以把判断这部用interface代替

     1 // approach 3
     2 public static void printPersons(
     3     List<Person> roster, CheckPerson tester) {
     4     for (Person p : roster) {
     5         if (tester.test(p)) {
     6             p.printPerson();
     7         }
     8     }
     9 }
    10 
    11 interface CheckPerson {
    12     boolean test(Person p);
    13 }

    然后用一个类来实现这个接口,而在具体调用这个函数的时候,把类当做参数就可以了,如下

     1 class CheckPersonEligibleForSelectiveService implements CheckPerson {
     2     public boolean test(Person p) {
     3         return p.gender == Person.Sex.MALE &&
     4             p.getAge() >= 18 &&
     5             p.getAge() <= 25;
     6     }
     7 }
     8 
     9 printPersons(
    10     roster, new CheckPersonEligibleForSelectiveService());

    这个时候我们发现 CheckPersonEligibleForSelectiveService 这个类从申明出来到使用只用过一次好嘛,还特地申明一下并且费脑子给他想了个名字,完全可以用刚刚学到的anonymous class来写嘛,稍作修改如下

     1 // approach 4
     2 printPersons(
     3     roster,
     4     new CheckPerson() {
     5         public boolean test(Person p) {
     6             return p.getGender() == Person.Sex.MALE
     7                 && p.getAge() >= 18
     8                 && p.getAge() <= 25;
     9         }
    10     }
    11 );

    而下面终于引出了主角,lambda表达式,由于这个anonymous class只有一个方法,所以不如直接用lambda表达式

    1 // approach 5
    2 printPersons(
    3     roster,
    4     (Person p) -> p.getGender() == Person.Sex.MALE
    5         && p.getAge() >= 18
    6         && p.getAge() <= 25
    7 );

    现在给出一个定义:

    functional interface: 只有一个abstract方法的interface (但是可以有多个default或者static方法)

    java.util.function中定义了很多standard functional interface,供大家使用。

    比如现在我们实现的这个interface是一个函数,返回boolean的值,这在标准库中可以用Predicate<T>来实现,而不需要定义CheckPerson这个interface了,代码则可以删去CheckPerson的代码,进一步缩减成:

     1 public static void printPersonsWithPredicate(
     2     List<Person> roster, Predicate<Person> tester) {
     3     for (Person p : roster) {
     4         if (tester.test(p)) {
     5             p.printPerson();
     6         }
     7     }
     8 }
     9 
    10 printPersonsWithPredicate(
    11     roster,
    12     p -> p.getGender() == Person.Sex.MALE
    13         && p.getAge() >= 18
    14         && p.getAge() <= 25
    15 );

    Predicate的定义如下,是在标准库中定义的:

    1 interface Predicate<T> {
    2     boolean test(T t);
    3 }

    好,现在我们尝试把更多的部分用lambda来代替,循环中printPerson() 这块实际上是一个返回void的函数接口,对应于标准库中的 Consumer<>,代码进一步可以写成:

     1 public static void processPersons(
     2     List<Person> roster,
     3     Predicate<Person> tester,
     4     Consumer<Person> block) {
     5         for (Person p : roster) {
     6             if (tester.test(p)) {
     7                 block.accept(p);
     8             }
     9         }
    10 }
    11 
    12 processPersons(
    13      roster,
    14      p -> p.getGender() == Person.Sex.MALE
    15          && p.getAge() >= 18
    16          && p.getAge() <= 25,
    17      p -> p.printPerson()
    18 );

     Lambda简直简洁!

     

    Lambda表达式的语法

    说了这么多,首先明确lambda是一个表达式,和 a+b 是一个东西,lambda表达式有自己的语法,他由以下几部分组成:

    1. 参数由逗号分隔,整体在一个括号中,比如 (a,b,c) 或 (Person p)
      • 类型可省略
      • 如果只有一个参数,那么连括号都能省略
    2. 箭头  ->
    3. 函数块
      1. 如果没有加上 { } ,则执行时候会自动计算并返回结果
      2. 如果加上了 { } ,就和正常函数一样

     可以看到lambda表达式语法还是很直接的。

    注意到lambda表达式长的很像函数定义,所以也能把lambda表达式理解为匿名函数

     

    Lambda表达式返回类型

    那么lambda表达式返回的是什么类型呢?java编译器会通过上下文来判断出应该返回什么类型。所以lambda表达式必须使用在java编译器能够判断返回类型的情况下。

    • Variable declarations

    • Assignments

    • Return statements

    • Array initializers

    • Method or constructor arguments

    • Lambda expression bodies

    • Conditional expressions,:

    • Cast expressions

    参考:

    https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html

    https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

  • 相关阅读:
    在Idea中使用Eclipse编译器
    Idea切换svn分支,类似Eclipse的Switch功能
    Spring AOP详解
    CGLib动态代理原理及实现
    IDEA下搜狗输入法输入中文时卡着不动的参考解决方法
    Nginx反向代理丢失cookie的问题
    redis连接池自动释放
    Redis常用命令
    waitpid之status意义解析
    bash中管道命令返回值如何确定(下)
  • 原文地址:https://www.cnblogs.com/daren-lin/p/anonymous-classes-and-lambda-expressions-in-java.html
Copyright © 2011-2022 走看看