1 行为参数化
行为参数化可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为将方法的行为参数化。
行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
可以行为参数化的类型有:类、匿名类、lambda
先贴出测试的一段代码:
package cn.com.trying.test.util;
import com.google.common.collect.Lists;
import java.io.File;
import java.io.FileFilter;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class LambdaTest {
public static void main(String args[]) {
//查找隐藏文件的两种写法
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isHidden();
}
});
File[] hFiles = new File(".").listFiles(File::isHidden);
Arrays.sort(hFiles,(File a, File b) ->{return a.compareTo(b);});
//---------------------------------------------------------------------------------
List<Apple> list = Lists.newArrayList();
Apple apple = new Apple();
apple.setColor("green");
apple.setWeight(100);
list.add(apple);
Apple apple1 = new Apple();
apple1.setColor("green");
apple1.setWeight(200);
list.add(apple1);
List<Apple> greenList = filterApples(list,LambdaTest::isGreenApple);
List<Apple> heavyList = filterApples(list,LambdaTest::isHeavyApple);
List<Apple> greenList1 = filterApples(list,(Apple a) -> "green".equals(a.getColor()));
List<Apple> heavyList1 = filterApples(list,(Apple a) -> a.getWeight() > 150 && "green".equals(a.getColor()));
List<Apple> heavyList3 = (List<Apple>)filter(list,(Apple a) -> a.getWeight() > 100);
List<Apple> list4 = list.stream().filter((Apple a)-> a.getWeight() > 100).collect(Collectors.toList());
long count = list.stream().filter((Apple a)-> a.getWeight() > 100).count();
long count1 = list.parallelStream().filter((Apple a)-> a.getWeight() > 100).count();
System.out.println(stringOp(LambdaTest::getStr,"ddddd"));
//方法引用 lambda表达式 流 默认方法 行为参数化
}
public static String stringOp(LambdaFunc sf, String s) {
return sf.stringFunc(s);
}
private static String getStr(String str){
int count = str.length();
System.out.println(count);
return count+"";
}
public static boolean isGreenApple(Apple apple){
return "green".equals(apple.getColor());
}
public static boolean isHeavyApple(Apple apple){
return apple.getWeight() > 150;
}
static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> predicate){
List<Apple> list = Lists.newArrayList();
for( Apple apple : inventory){
if(predicate.test(apple)){
list.add(apple);
}
}
return list;
}
static <T> Collection<T> filter(Collection<T> c, Predicate<T> p){
Collection<T> collection = Lists.newArrayList();
for(T t : c){
if(p.test(t)){
collection.add(t);
}
}
return collection;
}
}
2 Lambda表达式
2.1 lambda表达式示例:
(String s) -> s.length()
(Apple a) -> a.getWeight() > 150
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x+y);
}
() -> 42
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
2.2 lambda使用场景
1.在函数式接口上使用Lambda表达式。
2.函数式接口就是只定义一个抽象方法的接口
3.Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法实现,并把整个表达式作为函数式接口的实例。
2.3 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符
@FunctionalInterface 这个标注用于表示该接口会设计成一个函数式接口。如果你用 @FunctionalInterface 定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。
2.4 Lambda实践:环绕执行模式
- 行为参数化 将差异化的操作代码规整出来,作为行为参数传递,
- 定义函数式接口来传递行为。
/**
* 函数式接口
*/
public interface LambdaFunc {
/**
* @Title:
* @Description: 字符串处理
* @param
* @return
* @author huxx
* @date 2020/3/16 上午10:34
* @update
*/
String handleString(String n);
}
- 执行行为,在方法体内执行Lambda所代表的代码。Lambda表达式运行你直接内联,为函数式接口的抽象方法提供实现,并将整个表达式作为函数式接口的一个实例。
public static String stringOp(LambdaFunc sf, String s) {
return sf.handleString(s);
}
- 传递Lambda ,通过传递不同的Lambda表达式实现方法实现不同的业务逻辑。
System.out.println(stringOp((String str) -> str.length()+"---"+str.trim(),"ddddd"));
2.5 预定义的几个函数式接口
1、Predicate
java.util.function.Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型T 对象,并返回一个 boolean 。这恰恰和你先前创建的一样,现在就可以直接使用了。在你需要表示一个涉及类型 T 的布尔表达式时,就可以使用这个接口。
2、Consumer
java.util.function.Consumer 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回( void )。你如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。比如,你可以用它来创建一个 forEach 方法,接受一个 Integers 的列表,并对其中每个元素执行操作。
3、Function
java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个
泛型 T 的对象,并返回一个泛型 R 的对象。如果你需要定义一个Lambda,将输入对象的信息映射
到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。
public static String handleStr(Function<String,String> f, String s){
return f.apply(s);
}
System.out.println(handleStr((String str) -> str.length()+"---"+str.trim(),"ddddd"));
2.6 原始类型特化
上面三个泛型函数式接口: Predicate 、 Consumer 和 Function<T,R> 。还有些函数式接口专为某些类型而设计
java类型要么是引用类型(比如Byte、Integer、Object、List),要么是原始类型(比如int、double、byte、char)。但是泛型(比如 Consumer 中的 T )只能绑定到引用类型。这是由泛型内部的实现方式造成的。
在Java里有一个将原始类型转换为对应的引用类型的机制。这个机制叫作装箱(boxing)。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱(unboxing)。Java还有一个自动装箱机制来帮助程序员执行这一任务:装箱和拆箱操作是自动完成的。
装箱和拆箱在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如 DoublePredicate 、 IntConsumer 、 LongBinaryOperator 、 IntFunction 等。 Function接口还有针对输出参数类型的变种: ToIntFunction 、 IntToDoubleFunction 等。
IntPredicate evenNumbers = (int i) -> i % 2 == 0;
evenNumbers.test(1000); //不需要装箱
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1;
oddNumbers.test(1000);// 需要装箱
2.7 常用函数式接口
函数式接口 | 函数描述符 | 原始类型特化 |
---|---|---|
Predicate | T->boolean | IntPredicate,LongPredicate, DoublePredicate |
Consumer | T->void | IntConsumer,LongConsumer, DoubleConsumer |
Function<T,R> | T->R | IntFunction,IntToDoubleFunction,IntToLongFunction,LongFunction,LongToDoubleFunction,LongToIntFunction,DoubleFunction,ToIntFunction,ToDoubleFunction,ToLongFunction |
Supplier | ()->T | BooleanSupplier,IntSupplier, LongSupplier,DoubleSupplier |
UnaryOperator | T->T | IntUnaryOperator,LongUnaryOperator,DoubleUnaryOperator |
BinaryOperator | (T,T)->T | IntUnaryOperator,LongUnaryOperator,DoubleUnaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | (T,U)>void | IntBinaryOperator,LongBinaryOperator,DoubleBinaryOperator |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U>,ToLongBiFunction<T,U>,ToDoubleBiFunction<T,U> |
2.8 使用局部变量
Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。 它们被称作捕获Lambda
关于能对这些变量做什么有一些限制。Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为 final ,或事实上是 final 。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。
String testVar = "i am a variable";
System.out.println(handleStr((String str) -> str.length()+"---"+testVar,"ddddd"));
为什么局部变量有这些限制。第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
3 方法引用
3.1 方法引用介绍
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。
//Lambda表达式写法
System.out.println(stringOp((String str) -> str.length()+"---"+str.trim(),"ddddd"));
//方法引用写法
System.out.println(handleStr(LambdaTest::test,"ddddd"));
public static String test(String str){
return str.length()+"---"+str.trim();
}
可以把方法引用看作针对仅仅涉及单一方法的Lambda的语法糖,因为你表达同样的事情时要写的代码更少了。
3.2 方法引用三种方式
1.指向静态方法的方法引用(例如 Integer 的 parseInt 方法,写作 Integer::parseInt )。
2.指 向 任 意 类 型 实 例 方 法 的 方 法 引 用 ( 例 如 String 的 length 方 法 , 写 作String::length )。
3.指向现有对象的实例方法的方法引用(假设你有一个局部变量 expensiveTransaction用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensive-Transaction::getValue )。