JAVA 8 lambda 学习

基础

  • lambda 语法:
    1
    2
    3
    (parameters) -> expression

    (parameters) ->{ statements; }

表达式使用大括弧情况:

  1. 接口中的方法的返回类型是void 原因:如果接口的方法的实现是有返回值,但是函数声明为void,不加大括弧则会认为函数的返回值为实现的返回值(lambda是不写return的,所以都认为有返回值), 所以必须区别对待。
  2. 如果Lambda表达式主体部分包含多条语句,也必须用花括号,并且return语句不能省。
  • 功能接口

只包含一个方法的接口被称为功能接口,Lambda 表达式用用于任何功能接口适用的地方。

  • 编译器分析原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//java正常写法
File dir = new File("/an/dir/");
FileFilter directoryFilter = new FileFilter() {
public boolean accept(File file) {
return file.isDirectory();
}
};
//lambda
File dir = new File("/an/dir/");
FileFilter directoryFilter = (File f) -> f.isDirectory();
File[] dirs = dir.listFiles(directoryFilter);
//或者
File dir = new File("/an/dir/");
File[] dirs = dir.listFiles((File f) -> f.isDirectory());

编译器知道FileFilter只有一个方法accept(),所以accept()方法肯定对应(File f) -> f.isDirectory(),而且accept()方法只有一个File类型的参数,所以(File f) -> f.isDirectory()中的File f就是这个参数了.

通俗说法,将接口的实现直接放到函数的参数中。

  • 从for循环开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer","Roger Federer",
"Andy Murray","Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);

// 以前的循环方式
for (String player : players) {
System.out.print(player + "; ");
}

// 使用 lambda 表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));

// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);
  • 匿名内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//////各种Listener//////
// 使用匿名内部类
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});

// 或者使用 lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));

//////针对线程实现//////
// 1.1使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
}).start();

// 1.2使用 lambda expression
new Thread(() -> System.out.println("Hello world !")).start();

// 2.1使用匿名内部类
Runnable race1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};

// 2.2使用 lambda expression
Runnable race2 = () -> System.out.println("Hello world !");

// 直接调用 run 方法(没开新线程哦!)
race1.run();
race2.run();
  • 集合排序

其实跟上面如出一辙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//////之前java实现方法//////
String[] players = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"};

// 1.1 使用匿名内部类根据 name 排序 players
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.compareTo(s2));
}
});
//////now//////
// 1.2 使用 lambda expression 排序 players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players, sortByName);

// 1.3 也可以采用如下形式:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));

  • stream 引入 (重头戏)

Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。

跟着下面的例子走,去熟悉stream的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Person {

private String firstName, lastName, job, gender;
private int salary, age;

public Person(String firstName, String lastName, String job,
String gender, int age, int salary)
{

this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.age = age;
this.job = job;
this.salary = salary;
}
}

List<Person> javaProgrammers = new ArrayList<Person>() {
{
add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
}
};

List<Person> phpProgrammers = new ArrayList<Person>() {
{
add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
}
};
  • 现在我们使用forEach方法来迭代输出上述列表:

    1
    2
    3
    System.out.println("所有程序员的姓名:");
    javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
  • 我们同样使用forEach方法,增加程序员的工资5%:

    1
    2
    3
    4
    5
    System.out.println("给程序员加薪 5% :");
    Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());

    javaProgrammers.forEach(giveRaise);
    phpProgrammers.forEach(giveRaise);
  • 另一个有用的方法是过滤器filter() ,让我们显示月薪超过1400美元的PHP程序员:

    1
    2
    3
    4
    System.out.println("下面是月薪超过 $1,400 的PHP程序员:")
    phpProgrammers.stream()
    .filter((p) -> (p.getSalary() > 1400))
    .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
  • 我们也可以定义过滤器,然后重用它们来执行其他操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 定义 filters
    Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
    Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
    Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));

    System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:");
    phpProgrammers.stream()
    .filter(ageFilter)
    .filter(salaryFilter)
    .filter(genderFilter)
    .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    // 重用filters
    System.out.println("年龄大于 24岁的女性 Java programmers:");
    javaProgrammers.stream()
    .filter(ageFilter)
    .filter(genderFilter)
    .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
  • 使用limit方法,可以限制结果集的个数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    System.out.println("最前面的3个 Java programmers:");
    javaProgrammers.stream()
    .limit(3)
    .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));


    System.out.println("最前面的3个女性 Java programmers:");
    javaProgrammers.stream()
    .filter(genderFilter)
    .limit(3)
    .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
  • 排序呢? 我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:

    注意其中集合的使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    System.out.println("根据 name 排序,并显示前5个 Java programmers:");
    List<Person> sortedJavaProgrammers = javaProgrammers
    .stream()
    .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
    .limit(5)
    .collect(toList());

    sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

    System.out.println("根据 salary 排序 Java programmers:");
    sortedJavaProgrammers = javaProgrammers
    .stream()
    .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
    .collect( toList() );

    sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
  • 如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    System.out.println("工资最低的 Java programmer:");
    Person pers = javaProgrammers
    .stream()
    .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
    .get()

    System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())

    System.out.println("工资最高的 Java programmer:");
    Person person = javaProgrammers
    .stream()
    .max((p, p2) -> (p.getSalary() - p2.getSalary()))
    .get()

    System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())
  • 结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:

map作用有点类似forEach,不同的是,它会将这些返回值进行整合。传递给下一个操作。
使用collect可以将结果转换成多种形式输出,string、set、treeset、list等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
System.out.println("将 PHP programmers 的 first name 拼接成字符串:");
String phpDevelopers = phpProgrammers
.stream()
.map(Person::getFirstName)
.collect(joining(" ; ")); // 在进一步的操作中可以作为标记(token)

System.out.println("将 Java programmers 的 first name 存放到 Set:");
Set<String> javaDevFirstName = javaProgrammers
.stream()
.map(Person::getFirstName)
.collect(toSet());

System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
.stream()
.map(Person::getLastName)
.collect(toCollection(TreeSet::new));
  • Streams 还可以是并行的(parallel)。 示例如下:

    1
    2
    3
    4
    5
    System.out.println("计算付给 Java programmers 的所有money:");
    int totalSalary = javaProgrammers
    .parallelStream()
    .mapToInt(p -> p.getSalary())
    .sum();
  • 我们可以使用summaryStatistics方法获得stream 中元素的各种汇总数据。 接下来,我们可以访问这些方法,比如getMax, getMin, getSum或getAverage:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //计算 count, min, max, sum, and average for numbers
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    IntSummaryStatistics stats = numbers
    .stream()
    .mapToInt((x) -> x)
    .summaryStatistics();

    System.out.println("List中最大的数字 : " + stats.getMax());
    System.out.println("List中最小的数字 : " + stats.getMin());
    System.out.println("所有数字的总和 : " + stats.getSum());
    System.out.println("所有数字的平均值 : " + stats.getAverage());

参考文章:

http://blog.csdn.net/dm_vincent/article/category/2268127 (一个系列,未看完。。。)
http://blog.csdn.net/renfufei/article/details/24600507
http://developer.51cto.com/art/201206/344308.htm 有java 8默认接口的介绍