Java 8 - 02 Lambda Expression

2021-08-17 11:32:56 浏览数 (1)

Pre

上一节 Java 8 - 01 优雅编程 lambda 以及 @FunctionalInterface注解一点通

中有的时候使用了匿名类来表示不同的行为 ,代码比较啰嗦

代码语言:javascript复制
  List targetEngineerList6 = enginnerTest.findEnginner(enginnerList, new EnginnerFilter() {
            @Override
            public boolean getMatchedEnginner(Enginner enginner) {
                return "Python".equals(enginner.getJob());
            }
        });

Java 8中如何解决这个问题呢?

Lambda表达式 。 它可以让你很简洁地表示一个行为或传递代码。现在你可以把Lambda表达式看作匿名功能,它基本上就是没有声明名称的方法,但和匿名类一样,它也可以作为参数传递给一个方法。


Lambda 初探

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称
  • 函数—— Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  • 简洁——无需像匿名类那样写很多模板代码。

我们来看下lambda的语法

代码语言:javascript复制
 ( o1,  o2) -> o1.getJob().compareTo(o2.getJob());
  • ( o1, o2) : 参数 ,1个参数的话,可以省略括号
  • -> : 箭头,固定写法
  • o1.getJob().compareTo(o2.getJob()) : lambda主题

我们看看使用lambda之前的语法

代码语言:javascript复制
     Comparator<Enginner> enginnerComparator = new Comparator<Enginner>() {
            @Override
            public int compare(Enginner o1, Enginner o2) {
                return o1.getJob().compareTo(o2.getJob());
            }
        };

用了Lambda表达式:

代码语言:javascript复制
 Comparator<Enginner> enginnerComparator = ( o1,  o2) -> o1.getJob().compareTo(o2.getJob());

所以说:

  • 参数列表——这里它采用了 Comparator 中 compare 方法的参数,两个 Enginner
  • 箭头——箭头 -> 把参数列表与Lambda主体分隔开
  • Lambda主体——比较两个 Enginner 的职位。表达式就是Lambda的返回值

为啥这里连return也没有了呢? 如果 你加了 {} , 就必须要带上return了 ( o1, o2) -> { return o1.getJob().compareTo(o2.getJob()) ;}如果没有 { } 就一行代码,其实是可以省略的,lambda中是可以省略的

为了更进一步的理解lambda,我们接着看几个常见的lambda表达式

代码语言:javascript复制
  Function<String,Integer> flambda = (String s) -> s.length();

第一个Lambda表达式具有一个 String 类型的参数并返回一个 int 。Lambda没有 return 语句,因为已经隐含了 return 。

这个语句的功能,输入一个字符串,返回字符串的长度 。 如果你需要定义一个Lambda,将输入对象的信息映射 到输出 , java.util.function.Function 接口 是你的不二选择


代码语言:javascript复制
  Predicate<Enginner>  predicate= (Enginner e) -> e.getAge() > 30 ;

第二个Lambda 表达式有一个 Enginner类 型的参数并返回一 个 boolean (Enginner 的年龄是否大于30)

在你需要表示一个涉及类型 T 的布尔表达式时,就可以使用java.util.function.Predicate这个接口


代码语言:javascript复制
   (int x,int y ) -> {
            System.out.println(x);
            System.out.println(y);
        };

第三个Lambda表达式具有两个 int 类型的参数而没有返回值( void 返回)。注意Lambda表达式可以包含多行语句,这里是两行.


代码语言:javascript复制
() -> 18

第四个Lambda 表达式没有参数,返回一个 int


代码语言:javascript复制
  Comparator<Enginner> comparator2 = ( o1,  o2) -> o1.getJob().compareTo(o2.getJob());

第五个Lambda表达式具有两个 Enginner类型的参数,返回一个 int :比较两个 Enginner的职业。


lambda语法

Lambda 的基本语法是

代码语言:javascript复制
(parameters) -> expression

或(请注意语句的花括号)

代码语言:javascript复制
(parameters) -> { statements; }

小测验一把

根据上述语法规则,以下哪个不是有效的Lambda表达式?

代码语言:javascript复制
(1)  () -> {}
(2)  () -> "Raoul"
(3)  () -> {return "Mario";}
(4)  (Integer i) -> return "Alan"   i;
(5)  (String s) -> {"IronMan";}

答案:只有4和5是无效的Lambda。

(1) 这个Lambda没有参数,并返回 void 。它类似于主体为空的方法: public void run() {} 。

(2) 这个Lambda没有参数,并返回 String 作为表达式。

(3) 这个Lambda没有参数,并返回 String (利用显式返回语句)。

(4) return 是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示: (Integer i) -> {return "Alan" i;} 。 还得有 分号

(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括和分号,如下所示: (String s) -> "Iron Man" 。或者如果你喜欢,可以使用显式返回语句,如下所示: (String s)->{return "IronMan";}


再举几个例子 ,加深下印象:

布尔表达式

代码语言:javascript复制
  (List<String> list) -> list.isEmpty()

创建对象

代码语言:javascript复制
 () -> new Apple(10)

消费一个对象

代码语言:javascript复制
(Enginner a) -> {
    System.out.println(a.getAge());
}

从一个对象中选择/抽取

代码语言:javascript复制
(String s) -> s.length()

组合两个值

代码语言:javascript复制
  (int a, int b) -> a * b

比较两个对象

代码语言:javascript复制
Comparator<Enginner> comparator2 = ( o1,  o2) -> o1.getJob().compareTo(o2.getJob());

函数式接口

代码语言:javascript复制
 enginnerTest.findEnginner(enginnerList,engineer -> {
            return "Java".equals(engineer.getJob());
        });

就可以优化为

代码语言:javascript复制
enginnerTest.findEnginner(enginnerList,engineer ->  "Java".equals(engineer.getJob()) );

那到底在哪里可以使用Lambda呢?你可以在函数式接口上使用Lambda表达式。在上面的代

码中,我们把 Lambda表达式作为第二个参数传给 filter 方法,因为它这里需要EnginnerFilter ,而这是一个函数式接口.

EnginnerFilter就是一个标准的函数式接口。 因为 EnginnerFilter 仅仅定义了一个抽象方法getMatchedEnginner.

一言以蔽之,函数式接口就是只定义一个抽象方法的接口

实际上 接口现在还可以拥有 默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法抽象方法,它就仍然是一个函数式接口

测试下

下面哪些接口是函数式接口?

代码语言:javascript复制
public interface Adder{
	int add(int a, int b);
}
public interface SmartAdder extends Adder{
	int add(double a, double b);
}
public interface Nothing{
}
  • 只有 Adder 是函数式接口。
  • SmartAdder 不是函数式接口,因为它定义了两个叫作 add 的抽象方法(其中一个是从 Adder 那里继承来的)。
  • Nothing 也不是函数式接口,因为它没有声明抽象方法

用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。

当然了,你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。

举个例子

代码语言:javascript复制
  // 使用lambda
  Runnable r1 = () -> System.out.println("Hello artisan Lambda");

    // 使用匿名内部类
    Runnable r2 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello artisan Inner");
        }
    };
    r1.run();
    r2.run();

函数描述符

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。

举个例子

Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )。

代码语言:javascript复制
(Enginner o1, Enginner o2) -> o1.getJob().compareTo(o2.getJob());

代表接受两个 Enginner 作为参数且返回 int 的函数。

Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法 ,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样

比如 你可以像下面这样直接把一个Lambda传给 process 方法:

代码语言:javascript复制
public void process(Runnable r){
	r.run();
}




process(() -> System.out.println("This is Great!!"));

不接受参数且返回 void 。 这恰恰是 Runnable 接口中 run 方法的签名。

小测一把

以下哪些是使用Lambda表达式的有效方式?

代码语言:javascript复制
(1) execute(() -> {});
	public void execute(Runnable r){
	r.run();
}
(2) public Callable<String> fetch() {
	return () -> "Tricky example ;-)";
}
(3) Predicate<Apple> p = (Apple a) -> a.getWeight();

答案:只有1和2是有效的。

  • 第一个例子有效,是因为Lambda () -> {} 具有签名 () -> void ,这和 Runnable 中的抽象方法 run 的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的
  • 第二个例子也是有效的。事实上, fetch 方法的返回类型是 CallableCallable 基本上就定义了一个方法,签名是 () -> String ,其中 T 被 String 代替 了。因为Lambda () -> "Trickyexample;-)" 的签名是 () -> String ,所以在这个上下文 中可以使用Lambda。
  • 第三个例子无效,因为Lambda表达式 (Apple a) -> a.getWeight() 的签名是 (Apple) -> Integer ,这和 Predicate:(Apple) -> boolean 中定义的 test 方法的签名不同。

0 人点赞