1.行为参数化
jdk8中引入了一个很重要的思想:行为参数化:可以将方法(代码)作为参数传递给另一个方法.是可以帮助你处理频繁变更的需求的一种软件开发模式.
思考引入:筛选出库存中绿色的苹果?
方式1:
但是现在需求变了,要筛选出库存中红色的苹果.复制这个方法,改一下名字,修改if中的条件判断即可.但是要是筛选出浅绿色,暗红色,灰色呢?这种方法就不适用了.
方式2:
这样就能满足筛选颜色的需求了.但是此时需求又变了.要是能筛选出大苹果就好了,大苹果一般是重量大于150克的.
所以你写了下面的方法:
这个方法不错,但是,你复制了大部分的方法来实现遍历.到了DRY(Don’t repeat yourself)的软件工程原则.如果你想要改变筛选遍历方式来提升性能呢?那就的修改所有方法实现,从工程工作量来看,代价太大.
方式3:
一种把所有属性结合起来的笨拙的尝试:
(这种方法极度笨比).如果我要是加上大小,产地等要求会怎么样?根本无法扩展.
上述问题看出,我们需要一种比添加很多参数更好的方法来应对变化的需求.更高层次的抽象:你考虑的是苹果,需要根据Apple的某些属性(颜色是绿色吗?重量超过150克了吗…)来返回一个boolean值.我们把它称为谓词(返回boolean值的函数)
.
定义一个接口来对选择标准建模:
public interface ApplePredicate{
boolean test (Apple apple);
}
基于此接口,来实现筛选红色的大苹果:
方式4:
然后:
乍一看,这种方式好像也很麻烦:需要定义一个接口,然后根据不同需求定义不同实现类ApplePredicate,然后在方法中将ApplePredicate作为参数传入.
但是,这和方法1和方法2有很大不同:现在你把 filterApples 方法迭代集合的
逻辑与你要应用到集合中每个元素的行为(这里是一个谓词)区分开了。filterApple方法的行为取决于你通过ApplePredicate对象传递的代码.
换句话说:你把filterPredicate方法的行为参数化了.
遗憾的是,filterApple方法只能传递对象作为参数,你必须把你的代码包裹在ApplePredicate对象中,这种做法类似于在内联"传递代码".但是在后面的lambda表达式可以优化这种代码.
正如前面说的那样:行为参数化好处在于你可以把要筛选的集合的逻辑与集合每个元素的行为区分开来.你可以重复使用同一个方法,给他不同行为来达到不同的目的.多种行为,一个参数
再看,上述的例子,定义接口,一种要求,我们就需要编写一个实现类实现接口方法,多种要求我们就要写多个实现类.而这些类可能只用一次.这真的很啰嗦也很费时间.如果这个类只使用一次,我们可以使用匿名内部类.将方法4优化成这样:
方式5
这种方法也不是很好,因为它往往很笨重,因为它占用了空间.
除了浅色行的代码,其他地方都是重复的.
而且匿名内部类有时会让人很费解.比如下面测试:
结果是5.
继续优化,使用jdk8提供的Lambda表达式:
方式6:
筛选出红色的苹果:
这样的代码,即实现了参数类型化,也不用重复创建实现类,匿名内部类.
总之:行为参数化很重要就完事了.
2.Lambda表达式
可以把Lambda表达式理解为:简洁地表示可传递的匿名函数的一种方式:它没有名称,但是又参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表。
- 匿名:它不像一个普通方法有确切方法名。写的少,想的多。
- 函数:它是函数,但是它不像普通方法一样属于特定的某个类。它有参数,方法体,返回结果,还可能抛出异常列表。
- 传递:Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁:不需要像匿名内部类那样写很多模板代码。
举个栗子:
之前:
Comparator<Apple> byWeight = new Comparator<Apple>({
public int compare(Apple a1,Apple a2){
return a1.getWeight.compareTo(a2.getWeight);
}
});
之后(Lambda):
Comparator<Apple> byWeight = (Apple a1,Apple a2) -> a1.getWeight.compareTo(a2.getWeight);
在上面的Lambda表达式有三个部分:
- 参数列表。
- 箭头,箭头 ->把参数列表与Lambda主体分隔开。
- Lambda主体。
Lambda基本语法:
(paramters) -> expression
或者(注意语句的花括号):
(paramters) -> {statements;}
之一理解上面两个格式中的expression和statements的不同;
expression是表达式,statements是语句。
下面两个例子都是错误的:
// return 是流程控制语句,必须用{}包围起来
1. (Integer i) -> return "hello" +1;
//"Ironman" 是一个表达式,不是语句。把{}和;都删除或者在{}里加个return,变成一个语句。
2. (String s) -> {"Ironman" ;}
那么,到底哪里可以使用Lambda表达式呢?
答:你可以在函数式接口上使用Lambda表达式。
所以,什么是函数式接口?
3.函数式接口
一言以蔽之:函数式接口就是:只定义了一个抽象方法
的接口。(惊不惊喜,意不意外。。。)
java API中的一些函数式接口:
注意:jdk8以后,接口可以有默认方法(即类没有对方法进行实现的时候,其主体为方法提供默认实现的方法)。但是无论有多少个默认方法,只有只有一个抽象方法,就是函数式接口。
函数式接口可以干什么?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例
(具体来说,是函数式接口是个具体实现(类)的实例(对象))。
来个栗子:
public class Test {
public static void main(String[] args) {
// 当你在ideal上编写这种代码时,编译器就会提示用Lambda表达式来改写这段代码;所以,学习jdk8很有必要。
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("方式1");
}
};
Runnable r2 = () -> System.out.println("方式2");
process(r1);
process(r2);
process(() -> System.out.println("方式3"));
}
public static void process(Runnable r) {
r.run();
}
}
4.把Lambda表达式付诸实践:环绕执行模式
什么是环绕模式?
资源处理(如处理文件和数据)时一个常见的模式就是:打开资源,处理资源,关闭资源。其中,设置(打开资源)和清理阶段(关闭资源)总是很类似,并且总是围绕着执行处理的那些重要代码。这就是所谓的环绕模式。
上个图:
操作步骤:
- 行为参数化。这段代码具有局限性,只能读取第一行,如果要读取前两行?返回最频繁的此?你需要重复写设置和清理阶段的代码,而只修改具体的操作行为。那么你可修改这个方法,把操作的行为作为参数传入这个方法就好了。
- 使用函数式接口来传递行为。Lambda只能用于上下文是函数式接口的情况。所以你需要创建一个包含处理的抽象方法的函数式接口。
- 执行一个行为。将Lambda表达式作为一个函数式接口的一个实现。
- 传递Lambda
5.使用函数式接口
JDK8的设计师在java.util.function包下,引入了几个新的函数式接口.
5.1Predicate
java.util.function.Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型T对象,并返回一个 boolean 。在你需要涉及一个类型T的boolean表达式时,可以使用这个接口.
举个栗子:
public class Test {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
strings.add("hello");
strings.add("");
strings.add("world");
System.out.println(strings.size());
List<String> filters = filters(strings, (String s) -> !s.isEmpty());
System.out.println(filters.size());
}
public static List<String> filters(ArrayList<String> list, Predicate<String> predicate) {
ArrayList<String> result = new ArrayList<>();
for (String s : list) {
if (predicate.test(s)){
result.add(s);
}
}
return result;
}
}
输出结果:
3
2
5.2Consumer
java.util.function.Consumer 定义了一个名叫 accept 的抽象方法,它接受泛型T的对象,没有返回( void )。你如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。
举个栗子:
public class Test {
public static void main(String[] args) {
forEach (Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i) );
}
public static <T> void forEach(List<T> list, Consumer<T> consumer) {
for (T t : list) {
consumer.accept(t);
}
}
}
输出:
1
2
3
4
5
5.3Function
java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。
再来个栗子:
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("520");
List<Integer> map = map(list, (String s) -> s.length() + 1);
System.out.println(map);
}
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
ArrayList<R> objects = new ArrayList<>();
for (T t : list) {
objects.add(function.apply(t));
}
return objects;
}
}
输出:[6, 6, 4]
6.方法引用
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。
方法引用的符号是::
来个栗子:
方法引用可以被看作是仅仅调用特定方法的lambda的一种快捷写法.它的基本思想是:如果一个lambda代表的是"直接调用这个方法",那你还是直接调用它(上面截图的之后写法),而不是描述如何调用它(上面截图的之前写法).事实上,方法引用就是让你根据已有的方法来创建lambda.
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("520");
// 注意和5.3的例子做对比,就改动了这一行
List<Integer> map = map(list, String::length);
System.out.println(map);
}
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
ArrayList<R> objects = new ArrayList<>();
for (T t : list) {
objects.add(function.apply(t));
}
return objects;
}
}
方法引用主要有三类:
- 指向静态方法的方法引用:(例如 Integer 的 parseInt 方法,写作 Integer::parseInt )。
- 指向任意类型实例方法的方法引用:( 例 如 String 的 length 方 法 , 写 作
String::length )。 - 指向现有对象的实例方法的方法引用:(假设你有一个局部变量 expensiveTransaction用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensiveTransaction::getValue )。
注意一下第二种和第三种区别:
类似于Sting::length的第二种方法引用的思想就是:你在引用一个对象的方法,而这个对象本身是lambda的一个参数.例如,Lambda表达式 (String s) -> s.toUppeCase() 可以写作String::toUpperCase 。
第三种方法引用指的是:你在lambda表达式中调用一个已经存在的外部对象的方法.例如,Lambda表达式()->expensiveTransaction.getValue() 可以写作 expensiveTransaction::getValue 。
再来个对比图:
小结: