猪脚:以下内容参考《Java 8 in Action》

发布:https://ryan-miao.github.io/2017/07/15/java8-in-action-2/

源码:github

需求

果农需要筛选苹果,可能想要绿色的,也可能想要红色的,可能想要大苹果(>150g),也可能需要红的大苹果。基于此等条件,编写筛选的代码。

1. 策略模式解决方案

1.1 最直观的做法

首先,已知信息是一筐苹果(List<Apple> inventory),但筛选条件多种多样。我们可以根据不同的条件写不同的方法来达到目的。比如,找出绿色的苹果:

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值,那么可以把这个条件抽取出来,然后在筛选的时候传入条件。这个筛选条件叫做谓词

创建谓词接口:

public interface ApplePredicate {
boolean test(Apple apple);
}

添加几个判断条件:

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;
}
}

筛选的方法:

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;
}

这样,我们就可以根据不同的条件进行筛选了。


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提供了把方法当做参数传递的能力。这样,上面的代码就可以这样写:

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来实现。

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"); }
}

控制台打印:

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类型抽象化来构造筛选条件。

创建一个条件接口:

public interface Predicate<T> {
boolean test(T t);
}

更新一个更普遍的filter:

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;
}

那么,可能这样用:

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是有默认方法的:

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);
}
}

其实就是将以前手动排序封装了。那么,苹果的排序就可以传入一个比较器实现:

@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的提示,进一步:

appleList.sort(Comparator.comparingInt(Apple::getWeight));

这里就涉及了多次行为传参了。后面再说。

1.7 Runnable

多线程Runnable的时候经常会采用匿名内部类的做法:

@Test
public void testRunnable(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("running");
}
}; new Thread(runnable).start();
}

采用lambda行为传参就变为:

@Test
public void testRunnable(){
Runnable runnable = () -> System.out.println("running"); new Thread(runnable).start();
}

小结

本次测试主要理解如下内容:

  • 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
  • 传递代码,就是将行为作为参数传递给方法。

参考

Java8 in action(1) 通过行为参数化传递代码--lambda代替策略模式的更多相关文章

  1. 《Java 8 in Action》Chapter 2:通过行为参数化传递代码

    你将了解行为参数化,这是Java 8非常依赖的一种软件开发模式,也是引入 Lambda表达式的主要原因.行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式.一言以蔽之,它意味 着拿出一个代码 ...

  2. Java通过行为参数化传递代码

    在软件工程中,一个众所周知的问题就是,不管做什么,用户的需求肯定会变.如何应对这样不断变化的需求?理想的状态下,应该把的工作量降到最少.此外,类似的新功能实现起来还应该很简单,而且易于长期维护.行为参 ...

  3. JAVA8 in Action:行为参数化,匿名类及lambda表达式的初步认知实例整理

    import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.functio ...

  4. Liferay7 BPM门户开发之40: Form表单的Action到Render的数据传递

    在Form提交后的变量,很多情况是要展现在jsp页面中,这时Action到Render的变量传递就非常有用. 例如,您在数据库中添加了学生的详细信息. 为了实现这一需求,先创建Form表单(学生的细节 ...

  5. .net中用Action等委托向外传递参数

    原文:.net中用Action等委托向外传递参数      一般我们可以使用ref,out达到向外传递参数目的. Action<T>是一个特殊的委托,除了常规应用.我们还可以用它来实现简单 ...

  6. EF5+MVC4系列(7) 后台SelectListItem传值给前台显示Select下拉框;后台Action接收浏览器传值的4种方式; 后台Action向前台View视图传递数据的四种方式(ViewDate,TempDate,ViewBag,Model (实际是ViewDate.Model传值))

    一:后台使用SelectListItem 传值给前台显示Select下拉框 我们先来看数据库的订单表,里面有3条订单,他们的用户id对应了 UserInfo用户表的数据,现在我们要做的是添加一个Ord ...

  7. 如何优雅的将文件转换为字符串(环绕执行模式&行为参数化&函数式接口|Lambda表达式)

    首先我们讲几个概念: 环绕执行模式: 简单的讲,就是对于OI,JDBC等类似资源,在用完之后需要关闭的,资源处理时常见的一个模式是打开一个资源,做一些处理,然后关闭资源,这个设置和清理阶段类似,并且会 ...

  8. JAVA8初探-让方法参数具备行为能力并引入Lambda表达式

    关于JAVA8学习的意义先来贴一下某网站上的对它的简单介绍:“Java 8可谓Java语言历史上变化最大的一个版本,其承诺要调整Java编程向着函数式风格迈进,这有助于编写出更为简洁.表达力更强,并且 ...

  9. ASP Action函数 如何接收client传递的数据(编辑中。。。)

    参看链接:https://www.cnblogs.com/umlzhang/p/3654486.html 我这里总结一下,Action的参数的来源比较多 1.url 2.路由设定 3.post中的内容 ...

随机推荐

  1. 第40篇 使用Sublime+MarkDown快速写博客

    原文地址:http://blog.laofu.online/2017/06/03/how-use-sublime/ 前端的开发人员应该都知道sublime的神器,今天就说说如何使用sublime结合m ...

  2. Ubuntu安装genymotion模拟器步骤

    1.安装VitrualBox genymotion模拟器需要有VirtualBox环境,打开终端(ctrl + alt + T),执行以下命令: sudo apt-get install virtua ...

  3. h5 + nginx + php 视频上传之突破文件大小受限的解决办法

    一.环境: CentOS 6.8 nginx 1.8.0 php 7.0.10 二.背景 基于 nginx + php 的 h5 项目,上传视频的时候,如果视频太大,会上传失败. 三.正文 一份视频传 ...

  4. 第一次使用idea从SVN什么checkout项目,一直都是用的eclipse

    IntelliJ IDEA 14 拉取SVN maven 多模块项目 部署tomcat 详细图解!   二话不说 进入主题 我们创建空项目实际上是项目空间 进入主界面 想用svn必须先启用它 选择Su ...

  5. Chapter 1:Introduction

    作者:桂. 时间:2017-05-24  08:06:45 主要是<Speech enhancement: theory and practice>的读书笔记,全部内容可以点击这里. 1. ...

  6. 移动平台Unity3D 应用性能优化

    WeTest 导读 做了大概半年多VR应用了,VR由于双眼double渲染的原因,对性能的优化要求比较高,在项目的进展过程中,总结了一些关于移动平台上Unity3D的性能优化经验,供分享. 一.移动平 ...

  7. WPF MVVM 架构 Step By Step(3)(把后台代码移到一个类中)

    我觉得大部分开发者应该已经知道怎么去解决这个问题.一般都是把后台代码(GLUE code)移动到一个类库.这个类库用来代表UI的属性和行为.任何代码当被移到一个类库中时都可以被编译成一个DLL,然后可 ...

  8. react-native —— 在Mac上配置React Native Android开发环境排坑总结

    配置React Native Android开发环境总结 1.卸载Android Studio,在终端(terminal)执行以下命令: rm -Rf /Applications/Android\ S ...

  9. Java代码实现 增删查 + 分页——实习第四天

    今天项目内容已经开始了,并且已经完成好多基本操作,今天就开始总结今天学习到的内容,和我遇到的问题,以及分析这其中的原因. 内容模块: 1:Java代码实现对数据库的增删查: 2:分页且获取页面信息: ...

  10. 用nrm一键切换npm源

    当使用官方npm源安装各种包比较慢的时候,建议修改npm源地址 查看npm源地址,在终端输入以下命令 npm config list 会看到官方的npm源 metrics-registry = &qu ...