这两天看了《Java8实战》,做一下记录。
目录
一、行为参数化:
1.什么是行为参数化:
1. 你说:我要所有重量为 50g 的苹果。管理员调用 findByWeight(int weight) 拿给你。
2. 你又说你要红色的所有苹果,管理员调用 findByColor(String color) 帮你找到。
3. 这时你又要重量为 50g 并且为红色的所有苹果,管理员:???。
4.现在只好请来程序员带哥,修改程序加上一个 findByWeightAndColor(int weight, String color)。
有了 findByWeightAndColor 还要 findByWeight 和 findByColor,这就打破了Don't Repeat Yourself(不要重复你自己)的软件工程原则。于是考虑将这两个需求结合成一个方法。用两个 boolean 参来控制筛选条件,具体逻辑就不写了。
findByWeightAndColor(int weight, boolean useWeight, String color, boolean useColor
);
这样写不但臃肿,而且如果需要刷选大于50克的苹果,那还得重新写一个方法。这是不能接收的。
以上例子是把值作为参数进行查找,对于不同需求需要写不同的方法,每种查找方式都需要写一个方法,这显然是不合理的。这时程序员带哥就想到,为什么不能只写一个方法,把需求也就是行为当做参数进行传递呢?
①此时,只需要写一个过滤器接口,里面写一个抽象过滤方法,而过滤条件自己定义就好了。
public interface AppleFilter {
boolean filter(Apple apple);
}
②把自己写好的过滤条件 appleFilter 当做参数传进去。
pubic List<Apple> getApple(List<Apple> list, AppleFilter appleFilter){
if (appleFilter.filter(apple)){
......
}
.....
}
③自定义过滤重量 >50 克且颜色为 red 过滤器。这不就是熟悉的匿名内部类吗(匿名内部类需要注意这几点)。
List<Apple> list1 = obj.getApple(list, new AppleFilter() {
@Override
public boolean filter(Apple apple) {
if ("red".equals(apple.getColor) && apple.getWeight()>50){
return true;
}
return false;
}
});
以上就是行为参数化。
二、函数式接口:
1.概念:
① 如果一个接口只有一个抽象方法,那它就是函数式接口。(注意是抽象方法)
② 加了@FunctionalInterface 注解的接口就是函数式接口,但得符合条件①才行。
这样看来,上面定义的过滤器接口就是一个函数式接口
public interface AppleFilter {
boolean filter(Apple apple);
}
Java库中也有定义好的函数式接口:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
三、Lambda表达式:
现在就要引出 Lambda表达式了:
1.Lambda 必须依附在函数式接口上。
2.Lambda 实际上是一种语法糖。
List<Apple> list1 = obj.getApple(list, new AppleFilter() {
@Override
public boolean filter(Apple apple) {
.......
}
});
对于上文的代码,可以用 Lambda 表达式来写。
List<Apple> list1 = c.getApple(list,
(Apple apple) -> apple.getWeight()>50 && "red".equals(apple.getColor()));
也可以这样写:
AppleFilter appleFilter = apple -> "red".equals(apple.getColor()) && apple.getWeight()>50;
List<Apple> list1 = obj.getApple(list, appleFilter);
再如,新建一个线程执行打印:
new Thread(()-> sout("hello world")).start();
Lambda表达式必须通过上下文进行推断,推断过程:
四、方法引用(注意点):
1.静态方法引用:
Function<String, Integer> function = s -> Integer.parseInt(s);
↓
Function<String, Integer> function = Integer::parseInt;
int num = function.apply("123");
2.实例方法引用(重点来了):
①指向任意类型的实例方法引用:
Function<String, Integer> function = s -> s.length();
//可以这样写
Function<String, Integer> function = String::length;
//参数写进去
function.apply("qwe");
②指向现有对象的实例方法引用
public int getValue(int num) {
return this.hashCode() - num;
}
Apple apple = new Apple();
Function<Integer, Integer> function = num -> apple.getValue(num);
//可以写成这样
Function<Integer, Integer> function1 = apple1::getValue;
System.out.println((apple.hashCode()-3) == function.apply(3));//true
这究竟是什么意思?我比较浅显推断应该是这样(如果不对请指正):
①任意类型的实例方法引用:
Lambda:( arg0, rest ) -> arg0 .method ( rest ) 对应上面代码 s -> s.length();
方法引用: ClassName :: method 对应上面代码 String::length;
:: 前面的类型和后面的方法名可以用来匹配参数,返回值就是 :: 后面的方法的返回值,通过 :: 前面的类型可以创建一个对象 obj。如上面代码 String::length,通过String可以创建一个String对象,后面的 length() 不需要参数。所以 function.apply("qwe"), "qwe"就是传入的对象;返回值就是由 obj.length() 计算所得。
②现有对象的实例方法引用:
Lambda: (args) -> expr . method(args) 对应上面代码 function = num -> apple.getValue(num);
方法引用: expr :: method 对应上面代码 function = apple1 :: getValue;
function.apply(3)。:: 前面已经有具体对象,所以直接 apple1.getValue(3) 获得返回值。
再看一个究极例子加深理解(参考文章):
public interface FunctionInterface {
public int calculate(Bean1 bean1, Bean2 bean2);
}
public class Bean1 {
public void expect1(Bean1 bean1) {}
public int expect2(Bean2 bean2){
return this.hashCode() - bean2.hashCode();
}
public void test1(FunctionInterface i) {}
}
public class Bean2 {
public void expect1(Bean1 bean1) {}
public int expect2(Bean2 bean2) {}
public void test1(FunctionInterface i) {}
}
测试代码:
public static void main(String[] args) {
FunctionInterface interface1 = (Bean1 bean1, Bean2 bean2) -> bean1.hashCode() - bean2.hashCode();
FunctionInterface interface2 = Bean1::expect2;
Bean1 bean1 = new Bean1();
Bean2 bean2 = new Bean2();
//计算结果是一样的
System.out.println(bean1.hashCode() - bean2.hashCode());
System.out.println(interface1.calculate(bean1, bean2));//1.
System.out.println(interface2.calculate(bean1, bean2));//2.
}
1式:正宗的 Lambda 表达式,不用多说。
2式:这个就有意思了,Bean1 :: expect2。根据上文分析,:: 生成Bean1类型的一个对象,调用 expect2 方法,expect2 方法中有一个 Bean2 类型的参数,要想执行 calculate 方法,那么到目前还缺一个参数。其实并不缺,也就是说省略了Bean1类型的参数bean1,lambda 就可以使用:: 前导的Bean1构建一个对象,作为第一个参数。
3.构造方法引用:
Supplier supplier = () -> new Apple();
↓
Supplier supplier = Apple::new;