行为参数化
官方解释:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为。
代码解释:
class Apple{
private String colors;
private int weigth;
public String getColors() {
return colors;
}
public void setColors(String colors) {
this.colors = colors;
}
public int getWeigth() {
return weigth;
}
public void setWeigth(int weigth) {
this.weigth = weigth;
}
}
interface ApplePredicate{
boolean test(Apple apple);
}
public class AppleRedAndHeavyPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColors())&& apple.getWeigth()>150;
}
}
图片解释:
行为参数化的好处:可以轻松的应对不断变化的需求,这种模式可以把一个行为一段代码封装起来,并通过传递和使用创建行为将方法行为参数化。
lambda表达式
1.初识lambda
可以把lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表,函数主题,返回类型,可能还有一个可以抛出的异常列表。
- 匿名—说匿名,是因为它不像 普通的方法那样有一个明确的名称:写得少而想的多。
- 函数—说函数,是因为lambda函数不像方法那样属于某个特定的类。但和方法一样,lambda有参数列表,函数主体,返回类型,还可能有可以抛出的异常列表。
- 传递—lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁—无需像匿名函数那样写很多模板代码。
箭头左边为lambda参数,箭头右边为lambda主体。
基本语法:(parameters)->expression或者(parameters)->{statements;}
先前:
Comparator<Apple> byWeight=new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeigth().compareTo(o2.getWeigth());
}
};
之后(用了lambda表达式):
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
使用案例:
使用案例 | lambda示例 | 对应的函数式接口 |
---|---|---|
布尔表达式 | (List<String> list)->list.isEmpty(); | Predicate<List<String>> |
创建对象 | ()->new Apple(10) | Supplier<Apple> |
消费一个对象 | (Apple a)->{System.out.println(a.getWeitht());</br>} | Consumer<Apple> |
从一个对象中选择/抽取 | (String s)->s.length(); | Function<String,Integer>或ToIntFunction<String> |
组合两个值 | (int a,int b)->a*b | IntBinaryOperator |
比较两个对象 | (Apple a1,Apple a2)->a1.getWeight().compareTo(a2.getWeight()); | Comparator<Apple> |
2.在哪里以及如何使用lambda
①:函数式接口:只定义一个抽象方法的接口。注意:在java8中接口现在还可以拥有默认方法,哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
②:函数描述符:函数式接口的抽象方法的签名基本上就是lambda表达式的签名,我们将这种签名方法叫做函数描述符。例如可以用()->void代表了参数列表为空,且返回void的函数。
总之:lambda可以在函数式接口上使用lambda表达式。
3.环绕执行模式(使用lambda的一般步骤)
①:行为参数化(抽象行为)
②:使用函数式接口来传递行为
③:执行一个行为
④:传递lambda
/**
* 1.抽象行为
* @return
* @throws IOException
*/
public static String processFile()throws IOException{
try(BufferedReader bufferedReader=new BufferedReader(new FileReader("data.txt"))){
return bufferedReader.readLine();
}
}
/**
* 2,使用函数式接口来传递行为
*/
public interface BufferedReaderProcessor{
String process(BufferedReader bufferedReader)throws IOException;
}
/**
* 3.执行一个行为
* @param p
* @return
* @throws IOException
*/
public static String processFile(BufferedReaderProcessor p)throws IOException{
try(BufferedReader br=new BufferedReader(new FileReader("data.txt"))){
return p.process(br);
}
}
/**
* 4.传递lambda
*/
String oneLine=processFile((BufferedReader bufferedReader)->bufferedReader.readLine());
String twoLine=processFile((BufferedReader bufferedReader)->bufferedReader.readLine()+bufferedReader.readLine());
4.函数式接口
java库中的函数式接口
函数式接口 | 函数描述符 | 原始类型特化 |
---|---|---|
Predicate<T> | T->boolean | IntPredicate,LongPredicate,DoublePredicate |
Consumer<T> | T->void | IntConsumer,LongConsumer,DoubleConsumer |
Function<T,R> | T->R | ToLongFunction<T> ToDoubleFunction<T> </br>ToIntFunction<R> DoubleFunction<R> LongToIntFunction LongToDoubleFunction LongFunction<R> IntToLongFunction IntToDoubleFunction IntFunction<R> |
Supplier<T> | ()->T | BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
UnaryOperator<T> | T->T | LongUnaryOperator IntUnaryOperator DoubleUnaryOperator |
BinaryOperator<T> | (T,T)->T | LongBinaryOperator IntBinaryOperator DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | <T,U>->void | ObjDoubleConsumer<T> ObjLongConsumer<T> ObjIntConsumer<T> |
BiFunction<T,U,R> | (T,U)->R | ToDoubleBiFunction<T,U> ToLongBiFunction<T,U> ToIntBiFunction<T,U> |
5.方法引用
①:初识方法引用
官方解释:仅仅调用特定方法的lambda的一种快捷写法。
基本思想:如果一个lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述它。
实质:根据已有的方法实现来创建lambda表达式。
用法:目标引用放在分隔符“:”前,方法名称放在分隔符后。
lambda 及其等效方法引用的例子
lambda | 等效的方法引用 |
---|---|
(Apple a)->a.getWeight() | Apple::getWeight |
()->Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str,i)->str.substring(i) | String::substring |
(String s)->System.out.println(s) | System.out::println |
②:如何构建方法引用
方法引用主要有三类:
1. 指向静态方法的方法引用:(Integer的parseInt方法,可以用方法引用写作Integer::parseInt)
2. 指向任意类型实例方法的方法引用:(例如String的length方法,可以写作:String::Length)
3. 指向现有对象实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)
③:构造函数引用
对于一个现有构造函数,你可以利用它的名字和关键字new来创建它的一个引用:ClassName::new。它的功能与指向静态方法的引用类似。例如Apple类的一个无参构造函数的方法引用可以写为:Apple::new ,如果Apple的构造函数方法签名为(Integer weight),那么它就适合Function接口的签名,你就可以这样写:
Function
6.lambda复合
①:比较器复合
背景:或许你见过java8中可以使用静态方法Comparator.comparing,根据提取用于比较的键值的Function来返回一个Comparator。
Comparator<Apple> c=Comparator.comparing(Apple::getWeight)
- 逆序: 上述代码用于正序排序,如果是逆序排序应该怎么办?其实不用去创建一个Comparator实例对象,接口有一个默认的方法reverse可以使得给定的比较器进行逆序排序,因此上述代码中哪个比较器,只需要修改一下就可以使用
inventory.sort(comparing(Apple::getWeight).reversed());
- 比较器链: 有时可能会遇到这样的一种情况,两个对象的比较参数一样大小,比如两个苹果的重量一样重怎么办?在这种情况下你可以会出现第二比较参数,例如根据是否原产国排序,这时,你可以利用比较器链优雅的来解决这个问题了。
inventory.sort(comparing(Apple::getWeight)).reversed().thenComparing(Apple::getCountry);
②:谓词复合
谓词接口包括三个方法:negate,and,和or。可以通过Predicate来创建更多更复杂的谓词,比如:可以使用negate方法来返回一个predicate的非,比如苹果不是红的:
Predicate notRedApple=redApple.negate();
③:函数复合
函数复合是指可以把Function接口所代表的lambda表达式复合起来,Function接口为此配了‘andThen’和‘compose’两个默认方法,它们都会返回Function的一个实例。
andThen:
andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输入应用另外一个函数,比如假设有一个函数f给数字加1(x->x+1)另一个函数g给函数乘2,你可以将它们组合成一个函数h,先给数字加1,再给结果乘2:
Function<Integer,Integer> f=x->x+1;
Function<Integer,Integer> g=x->x+2;
Function<Integer,Integer> h=f.andThen(g);
int result=h.apply(1);//结果为4
- compose:
先把给定的函数用作compose的参数里面的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)),而andThen则意味着g(f(x)):
Function<Integer,Integer> f=x->x+1;
Function<Integer,Integer> g=x->x=2;
Function<Integer,Integer> h=f.compose(g);
int result=h.apply(1);//结果为3