java 8 Lambda表达式
介绍
本篇文章主要是RxJava官方文档的辅料,里面太多的Lambda表达式,没有一些了解的确难以看懂,因为是主要的重心还是落在RxJava上,所以本文概述一下如何在Android Studio中使用以及各个表达式的意思。官方文档如何描述Lambda表达式:
Lambda expressions are a new and important feature included in Java SE 8. They provide a clear and concise way to represent one method interface using an expression. Lambda expressions also improve the Collection libraries making it easier to iterate through, filter, and extract data from a Collection. In addition, new concurrency features improve performance in multicore environments.
这段话指出了在java8中lambda表达式三个主要的作用:简洁的方式呈现接口;提高集合迭代,过滤和扩展的性能;提高多核环境下并发的性能。具体的翻译还是大家自行去理解,中英转换肯定会出现一定的偏差。
软硬件环境
- JDK 8
- NetBeans 7.4 (这个只是IDE 在Android Studio中同样是可以使用的)
Lambda应用在那些场景呢?因为不知道怎么翻译合适就直接上英语了.
Anonymous Inner Class
匿名内部类,用过java的人这个应该都知道就不再赘述
// 匿名内部类
JButton jb1 = new JButton();
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("xxx");
}
});
// lambda表达式的方式
JButton jb = new JButton();
jb.addActionListener((e) -> System.out.println());
Functional Interface
在java中,一个interface里面只有一个需要实现的方法,这种形式的接口就被称作为Functional Interface,也被叫做SAM.(Single Abstract Method)
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e);
}
Lambda Expression Syntax
λ表达式的语法,通常λ表达式可以将5行代码,转换成一行代码来表示,上面的匿名内部类便是例子,λ表达式的语法由如下三个部分组成:
参数列表 | 箭头 | 方法体 |
---|---|---|
(int x,int y) | -> | x + y |
lambda表达式实例
Listener Lambda
private static void listenerLambda() {
// Listener Lambda
JButton jb1 = new JButton();
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("xxx");
}
});
// lambda表达式的方式
JButton jb = new JButton();
jb.addActionListener((e) -> System.out.println());
}
Comparator Lambda
private static void compatorLambda() {
// sort with inner class
List<Person> personList = new ArrayList<>();
personList.add(new Person("zk"));
personList.add(new Person("ak"));
System.out.println("sort with inner class");
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// 按照字典序排列 person
return o1.getSurName().compareTo(o2.getSurName());
}
});
for (Person p:
personList) {
System.out.println(p.getSurName());
}
System.out.println("sort with lambda");
// sort with lambda
Collections.sort(personList, (p1,p2)->p1.getSurName().compareTo(p2.getSurName()));
for (Person p:
personList) {
System.out.println(p.getSurName());
}
}
Runnable Lambda
private static void runnableLambda() {
// 传统模式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Tran A");
System.out.println("Tran B");
}
};
// lambda
Runnable r2 = () -> System.out.print("Lambda A");
System.out.print("Lambda B");
r1.run();
r2.run();
}
Improving Code with Lambda Expression
使用Lambda优化代码。代码优化这件事可轻可重,像我写代码的主要目的就是奔着功能去,一往无前,有bug回头改,往往都是一去不返。代码优化有很多方面,像6大设计原则,23种设计模式等等,今天得例子要验证得Principle叫做 — ‘Don’t Repeat Yourself ’ 简称 (DRY),不得不说所有得原则全称都不如简称有格调。
那下面我们就来验证一下lambda相对于原有得做法到底 DRY 在哪里:
1.场景
有一群人来面试,只招聘司机、应征入伍、飞行员,面试得条件如下:
司机 : 年纪超过16
入伍 : 男性年龄在18-25之间
飞行员:年龄在23 到 65之间
Person 字段
public class Person {
private String givenName;
private String surName;
private int age;
private Gender gender;
private String eMail;
private String phone;
private String address;
2.创建person List
怎么创建比较随意,我这里就按照官网得方式来创建(在上一篇RxJava中有关于设计模式得附录)
public static List<Person> createShortList() {
List<Person> people = new ArrayList<>();
people.add(
new Person.Builder().
givenName("Bob")
.surName("Baker")
.age(21)
.gender(Gender.MALE)
.email("bob.baker@example.com")
.phoneNumber("201-121-4678")
.address("44 4th St, Smallville, KS 12333")
.build()
);
people.add(
new Person.Builder()
.givenName("Jane")
.surName("Doe")
.age(25)
.gender(Gender.FEMALE)
.email("jane.doe@example.com")
.phoneNumber("202-123-4678")
.address("33 3rd St, Smallville, KS 12333")
.build()
);
people.add(
new Person.Builder()
.givenName("John")
.surName("Doe")
.age(25)
.gender(Gender.MALE)
.email("john.doe@example.com")
.phoneNumber("202-123-4678")
.address("33 3rd St, Smallville, KS 12333")
.build()
);
return people;
}
3.选出你所要挑选的满足条件的人
public class RoboContactMethods {
public void callDrivers(List<Person> pl) {
for (Person p : pl) {
if (p.getAge() >= 16) {
roboCall(p);
}
}
}
public void emailDraftees(List<Person> pl) {
for (Person p : pl) {
if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE) {
roboEmail(p);
}
}
}
public void mailPilots(List<Person> pl) {
for (Person p : pl) {
if (p.getAge() >= 23 && p.getAge() <= 65) {
roboMail(p);
}
}
}
public void roboCall(Person p) {
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p) {
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.geteMail());
}
public void roboMail(Person p) {
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
我看这段代码是没问题的,for循环迭代选出满足条件的人,完全没毛病;那我们来看看官方的犊子是怎么演的。
- 没有遵循DRY原则
- 每个方法的选择条件必须重写
- 每个方法都重复循环
- 需要大量方法来实现每个用例
- 上述代码不灵活,如果招聘条件发生变化,那么将引起大量的更改,可维护性不强
4.第一次重构
这次我们将上述的几个问题列入考虑,其实,我感觉我的翻译貌似出了点小差错,或者是我没明白他是什么意思,除了最后一项可以理解之外,其他的情况没弄懂是什么意思,再或者是本身语言的限制只能优化最后一项,那么下面看看,该如何重构。
public class RoboContactMethods2 {
public void callDrivers(List<Person> pl) {
for (Person p : pl) {
if (isDriver(p)) {
roboCall(p);
}
}
}
public void emailDraftees(List<Person> pl) {
for (Person p : pl) {
if (isDraftee(p)) {
roboEmail(p);
}
}
}
public void mailPilots(List<Person> pl) {
for (Person p : pl) {
if (isPilot(p)) {
roboMail(p);
}
}
}
public boolean isDriver(Person p) {
return p.getAge() >= 16;
}
public boolean isDraftee(Person p) {
return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
}
public boolean isPilot(Person p) {
return p.getAge() >= 23 && p.getAge() <= 65;
}
public void roboCall(Person p) {
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p) {
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.geteMail());
}
public void roboMail(Person p) {
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
搜索条件被封装进方法中,较之前的代码而言确实有一定的提升,条件可以被反复利用并且可以更方便的更改。但是,还是有很多重复的地方,是不是没有更好的办法呢?(······肯定是有的,要不我说个球)
在使用lambda之前先看看还有什么能改进的或者是为lambda做一些铺垫。上述的代码,其实并不符合开闭原则(OCP – 原则上对修改关闭,对扩展开放),这段程序可能发生改变是招聘条件,那么我们让调用程序自己去维护,就更进一步的优化了。
将条件抽象成接口,让调用者自己去维护,接口如下:
public interface MyTest<T> {
public boolean test(T t);
}
RoboContactsAnon.java
package com.example.lambda;
import java.util.List;
import java.util.function.Predicate;
/**
* @author MikeW
*/
public class RoboContactsAnon {
public void phoneContacts(List<Person> pl, MyTest<Person> pred) {
for (Person p : pl) {
if (pred.test(p)) {
roboCall(p);
}
}
}
public void emailContacts(List<Person> pl, MyTest<Person> pred) {
for (Person p : pl) {
if (pred.test(p)) {
roboEmail(p);
}
}
}
public void mailContacts(List<Person> pl, MyTest<Person> pred) {
for (Person p : pl) {
if (pred.test(p)) {
roboMail(p);
}
}
}
public void roboCall(Person p) {
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p) {
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
}
public void roboMail(Person p) {
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
RoboCallTest03.java 调用程序
public class RoboCallTest03 {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
RoboContactsAnon robo = new RoboContactsAnon();
System.out.println("\n==== Test 03 ====");
System.out.println("\n=== Calling all Drivers ===");
robo.phoneContacts(pl,
new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >=16;
}
}
);
System.out.println("\n=== Emailing all Draftees ===");
robo.emailContacts(pl,
new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
}
}
);
System.out.println("\n=== Mail all Pilots ===");
robo.mailContacts(pl,
new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >= 23 && p.getAge() <= 65;
}
}
);
}
}
仅通过java8之前得方式,程序看起来已经符合OCP的原则;在前面提到过SAM这种形式是可以用,lambda来表示的。那么看看如何去用lambda,来进一步的优化上面的程序
lambda 优化
在java.util.function包中提供了一种形式类似的MyTest的接口,Predicate;
public interface Predicate<T> {
public boolean test(T t);
}
RoboContactsLambda.java
package com.example.lambda;
import java.util.List;
import java.util.function.Predicate;
/**
* @authorzhoukan
*/
public class RoboContactLambda {
public void phoneContacts(List<Person> pl, Predicate<Person> pred) {
for (Person p : pl) {
if (pred.test(p)) {
roboCall(p);
}
}
}
public void emailContacts(List<Person> pl, Predicate<Person> pred) {
for (Person p : pl) {
if (pred.test(p)) {
roboEmail(p);
}
}
}
public void mailContacts(List<Person> pl, Predicate<Person> pred) {
for (Person p : pl) {
if (pred.test(p)) {
roboMail(p);
}
}
}
public void roboCall(Person p) {
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p) {
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
}
public void roboMail(Person p) {
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
Mytest接口被更换成了Predicate,调用程序的代码片段如下
public static void main(String[] args){
List<Person> pl = Person.createShortList();
RoboContactLambda robo = new RoboContactLambda();
Predicate<Person> allDrivers = p -> p.getAge() >= 16;
robo.phoneContacts(pl, allDrivers);
}
当然即使不适用util.function提供的接口,也是可以实现了,只要满足lambda表达式的场景就好:
robo.mailContacts(pl,
/*new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >= 23 && p.getAge() <= 65;
}
}*/
(p) -> p.getAge() > 23 && p.getAge() <= 65
);
The java.util.function 包
function包提供了几种方便去使用lambda的interface,当然不仅仅是给lambda表达式使用,所有诸如上述的优化都是可以考虑用这些方法的,确实我看到这里就有点抑制不住内心的小狂喜,lambda的这些function不就是Rxjava中常用的操作符嘛,这么麻烦的工作算是没白做,就下面的需求,我们来体验一下她的魅力。
- Predicate: A property of the object passed as argument
- Consumer: An action to be performed with the object passed as argument
- Function: Transform a T to a U
- Supplier: Provide an instance of a T (such as a factory)
- UnaryOperator: A unary operator from T -> T
- BinaryOperator: A binary operator from (T, T) -> T
输出东方人和西方人的信息
上面的personList(求职者),要将他们的信息分别发给,老板和老板娘,他们一个是中国人,一个是美国人;由于习惯不同,西方习惯姓在后名在前(分别对应Given name 和 surname),中国的习惯我就不提了。
传统的方式
person.java
public void printWesternName() {
System.out.println("\nName: " + this.getGivenName() + " " + this.getSurName() + "\n" +
"Age: " + this.getAge() + " " + "Gender: " + this.getGender() + "\n" +
"EMail: " + this.geteMail() + "\n" +
"Phone: " + this.getPhone() + "\n" +
"Address: " + this.getAddress());
}
public void printEasternName() {
System.out.println("\nName: " + this.getSurName() + " " + this.getGivenName() + "\n" +
"Age: " + this.getAge() + " " + "Gender: " + this.getGender() + "\n" +
"EMail: " + this.geteMail() + "\n" +
"Phone: " + this.getPhone() + "\n" +
"Address: " + this.getAddress());
}
使用Function Interface的方式
public interface Function<R,T>{
public R apply(T t);
}
指定两个泛型以T作为输入,R作为输出,那么对于上述场景而言 -> Function
输出
==== NameTestNew02 ===
===Custom List===
Name: Bob EMail: bob.baker@example.com
Name: Jane EMail: jane.doe@example.com
Name: John EMail: john.doe@example.com
Name: James EMail: james.johnson@example.com
Name: Joe EMail: joebob.bailey@example.com
Name: Phil EMail: phil.smith@examp;e.com
Name: Betty EMail: betty.jones@example.com
===Western List===
Name: Bob Baker
Age: 21 Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333
Name: Jane Doe
Age: 25 Gender: FEMALE
EMail: jane.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: John Doe
Age: 25 Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: James Johnson
Age: 45 Gender: MALE
EMail: james.johnson@example.com
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111
Name: Joe Bailey
Age: 67 Gender: MALE
EMail: joebob.bailey@example.com
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111
Name: Phil Smith
Age: 55 Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333
Name: Betty Jones
Age: 85 Gender: FEMALE
EMail: betty.jones@example.com
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
===Eastern List===
Name: Baker Bob
Age: 21 Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333
Name: Doe Jane
Age: 25 Gender: FEMALE
EMail: jane.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: Doe John
Age: 25 Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: Johnson James
Age: 45 Gender: MALE
EMail: james.johnson@example.com
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111
Name: Bailey Joe
Age: 67 Gender: MALE
EMail: joebob.bailey@example.com
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111
Name: Smith Phil
Age: 55 Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333
Name: Jones Betty
Age: 85 Gender: FEMALE
EMail: betty.jones@example.com
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
lambda表达式和集合
体验过Function接口的优点还没完,java 8给我们的惊喜还不止这些。(不这些都不够,我必须是你近旁的一只木棉,根紧握地里,也相触云间 … 神特么的乱入),lambda基础部分结束后,来看看在集合方面它又有哪些优势。
先看看有哪些不一样的操作符:
- forEach
- stream
- filter
- collection
- map
1.添加新的类,将查询条件全部封装进HashMap中
public class SearchCriteria {
private final Map<String, MyTest<Person>> searchMap = new HashMap<>();
private SearchCriteria() {
super();
initSearchMap();
}
private void initSearchMap() {
MyTest<Person> allDrivers = p -> p.getAge() >= 16;
MyTest<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
MyTest<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;
searchMap.put("allDrivers", allDrivers);
searchMap.put("allDraftees", allDraftees);
searchMap.put("allPilots", allPilots);
}
public MyTest<Person> getCriteria(String PredicateName) {
MyTest<Person> target;
target = searchMap.get(PredicateName);
if (target == null) {
System.out.println("Search Criteria not found... ");
System.exit(1);
}
return target;
}
public static SearchCriteria getInstance() {
return new SearchCriteria();
}
}
2.Looping
Test01ForEach.java
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
System.out.println("\n=== Western Phone List ===");
pl.forEach(p -> p.printWesternName());
System.out.println("\n=== Eastern Phone List ===");
pl.forEach(Person::printEasternName);
System.out.println("\n=== Custom Phone List ===");
pl.forEach(p -> {
System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail()));
});
}
3.链式调用和过滤器
Test02Filter.java
public class Test02Filter {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
SearchCriteria search = SearchCriteria.getInstance();
System.out.println("\n=== Western Pilot Phone List ===");
pl.stream().filter(search.getCriteria("allPilots"))
.forEach(Person::printWesternName);
System.out.println("\n=== Eastern Draftee Phone List ===");
pl.stream().filter(search.getCriteria("allDraftees"))
.forEach(Person::printEasternName);
}
}
4.getting Lazy
由于篇幅过长,写的有点疲惫,细枝末节的地方,可能没描述清楚,留言里面见;当然这个getting lazy说的并不是我懒
- Laziness:跟java-web的懒加载类似的概念,在前面程序中,循环就采取了了‘lazy’的方式,在filter之后再进行输出,这是一种更为高效的方式
- Eagerness:Code that performs operations on every object in a list is considered “eager”. For example, an enhanced for loop that iterates through an entire list to process two objects, is considered a more “eager” approach.(ps : 直接给原文了)
stream
在上面的代码中,filter之前都会先使用stream,stream方法会将Collection作为输入,返回java.util.stream.Stream接口作为输出。Stream就是集合中元素的序列;默认情况下,一旦元素被调用在stream中将不再有该元素,类似于java中的queuen,因此,每个stream只能被使用一次;另外,stream能变成一个序列在该方法调用之后,后文的例子将印证这一点。
转化和结果
当stream被使用之后被处理,所以在stream中的元素,是无法发生更改;那么如果你还是想在链式操作之后保留stream对应的collection:可以将他们保留在一个新的集合中SHOW CODE :
Test03toList.java
public class Test03toList {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
SearchCriteria search = SearchCriteria.getInstance();
// Make a new list after filtering.
List<Person> pilotList = pl
.stream()
.filter(search.getCriteria("allPilots"))
.collect(Collectors.toList());
System.out.println("\n=== Western Pilot Phone List ===");
pilotList.forEach(Person::printWesternName);
}
}
.collect(Collectors.toList()); 返回一个新的collection
集合中的计算
map()通常和filter成对出现,map会从对象里抽出某个属性,来执行一些操作,下面的例子以age作为主要的操作字段,来呈现集合中计算的用法
Test04Map.java
public class Test04Map {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
SearchCriteria search = SearchCriteria.getInstance();
// Calc average age of pilots old style
System.out.println("== Calc Old Style ==");
int sum = 0;
int count = 0;
for (Person p:pl){
if (p.getAge() >= 23 && p.getAge() <= 65 ){
sum = sum + p.getAge();
count++;
}
}
long average = sum / count;
System.out.println("Total Ages: " + sum);
System.out.println("Average Age: " + average);
// Get sum of ages
System.out.println("\n== Calc New Style ==");
long totalAge = pl
.stream()
.filter(search.getCriteria("allPilots"))
.mapToInt(p -> p.getAge())
.sum();
// Get average of ages
OptionalDouble averageAge = pl
.parallelStream()
.filter(search.getCriteria("allPilots"))
.mapToDouble(p -> p.getAge())
.average();
System.out.println("Total Ages: " + totalAge);
System.out.println("Average Age: " + averageAge.getAsDouble());
}
}
结语
总算是写完了,博客后面写得不是很走心,但是还是比较开心,又一次通过官方文档,完成了一个知识点:另外由于篇幅问题,Android Studio中如何引入java 8将在另外一篇博客中说明,lambda就到这里了,欢迎勘误。