Lambda Expressions
功能接口是指只有一个抽象方法的接口
一个匿名实现功能接口的例子
public class RunnableDemo {
public static void main(String[] args) {
new Thread(new Runnable() { 1
@Override
public void run() {
System.out.println(
"inside runnable using an anonymous inner class");
}
}).start();
}
}
等价于
new Thread(() -> System.out.println(
"inside Thread constructor using lambda")).start();
这个语句使用箭头分隔参数和函数体,因为只有0个参数所以用了一对空括号,因为函数体只有一行所以不需要花括号。这被称作lambda表达式,一行表达式产生的值会被自动返回。在这个例子中,表达式返回值正好匹配run方法的返回值void。
lambda表达式参数列表和返回类型必须要和抽象方法匹配,但是抽象方法名可以任意命名。因此lambda表达式就是接口的一个实现,可以只用此接口类型引用,比如
Runnable r = () -> System.out.println(
"lambda expression implementing the run method");
new Thread(r).start();
例子:java.io.FilenameFilter是一个功能接口,他的实例传给File.list方法用来限制只返回满足FilenameFilter实现类条件的文件。FilenameFilter 接口包含一个抽象方法boolean accept(File dir, String name)
File参数是目录中发现的文件,name是这个文件名
File directory = new File("./src/main/java");
String[] names = directory.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
});
System.out.println(Arrays.asList(names));
对应相同功能的lambda表达式
File directory = new File("./src/main/java");
String[] names = directory.list((dir, name) -> name.endsWith(".java"));
System.out.println(Arrays.asList(names));
}
这个例子中,参数在括号中但是没指名类型,表达式返回bool类型,编译器可以自动推断参数类型,也可以明确指定类型,比如
File directory = new File("./src/main/java");
String[] names = directory.list((File dir, String name) ->
name.endsWith(".java"));
最后如果方法体有多行,此时需要大括号和return语句,比如
File directory = new File("./src/main/java");
String[] names = directory.list((File dir, String name) -> { 1
return name.endsWith(".java");
});
System.out.println(Arrays.asList(names));
lambda表达式可以是方法参数,方法返回值,或者分配给接口引用。
Method References
用方法参考来参照一个存在的方法并把它当成一个lambda表达式
如果一个lambda表达式把一个方法对待成一个对象一样,name一个方法参考对待一个存在的方法好像是一个lambda
比如Iterable 中的forEach 方法使用Consumer 作为参数:
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(x -> System.out.println(x));
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(System.out::println);
Consumer<Integer> printer = System.out::println;
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(printer);
::提供一个System.out实例中的println方法参考,方法参考后面没有放括号。方法参考逼lambda更好因为更短更易于阅读。
也可以使用静态方法作为方法参考,比如:
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println);
Math::random是静态方法参考,System.out::println是实例方法参考。Stream 的generate方法采用Supplier 作为参数,Supplier 是一个功能接口,他的抽象方法不接受参数并返回一个单一的结果,Math 类的random 静态方法满足Supplier 的抽象方法定义,也是不需要传入参数,返回一个伪随机的小数。所以Math::random就是Supplier 接口的实现。System.out::println是Consumer接口的实现
语法:
object::instanceMethod 参照实例对象的实例方法如System.out::println
Class::staticMethod 参考静态方法,例如Math::max
Class::instanceMethod 参考类的水方法,比如String::length
Class::instanceMethod有些迷惑,因为我们只知道使用类名调用静态方法。记住lambda 表达式和方法参照总是有上下文接口做参照。
如果是object::instanceMethod,上下文将把参数提供给这个方法,比如
// equivalent to System.out::println
x -> System.out.println(x)
如果是Class::staticMethod,上下文将把参数提供给这个方法,比如
// equivalent to Math::max
(x,y) -> Math.max(x,y)
但是Class::instanceMethod 不一样,比如下面例子,上下文提供的参数x作为函数length的调用者而不是函数length的参数了。
如果上下文提供多个参数那么第一个参数作为函数调用者其余参数作为函数参数
// equivalent to String::length
x -> x.length()
List<String> strings =
Arrays.asList("this", "is", "a", "list", "of", "strings");
List<String> sorted = strings.stream()
.sorted((s1, s2) -> s1.compareTo(s2))
.collect(Collectors.toList());
List<String> sorted = strings.stream()
.sorted(String::compareTo)
.collect(Collectors.toList());
Stream.of("this", "is", "a", "stream", "of", "strings")
.map(String::length) Class::instanceMethod
.forEach(System.out::println); object::instanceMethod
和上面相等的lambda表达式语法
Stream.of("this", "is", "a", "stream", "of", "strings")
.map(s -> s.length())
.forEach(x -> System.out.println(x));
Constructor References
List<String> names = people.stream()
.map(person -> person.getName())
.collect(Collectors.toList());
// 或者如下写法
List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
// getters and setters ...
// equals, hashCode, toString ...
}
List<String> names =
Arrays.asList("Grace Hopper", "Barbara Liskov", "Ada Lovelace",
"Karen Spärck Jones");
List<Person> people = names.stream()
.map(name -> new Person(name))
.collect(Collectors.toList());
// 或者如下写法
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
Person::new参考Person 类中的构造器,和所有lambda一样,上下文决定了那个构造器被调用。因为上下文只提供一个string参数,所以public Person(String name) {
this.name = name;
}
构造器被使用
Copy constructor
Person before = new Person("Grace Hopper");
List<Person> people = Stream.of(before)
.collect(Collectors.toList());
Person after = people.get(0);
assertTrue(before == after); 相等
before.setName("Grace Murray Hopper");
assertEquals("Grace Murray Hopper", after.getName()); 相等
public Person(Person p) {
this.name = p.name;
}
Person before = new Person("Grace Hopper");
List<Person> people = Stream.of(before)
.collect(Collectors.toList());
Person after = people.get(0);
assertTrue(before == after); 不相等
before.setName("Grace Murray Hopper");
assertEquals("Grace Murray Hopper", after.getName()); 不相等
Arrays
构造器参考也可应用于arrays,Stream中有一个toArray 方法,<A> A[] toArray(IntFunction<A[]> generator)
。
Person[] people = names.stream()
.map(Person::new)
.toArray(Person[]::new); 创建正确大小Person 数组,并且用Person 实例来初始化此数组
构造器参考是方法参考的一种
Functional Interfaces
@FunctionalInterface
public interface PalindromeChecker {
boolean isPalidrome(String s);
}
接口中所有方法都是public abstract 的。@FunctionalInterface标记这个接口是功能接口。功能接口可以有default 方法和static 方法,它们两个都是有实现的。
@FunctionalInterface
public interface MyInterface {
int myMethod();
// int myOtherMethod();
default String sayHello() {
return "Hello, World!";
}
static void myStaticMethod() {
System.out.println("I'm a static method in an interface");
}
}
有一个特例Comparator 接口,他有两个抽象方法但是是功能接口,用作排序,其他章节还会讲到这个接口。如下,equals 来自于Object。因此有个默认的实现。文档上说为了性能的原因你可以提供equals 实现,但是不要重现这个方法是安全的的。方法只要来自于Object ,就不会违反单抽象方法的要求,因此Comparator 任然是功能接口
Default Methods in Interfaces
public interface Employee {
String getFirst();
String getLast();
void convertCaffeineToCodeForMoney();
default String getName() {
return String.format("%s %s", getFirst(), getLast());
}
}
Static Methods in Interfaces
List<String> bonds = Arrays.asList("Connery", "Lazenby", "Moore",
"Dalton", "Brosnan", "Craig");
List<String> sorted = bonds.stream()
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
// [Brosnan, Connery, Craig, Dalton, Lazenby, Moore]
sorted = bonds.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// [Moore, Lazenby, Dalton, Craig, Connery, Brosnan]
sorted = bonds.stream()
.sorted(Comparator.comparing(String::toLowerCase))
.collect(Collectors.toList());
// [Brosnan, Connery, Craig, Dalton, Lazenby, Moore]
sorted = bonds.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
// [Moore, Craig, Dalton, Connery, Lazenby, Brosnan]
sorted = bonds.stream()
.sorted(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()))
.collect(Collectors.toList());
// [Craig, Moore, Dalton, Brosnan, Connery, Lazenby]