java 8 学习二(Lambda表达式)
粗略的概括:lambda表达式主要用来实现“函数接口”中“唯一”的抽象方法用的。
他的特殊版有 方法引用,构造函数引用,用对应的接口实例接收即可。
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式 它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
- 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样, Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁——无需像匿名类那样写很多模板代码。
lambda表达式的5种方式
public void process(Runnable r){ r.run(); } process(() -> System.out.println("This is awesome!!"));
为什么只有在需要函数式接口的时候才可以传递Lambda呢?
函数式接口
函数式接口就是只定义一个抽象方法的接口;哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
一般来说最好用@FunctionalInterface 注解标注一下。
在哪里可以使用Lambda?
第三个例子的Predicate接口的定义如下:
public interface Predicate<T>{ boolean test (T t); }
环绕模式没看懂
通用函数式接口
Predicate、 Consumer和Function
- java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
- java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void) 。
- java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
泛型的一个坑
1.Java类型要么是引用类型(比如Byte、 Integer、 Object、 List) ,要么是原始类型(比如int、 double、 byte、 char)。但是泛型(比如Consumer<T>中的T)只能绑定到引用类型。
2.原始类型也是可以使用的,但是会在内部被封装为引用类型---作者强调---
这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
所以针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,接口如下:
总结关于函数式接口和Lambda的使用案例、 Lambda的例子,以及可以使用的函数式接口。
Lambda,还有函数式接口 的异常处理
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException;//接口中抛出异常 }
//接口无法抛出的,try catch 包裹
Function<BufferedReader, String> f = (BufferedReader b) -> {
try {
return b.readLine();
}
catch(IOException e) {
throw new RuntimeException(e);
}
};
困惑
当我们第一次提到Lambda表达式时,说它可以为函数式接口生成一个实例。然而, Lambda表达式本身并不包含它在实现哪个函数式接口的信息。为了全面了解Lambda表达式,你应该知道Lambda的实际类型是什么。
类型检查、类型推断以及限制
有了目标类型的概念,同一个Lambda表达式就可以与不同的函数式接口联系起来,只要它们的抽象方法签名能够兼容。比如,前面提到的Callable和PrivilegedAction,这两个接口都代表着什么也不接受且返回一个泛型T的函数。 因此,下面的赋值是有效的:
Callable<Integer> c = () -> 42; PrivilegedAction<Integer> p = () -> 42;
Comparator<Apple> c1 =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
ToIntBiFunction<Apple, Apple> c2 =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
所以需要从赋值的上下文、方法调用的上下文(参数和返回值) 来判断lambda表达式。
类型推断
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
//有时候显式写出类型更易读,有时候去掉它们更易读。 这两种都可以编译。
List<Apple> greenApples =
filter(inventory, a -> "green".equals(a.getColor()));
//当Lambda仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略。
局部变量的限制(懵懂)
下面的代码无法编译
错误: Lambda表达式引用的局部变量必须是最终的( final)或事实上最终的
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber); portNumber = 31337;
原书中是这样描述的
方法引用
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法。
方法引用主要有三类。
1.指向静态方法的方法引用
(例如Integer的parseInt方法,写作Integer::parseInt)
2.指 向 任 意 类 型 实 例 方 法 的 方 法 引 用
( 例 如 String 的 length 方 法 , 写 作String::length)
3.指向现有对象的实例方法的方法引用
(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。
将lambda表达式重构为等价的方法引用
List<String> str = Arrays.asList("a","b","A","B"); str.sort((s1, s2) -> s1.compareToIgnoreCase(s2)); //等价与 List<String> str = Arrays.asList("a","b","A","B"); str.sort(String::compareToIgnoreCase);
构造函数引用
如果就new 一个对象感觉这样有点啰嗦,如果new多个对象,确实省点代码。
构造函数引用的例子:
---------------------------------------------------------------------------------------------------------------------------------
看个例子:
需求--用不同的排序策略给一个Apple列表排序
//第一版正常java代码:
public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } inventory.sort(new AppleComparator()); //inventory是一个List的实例,下同
//第二版 匿名内部类
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
//第三版 lambda表达式
inventory.sort((Apple a1, Apple a2)-> a1.getWeight().compareTo(a2.getWeight()));
或者
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
或者
Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象(接口的静态方法) Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
现在你可以把代码再改得紧凑一点了:
import static java.util.Comparator.comparing;
inventory.sort(comparing((a) -> a.getWeight()));
//第四版(最终版)
import static java.util.Comparator.comparing;
inventory.sort(comparing(Apple::getWeight));
---------------------------------------------------------------------------------------------------------------------------------
复合Lambda表达式
比较器复合 --Comparator.comparing 的内置方法
inventory.sort(comparing(Apple::getWeight) //comparing reversed thenComparing 方法返回仍然是Comparator所以可以继续调用内置方法
.reversed()
.thenComparing(Apple::getCountry));
谓词复合 --Predicate 接口自带的 and or negate isEqual方法
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor()));
函数复合 --Function接口的andThen和compose两个默认方法
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2;
//先加1在乘2
Function<Integer, Integer> h = f.andThen(g); 数学上会写作g(f(x))
//先乘2在加1
Function<Integer, Integer> h = f.compose(g); 数学上会写作f(g(x))
int result = h.apply(1);