Pre
上一节 Java 8 - 01 优雅编程 lambda 以及 @FunctionalInterface注解一点通
中有的时候使用了匿名类来表示不同的行为 ,代码比较啰嗦
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的语法
( o1, o2) -> o1.getJob().compareTo(o2.getJob());
- ( o1, o2) : 参数 ,1个参数的话,可以省略括号
- -> : 箭头,固定写法
- o1.getJob().compareTo(o2.getJob()) : lambda主题
我们看看使用lambda之前的语法
Comparator<Enginner> enginnerComparator = new Comparator<Enginner>() { @Override public int compare(Enginner o1, Enginner o2) { return o1.getJob().compareTo(o2.getJob()); } };
用了Lambda表达式:
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表达式
Function<String,Integer> flambda = (String s) -> s.length();
第一个Lambda表达式具有一个 String 类型的参数并返回一个 int 。Lambda没有 return 语句,因为已经隐含了 return 。
这个语句的功能,输入一个字符串,返回字符串的长度 。 如果你需要定义一个Lambda,将输入对象的信息映射 到输出 , java.util.function.Function<T, 接口 是你的不二选择
Predicate<Enginner> predicate= (Enginner e) -> e.getAge() > 30 ;
第二个Lambda 表达式有一个 Enginner类 型的参数并返回一 个 boolean (Enginner 的年龄是否大于30)
在你需要表示一个涉及类型 T 的布尔表达式时,就可以使用java.util.function.Predicate<T>这个接口
(int x,int y ) -> { System.out.println(x); System.out.println(y); };
第三个Lambda表达式具有两个 int 类型的参数而没有返回值( void 返回)。注意Lambda表达式可以包含多行语句,这里是两行.
() -> 18
第四个Lambda 表达式没有参数,返回一个 int
Comparator<Enginner> comparator2 = ( o1, o2) -> o1.getJob().compareTo(o2.getJob());
第五个Lambda表达式具有两个 Enginner类型的参数,返回一个 int :比较两个 Enginner的职业。
lambda语法
Lambda 的基本语法是
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements; }
小测验一把
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(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";} 。
再举几个例子 ,加深下印象:
布尔表达式
(List<String> list) -> list.isEmpty()
创建对象
() -> new Apple(10)
消费一个对象
(Enginner a) -> { System.out.println(a.getAge()); }
从一个对象中选择/抽取
(String s) -> s.length()
组合两个值
(int a, int b) -> a * b
比较两个对象
Comparator<Enginner> comparator2 = ( o1, o2) -> o1.getJob().compareTo(o2.getJob());
函数式接口
enginnerTest.findEnginner(enginnerList,engineer -> { return "Java".equals(engineer.getJob()); });
就可以优化为
enginnerTest.findEnginner(enginnerList,engineer -> "Java".equals(engineer.getJob()) );
那到底在哪里可以使用Lambda呢?你可以在函数式接口上使用Lambda表达式。在上面的代
码中,我们把 Lambda表达式作为第二个参数传给 filter 方法,因为它这里需要EnginnerFilter ,而这是一个函数式接口.
EnginnerFilter就是一个标准的函数式接口。 因为 EnginnerFilter 仅仅定义了一个抽象方法getMatchedEnginner.
一言以蔽之,函数式接口就是只定义一个抽象方法的接口
实际上 接口现在还可以拥有 默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法抽象方法,它就仍然是一个函数式接口
测试下
下面哪些接口是函数式接口?
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表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。
当然了,你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。
举个例子
// 使用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 )。
(Enginner o1, Enginner o2) -> o1.getJob().compareTo(o2.getJob());
代表接受两个 Enginner 作为参数且返回 int 的函数。
Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法 ,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样
比如 你可以像下面这样直接把一个Lambda传给 process 方法:
public void process(Runnable r){ r.run(); } process(() -> System.out.println("This is Great!!"));
不接受参数且返回 void 。 这恰恰是 Runnable 接口中 run 方法的签名。
小测一把
以下哪些是使用Lambda表达式的有效方式?
(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 方法的返回类型是 Callable<String> 。
Callable<String> 基本上就定义了一个方法,签名是 () -> String ,其中 T 被 String 代替
了。因为Lambda () -> "Trickyexample;-)" 的签名是 () -> String ,所以在这个上下文
中可以使用Lambda。
第三个例子无效,因为Lambda表达式 (Apple a) -> a.getWeight() 的签名是
(Apple) -> Integer ,这和 Predicate<Apple>:(Apple) -> boolean 中定义的 test 方法的签名不同。