猪脚:以下内容参考《Java 8 in Action》
下一篇Java8学习(3)- Lambda 表达式
需求 果农需要筛选苹果,可能想要绿色的,也可能想要红色的,可能想要大苹果(>150g),也可能需要红的大苹果。基于此等条件,编写筛选的代码。
1. 策略模式解决方案 1.1 最直观的做法 首先,已知信息是一筐苹果(List<Apple> inventory
),但筛选条件多种多样。我们可以根据不同的条件写不同的方法来达到目的。比如,找出绿色的苹果:
1 2 3 4 5 6 7 8 9 10 11 public static List<Apple> filterGreenApples (List<Apple> inventory) { List<Apple> result = new ArrayList<>(); for (Apple apple: inventory){ if ("green" .equals(apple.getColor())){ result.add(apple); } } return result; }
同样的,可以编写filterRed
, filterWeight
等等。但必然出现重复代码,违反软件工程原则Don't repeast yourself
。而且,筛选的类也会显得臃肿。
现在,有一种更容易维护,更容易阅读的策略模式来实现这个需求。
1.2 策略模式 由于多种筛选条件的结果都是返回一个boolean
值,那么可以把这个条件抽取出来,然后在筛选的时候传入条件。这个筛选条件叫做谓词 。
创建谓词接口:
1 2 3 public interface ApplePredicate { boolean test (Apple apple) ; }
添加几个判断条件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class AppleGreenColorPredicate implements ApplePredicate { @Override public boolean test (Apple apple) { return "green" .equals(apple.getColor()); } } public class AppleHeavyWeightPredicate implements ApplePredicate { @Override public boolean test (Apple apple) { return apple.getWeight() > 150 ; } } public class AppleRedAndHeavyPredicate implements ApplePredicate { @Override public boolean test (Apple apple) { return "red" .equals(apple.getColor()) && apple.getWeight() >150 ; } }
筛选的方法:
1 2 3 4 5 6 7 8 9 10 public static List<Apple> filterApples (List<Apple> inventory, ApplePredicate predicate) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (predicate.test(apple)){ result.add(apple); } } return result; }
这样,我们就可以根据不同的条件进行筛选了。
1 2 3 4 5 6 7 8 List<Apple> inventory = new ArrayList<>(); inventory.add(new Apple("red" , 100 )); inventory.add(new Apple("red" , 200 )); inventory.add(new Apple("green" , 200 )); List<Apple> redHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate()); Assert.assertEquals(1 , redHeavyApples.size()); Assert.assertEquals(200 , redHeavyApples.get(0 ).getWeight());
以上的代码设计方案几乎是最好理解和扩展的了,当条件发生改变的时候只要增加一个类就可以。但java8提供了更好的选择,一种你只要声明一个接口,具体实现不用管,只有当使用的时候才去关心。
1.3 方法传递 java8提供了把方法当做参数传递的能力 。这样,上面的代码就可以这样写:
1 2 3 List<Apple> apples = filterApples(inventory, apple -> "red" .equals(apple.getColor()) && apple.getWeight() > 150 ); Assert.assertEquals(1 , apples.size()); Assert.assertEquals(200 , apples.get(0 ).getWeight());
除了接口声明,不需要实现接口的类。我们只需要传入一个类似匿名内部类的东西,是的,lambda表达式和匿名内部类是可以互相转换的。
如此,我们设计接口的时候只要声明一个接口作为参数,然后再调用的时候把逻辑当做参数传进去。这个在我看来就是传递方法了。就像Javascript,可以把一个方法当做参数。
与之前的设计模式相比,lambda可以不用写那么类。
1.4 新需求 现在,果农需要包装苹果。包装的方式有多种,我将包装的结果打印出来,就是打印的样式也有多种。比如:
A light green apple
或者
An apple of 150g
上面是两种打印方式,按照之前的策略模式需要创建两个类。下面采用lambda来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public interface AppleFormatter { String format (Apple apple) ; } public class AppleOutput { public static void prettyPrintApple (List<Apple> inventory, AppleFormatter formatter) { for (Apple apple : inventory) { String format = formatter.format(apple); System.out.println(format); } } public static void main (String[] args) { List<Apple> inventory = new ArrayList<>(); inventory.add(new Apple("red" , 100 )); inventory.add(new Apple("red" , 200 )); inventory.add(new Apple("green" , 200 )); prettyPrintApple(inventory, new AppleFormatter() { @Override public String format (Apple apple) { String characteristic = apple.getWeight()>150 ?"heavy" :"light" ; return "A " + characteristic + " " + apple.getColor() + " apple." ; } }); prettyPrintApple(inventory, apple -> "An apple of " + apple.getWeight() + "g" ); } }
控制台打印:
1 2 3 4 5 6 A light red apple. A heavy red apple. A heavy green apple. An apple of 100g An apple of 200g An apple of 200g
如果使用IntelIJ IDEA作为编辑器,那么肯定会忍受不了匿名内部类,因为IDEA会不停的提示你:匿名内部类可以转变为方法参数。
1.5 更普遍的用法 上面的筛选只是针对Apple的,那么是否可以推广开来呢?下面针对List类型抽象化来构造筛选条件。
创建一个条件接口:
1 2 3 public interface Predicate <T > { boolean test (T t) ; }
更新一个更普遍的filter:
1 2 3 4 5 6 7 8 9 10 public static <T> List<T> filter (List<T> list, Predicate<T> p) { List<T> result = new ArrayList<T>(); for (T e : list) { if (p.test(e)){ result.add(e); } } return result; }
那么,可能这样用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) { List<Apple> appleList = new ArrayList<>(); appleList.add(new Apple("red" , 100 )); appleList.add(new Apple("red" , 160 )); appleList.add(new Apple("green" , 60 )); List<Apple> redApples = filter(appleList, (Apple apple) -> "red" .equals(apple.getColor())); Assert.assertEquals(2 , redApples.size()); List<Integer> numberList = Arrays.asList(1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ); List<Integer> lessThan4Numbers = filter(numberList, (Integer num) -> num < 4 ); Assert.assertEquals(3 , lessThan4Numbers.size()); }
1.6 排序 行为参数化的过程掌握后,很多东西就会自然而然的使用了。比如排序。果农需要将苹果按照大小排序呢?
java8中List是有默认方法的:
1 2 3 4 5 6 7 8 9 default void sort (Comparator<? super E> c) { Object[] a = this .toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this .listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
其实就是将以前手动排序封装了。那么,苹果的排序就可以传入一个比较器实现:
1 2 3 4 5 6 7 8 9 @Test public void sort () { List<Apple> appleList = new ArrayList<>(); appleList.add(new Apple("red" , 100 )); appleList.add(new Apple("red" , 160 )); appleList.add(new Apple("green" , 60 )); appleList.sort((o1, o2) -> o1.getWeight()-o2.getWeight()); }
根据IDEA的提示,进一步:
1 appleList.sort(Comparator.comparingInt(Apple::getWeight));
这里就涉及了多次行为传参了。后面再说。
1.7 Runnable 多线程Runnable的时候经常会采用匿名内部类的做法:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testRunnable () { Runnable runnable = new Runnable() { @Override public void run () { System.out.println("running" ); } }; new Thread(runnable).start(); }
采用lambda行为传参就变为:
1 2 3 4 5 6 @Test public void testRunnable () { Runnable runnable = () -> System.out.println("running" ); new Thread(runnable).start(); }
小结 本次测试主要理解如下内容:
行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。 传递代码,就是将行为作为参数传递给方法。 参考