行为参数化和Lambda表达式(Java8)

1 行为参数化

使用场景:应对频繁变更的需求(的一种软件开发模式)

使用好处:”一个方法,多个行为”,即写一个方法,可以接受不同的新行为作为参数,实现不同的功能。

举个例子:你要处理一个集合,可能会写出这样的业务方法:

      对列表中每个元素都做“事件A”

     处理完列表后做“事件B”

     遇到错误时做“事件E”

行为参数化就是将上面所述的事件A、B、E等行为提升为参数传递给这个业务方法

仍旧使用旧例子,大背景如下:一个菜单,菜单上有几十个菜,每个菜的属性为如下的Dish类:

/**
 * 菜品
 */
@Data
@AllArgsConstructor
public class Dish {
 
    private final String name;         //菜名
    private final boolean vegetarian;  //是否为素菜
    private int calories;     //菜的热量
    private final Type type;  //菜的类型
 
    public enum Type{ MEAT,FISH,OTHER}
}
 
public List<Dish> menu = Arrays.asList(
            new Dish("pork",false,800,Dish.Type.MEAT),
            new Dish("beef",false,700,Dish.Type.MEAT),
            new Dish("chicken",false,400,Dish.Type.MEAT),
            new Dish("french fries",true,530,Dish.Type.OTHER),
            new Dish("rice",true,350,Dish.Type.OTHER),
            new Dish("season fruit",true,120,Dish.Type.OTHER),
            new Dish("pizza",true,550,Dish.Type.OTHER),
            new Dish("prawns",false,300,Dish.Type.FISH),
            new Dish("salmon",false,450,Dish.Type.FISH)
);

需求:对菜单menu数据集中的菜品Dish进行运算。

    1.找出卡路里在300以上的菜品Dish。 

     (解决办法:java8以前你可能写一个方法1,直接遍历menu集合,按照条件得到卡路里大于300的菜品,存放在list中)

    2.又增加了需求,需要找出鱼肉类蔬菜。  

     (又写了一个方法2,采用上面同样的思想遍历一遍,找到符合条件的菜品,存放在list中)

    3.又增加了需求,找到符合上述两个条件的菜品?

     (同样,又写了一个方法3遍历,选择菜品,存放在list,或者直接修改上面的方法1或者2)

    4.其他需求……

 

应该怎么优化呢?

引入行为参数化:此时,需要让业务方法接受多种行为作为参数,并在内部使用,来完成不同的行为。

抽取需求行为(这种返回boolean类型的代码行为,我们叫他谓词)和共同行为分别为:

/**
 * 函数式接口
 */
@FunctionalInterface    //函数式接口声明注解,可省略
public interface Predicate<Dish> {
    boolean pick(Dish dish);
}
//业务代码
public List<Dish> filterMenu(List<Dish> menu, Predicate<Dish> pre){
    List<Dish> dishes=new ArrayList<>();
    for(Dish dish:menu){
        if(pre.pick(dish)){  //利用参数化的行为作为筛选条件
            dishes.add(dish);
        }
    }
    return dishes;
}

(1)策略模式

       对,有的人想到了策略模式,将需求抽象为一个接口A,为每种需求都创建一个实现类来实现接口A,我们把这一系列实现类叫做策略模式的算法簇,当需要谁时就用谁。

//算法簇
public class HighCalories implements Predicate<Dish> { //需要高热量菜品
    @Override
    public boolean pick(Dish dish) {return dish.getCalories()>500; }
}
public class FishAndHighCalories implements Predicate<Dish> { //需要高热量鱼类菜品
    @Override
    public boolean pick(Dish dish) {
        return dish.getCalories()>500&&dish.getType().equals(Dish.Type.FISH);
    }
}

@Test
public void testStrategy() {
    List<Dish> dishes=filterMenu(menu,new HighCalories());
List<Dish> dishes2=filterMenu(menu,new FishAndHighCalories());
}
  此时可以根据需求编写灵活的算法,将其实例化带入即可。

      But,使用策略模式,当有新的需求时,你不得不添加新的算法,实例化新的对象,比较啰嗦,也费时间!那么,能不能做的更好呢?

(2)匿名类

匿名类可以同时声明和实例化一个类,进一步改善代码,让它更简洁。

换句话说,匿名类可以随意创建

List<Dish> dishes= filterMenu(menu, new Predicate<Dish>(){
    @Override
    public boolean pick(Dish dish) {
        return dish.getCalories()>500;
    }
});

但是匿名类也比不太好,往往比较笨重,重写了很多代码,比如上面的pick方法签名和参数列表都要重写一遍,篇幅也较多。

所以,这种改善还是不到位,仍然有点啰嗦!所以java8引入了Lambda表达式—一种更为简洁的传递代码的方式。

(3)Lambda表达式

   //蔬菜且热量大于500  
   List<Dish> dishes_5= filterMenu(menu,(Dish dish)->dish.isVegetarian()&&dish.getCalories()>500 ); 

       采用这种方式,不用重新定义算法类,也无需使用臃肿的匿名类,直接使用lambda表达式,简洁的传递需求行为。

总之,从优化过程体现出的变化:

                      

(4)数据抽象化

/**
 * 函数式接口
 */ 
public interface Predicate<T> {
    boolean pick(T t);
}

/**
 *  抽象化数据,更加通用,通用方法
 */
public <T> List<T> filter(List<T> list, Predicate<T> pre){
    List<T> result=new ArrayList<>();
    for(T t:list){
        if(pre.pick(t)){  //利用参数化的行为作为筛选条件
            result.add(t);
        }
    }
    return result;
}

List<Dish> dishes_4= filterMenu(menu,(Dish dish)->dish.isVegetarian() );  //选择蔬菜

真实实例:

@Test
public void test(){
    //线程例子
    Thread t=new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("匿名内部类实现的线程要执行的代码块A");
        }
    });

    Thread thread=new Thread(()->System.out.println("Lambda实现的线程要执行的代码块A"));
    t.run();
    thread.run();

    //比较器Comparator
    List<Dish> menu=new ArrayList<>();
   menu.sort(new Comparator<Dish>() {
       @Override
       public int compare(Dish o1, Dish o2) {
           return o1.getCalories()-o2.getCalories();
       }
   });
    menu.sort((Dish d1,Dish d2)->d1.getCalories()-d2.getCalories());

}

2 Lambda表达式

2.1 认识Lambda

我们可以把Lambda理解为匿名函数的一种简洁的传递形式

     匿名:没有方法签名,写的少想得多。

     函数:lambda不属于某个类,但是像方法一样有自己的参数列表、函数主体、返回类型,还可能有异常列表

     简洁:lambda表达式不用过多的写模板代码。

     传递:lambda表达式可以作为参数当成值传递给方法。

它的基本语法就是:

      parameters)->expression    或(含有花括号)     (parameters)->{ statement ; }

两种形式,前者是表达式,后者含有花括号的是状态控制语句,要以分号结束

Lambda表达式可以返回空、boolean、具体对象。典型的示例有:

Boolean

  List<String> list->list.isEmpty()

创建对象

()->new Dish()

消费一个对象

(Dish dish)->{System.out.println(dish.getName());}

从对象中抽取数据

(Dish dish)->dish.getCalories()

组合两个值

(int a,int b)->a*b

比较两个对象

(Dish d1,Dish d2)->d1.getCalories().compareTo(d2. getCalories())

2.2 Lambda如何使用

      Lambda要建立在函数式接口上的。

      函数式接口:只定义一个抽象方法的接口。诸如前面的:

public interface Predicate<T> {
    boolean pick(T t);
}
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t); 
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

都是函数式接口,@FunctionalInterface用于标注该接口是函数式接口,非必需,但推荐使用

        函数描述符:即函数式接口的抽象方法,因为该抽象方法签名也就是lambda表达式的方法签名。

        而前面的menu.sort((Dish d1,Dish d2)->d1.getCalories()-d2.getCalories())这种lambda都是以内联的形式为函数式接口提供实现。

Lambda也适用于环绕行模式:一种“初始化-执行-关闭结束”的执行模式,使用lambda传递要执行的行为。

2.3 Java8中典型的函数式接口

2.3.1 Predicate

        接受一个泛型T,返回一个boolean.

@FunctionalInterface    //函数式接口声明注解,可省略
public interface Predicate<T> {
    boolean test(T t);
}

2.3.2 Consumer

      接受一个对象并消费,不返回值。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2.3.4 Function

       接受一个泛型T对象,返回一个泛型R对象。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

还有关于避免拆箱装箱的函数式接口:IntPredicate/LongToDoubleFunction/……

函数式接口那么多,编译器是如何确定我传入的lambda表达式是对哪个函数式接口实现呢?

2.4 Lambda验证机制

      Java编译器会自动从上下文推断出用什么函数式接口来配合Lambda表达式。

     上下文中lambda表达式需要的类型叫做目标类型。

比如: List<Dish> dishes_5= filterMenu(menu,(Dish dish)->dish.isVegetarian()&&dish.getCalories()>500 );

他的推断过程可以拆分成如下几步:

(1)先找到filterMenu()的声明

(2)要求lambda表达式是Predicate<T>对象的正式参数

(3)确认Predicate<T>是一个函数式接口,找到它定义抽象方法。

(4)该抽象方法描述了一个函数描述符:接受一个对象T,返回boolean。

(5)检查filterMenu的行为参数是否符合函数描述符要求的输入输出。

  这样的检查机制就形成了一个闭环。

Lambda表达式可以使用外部的局部变量,但是只能使用final型的常量或者只被赋值一次的变量。

2.5 方法引用

方法引用可以让你重复使用已有的方法,使用已有的构造函数,自定义函数等等。如:

String::length,
Strategy::isHighCalories
等
此时编译器会进行一种与lambda表达式类似的类型检查机制。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值