20180629 Java Lambda
- lambda从Java8开始
基础语法
expression = (variable) -> action
- variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
- action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。
函数式接口
- 我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即 单抽象方法类型(Single Abstract Method))
- 注解
@FunctionalInterface
标识一个接口为函数式接口,被标识的接口只能有一个抽象方法(除了已经在Object中定义的方法),否则会报错 - 不标识@FunctionalInterface的接口,但事实上也是函数式接口的,依然可以使用lambda表达式
- JDK中的函数式接口位置:
java.util.function
,包含了常用的函数式接口:
Predicate
——接收 T
并返回boolean
Consumer——接收 T
,不返回值
Function<T, R>——接收T
,返回R
Supplier——提供 T
对象(例如工厂),不接收值
UnaryOperator——接收 T
对象,返回T
BinaryOperator——接收两个 T
,返回T
- Java SE 7 中已经存在的函数式接口
java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator
java.io.FileFilter
- 除了上面的这些基本的函数式接口,还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如
IntSupplier
和LongBinaryOperator
,还有很多在java.util.function
包中,应该每一个都有所了解
四大核心函数式接口:
消费型接口示例
public static void donation(Integer money, Consumer<Integer> consumer){
consumer.accept(money);
}
public static void main(String[] args) {
donation(1000, money -> System.out.println("好心的麦乐迪为Blade捐赠了"+money+"元")) ;
}
供给型接口示例
public static List<Integer> supply(Integer num, Supplier<Integer> supplier){
List<Integer> resultList = new ArrayList<Integer>() ;
for(int x=0;x<num;x++)
resultList.add(supplier.get());
return resultList ;
}
public static void main(String[] args) {
List<Integer> list = supply(10,() -> (int)(Math.random()*100));
list.forEach(System.out::println);
}
函数型接口示例
public static Integer convert(String str, Function<String, Integer> function) {
return function.apply(str);
}
public static void main(String[] args) {
Integer value = convert("28", x -> Integer.parseInt(x));
}
断言型接口示例
public static List<String> filter(List<String> fruit, Predicate<String> predicate){
List<String> f = new ArrayList<>();
for (String s : fruit) {
if(predicate.test(s)){
f.add(s);
}
}
return f;
}
public static void main(String[] args) {
List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃");
List<String> newFruit = filter(fruit, (f) -> f.length() == 2);
System.out.println(newFruit);
}
自定义函数式接口
- 实现简单加法
public class Test2 {
/*
一个接口,如果只有一个显式声明的抽象方法,
那么它就是一个函数接口。
一般用@FunctionalInterface标注出来(也可以不标)
*/
interface Inteface1 {
// 可以不用abstract修饰
public abstract void test(int x, int y);
// public void test1();//会报错,不能有两个方法,尽管没有使用abstract修饰
public boolean equals(Object o);// equals属于Object的方法,所以不会报错
}
public static void main(String args[]) {
Inteface1 f1 = (x, y) -> {
System.out.println(x + y);
};
f1.test(3, 4);
Inteface1 f2 = (int x, int y) -> {
System.out.println("Hello Lambda!\t the result is " + (x + y));
};
f2.test(3, 4);
}
}
lambda表达式
- lambda 表达式的语法由参数列表、箭头符号
->
和函数体组成。函数体既可以是一个表达式,也可以是一个语句块 - 出现在语句中的lambda表达式
FileFilter java = (File f) -> f.getName().endsWith("*.java");
String user = doPrivileged(() -> System.getProperty("user.name"));
new Thread(() -> {
connectToService();
sendNotification();
}).start();
目标类型(Target typing)
- 同样的 lambda 表达式在不同上下文里可以拥有不同的类型
Callable<String> c = () -> "done";
PrivilegedAction<String> a = () -> "done";
第一个 lambda 表达式 () -> "done" 是Callable
的实例,而第二个 lambda 表达式则是PrivilegedAction
的实例。
编译器负责推导 lambda 表达式类型。
lambda 表达式的参数类型可以从目标类型中得出。
当 lambda 的参数只有一个而且它的类型可以被推导得知时,该参数列表外面的括号可以被省略:
FileFilter java = f -> f.getName().endsWith(".java");
button.addActionListener(e -> ui.dazzle(e.getModifiers()));
目标类型的上下文(Contexts for target typing)
- lambda 表达式本身也可以为它自己的函数体提供目标类型,也就是说 lambda 表达式可以通过外部目标类型推导出其内部的返回类型,这意味着我们可以方便的编写一个返回函数的函数:
Supplier<Runnable> c = () -> () -> { System.out.println("hi"); };
- 条件表达式可以把目标类型“分发”给其子表达式:
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);
- 转型表达式(Cast expression)可以显式提供 lambda 表达式的类型,这个特性在无法确认目标类型时非常有用:
// Object o = () -> { System.out.println("hi"); }; 这段代码是非法的
Object o = (Runnable) () -> { System.out.println("hi"); };
词法作用域(Lexical scoping)
- lambda 表达式函数体里面的变量和它外部环境的变量具有相同的语义,不同于内部类
public class Hello {
Runnable r1 = () -> {
System.out.println(this);
};
Runnable r2 = new Runnable() {
public void run() {
System.out.println(this);
}
};
public String toString() {
return "Hello, world";
}
public static void main(String... args) {
new Hello().r1.run(); // Hello, world
new Hello().r2.run(); // lambda.Hello$1@1fb3ebeb
}
}
变量捕获(Variable capture)
- 类似于在匿名类内部引用函数局部变量,必须将其声明为final
public static Supplier<Integer> testClosure() {
int i = 1;
// i++; // 会报错
return () -> {
return i;
};
}
方法引用(Method references)
- 不需要为方法引用提供方法体,可以直接通过方法名称引用已有方法
Comparator<Person> byName = Comparator.comparing(p -> p.getName());
Comparator<Person> byName = Comparator.comparing(Person::getName); // 缩写形式
- 因为函数式接口的方法参数对应于隐式方法调用时的参数,所以被引用方法签名可以通过放宽类型,装箱以及组织到参数数组中的方式对其参数进行操作,就像在调用实际方法一样:
Consumer<Integer> b1 = System::exit; // void exit(int status)
Consumer<String[]> b2 = Arrays::sort; // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)
Runnable r = Myprogram::mapToInt // void mapToInt()
方法引用的种类(Kinds of method references)
- 静态方法引用:
ClassName::methodName
- 实例上的实例方法引用:
instanceReference::methodName
- 超类上的实例方法引用:
super::methodName
- 类型上的实例方法引用:
ClassName::methodName
- 构造方法引用:
Class::new
- 数组构造方法引用:
TypeName[]::new
默认方法和静态接口方法(Default and static interface methods)
- 默认方法 利用面向对象的方式向接口增加新的行为。它是一种新的方法:接口方法可以是 抽象的 或是 默认的。默认方法拥有其默认实现,实现接口的类型通过继承得到该默认实现(如果类型没有覆盖该默认实现)。此外,默认方法不是抽象方法,所以我们可以放心的向函数式接口里增加默认方法,而不用担心函数式接口的单抽象方法限制。
- 除了默认方法,Java SE 8 还在允许在接口中定义 静态 方法。这使得我们可以从接口直接调用和它相关的辅助方法(Helper method),而不是从其它的类中调用(之前这样的类往往以对应接口的复数命名,例如 Collections)
- 理解这两种方法的一个实例:
java.util.Comparator<T>
使用实例
Collections.sort(list, new Comparator<Person>() {
public int compare(Person x, Person y) {
return x.getLastName().compareTo(y.getLastName());
}
});
Collections.sort(list, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));
Collections.sort(list, Comparator.comparing((Person p) -> p.getLastName()));
Collections.sort(list, Comparator.comparing(p -> p.getLastName()));
Collections.sort(list, Comparator.comparing(Person::getLastName));
list.sort(Comparator.comparing(Person::getLastName));