01-将函数参数化进行传递
项目源码:https://github.com/java8/
1 应对不断变化的需求
在我们进行开发中,经常需要面临需求的不断变更,我们可以将行为参数化以适应不断变更的需求。
行为参数化就是可以帮助我们处理频繁变更的需求的一种软件开发模式
我们可以将代码块作为参数传递给方法。
例如,现有一个仓库,我们想定义从仓库中查询绿苹果的功能。后来我们又想查询重苹果(>150g)…
面对这种不断变更的需求,我们就可以使用行为参数化来维护我们的代码。
1.1 初试牛刀:筛选出绿苹果
Apple类:
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;@Getter
@Setter
@AllArgsConstructor
@ToString
public class Apple {private int weight = 0;private String color = "";
}
// 初试牛刀:筛选绿苹果
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;
}
如果说我们现在改变主意,想要查询红色的苹果,最简单的办法就是copy上面这个方法,然后将方法改名为 fliterRedApples,改变if 判断条件。谈若我们需要查询各种演示的苹果:浅绿色、暗红色、黄色等,那么再按照前面的这种方法来做代码将变得非常的冗余。
一个好的原则是尝试将我们上的的这个方法进行抽象化,以适应不同颜色的苹果的查询。
下面我们进行尝试:
1.2 再展身手:把颜色作为参数
我们立马能想到的方法是将上面的 filterGreenApples 加上一个颜色参数,就可以了:
// 再展身手:把颜色作为参数
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (apple.getColor().equals(color)) {result.add(apple);}}return result;
}
这样我们就可以查询各种各样的苹果了,如下:
List<Apple> greenApple = filterApplesByColor(inventory,"green");
List<Apple> redApple = filterApplesByColor(inventory,"red");
...
假设我们现在又要查询重苹果(>150g)或轻苹果,那么我们只需要根据
filterApplesByColor 进行稍稍修改即可,如下:
// 再展身手:根据苹果的重量进行查询
public static List<Apple> filterApplesWeight(List<Apple> inventory, int weight) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (apple.getWeight() > weight) {result.add(apple);}}return result;
}
现在我们完成了根据颜色查询苹果、根据重量查询苹果的功能,但是我们这两个方法极为相似,复制了大部分的代码来实现遍历库存,并对每个苹果应用筛选条件稍稍做了修改。
如果后续我们想改变查询的遍历方式来提升性能,那么需要修改所有的方法,这样显然是不合适的。
我们可以考虑将颜色和重量结合为一个方法,称为filterApples。不过这样需要加上一个标记来区分是对什么属性(颜色或重量)的查询(但是我们不推荐这种方式)
1.3 第三次尝试:对你能想到的每个属性做筛选
下面是一个比较笨拙的方法:
// 生产环境别这么用
public static List<Apple> filterApples(List<Apple> inventory, String color,int weight, boolean flag) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {// 阅读性不好,也很笨拙if (flag && apple.getColor().equals(color) || !flag && apple.getWeight() > weight) {result.add(apple);}}return result;
}
我们可以这样调用这个方法,以使用不同属性的查询:
// 根据颜色查询:查询绿苹果
List<Apple> greenApples = filterApples(inventory, "green",0,true);
// 根据重量查询:查询>150g的重苹果
List<Apple> heavyApples = filterApples(inventory,"",150,false);
这个方法能解决根据不同属性查询苹果的功能,但是这样写代码很烂,并且我们也不知道 boolean flag 参数传入 true、flase是什么意思,可读性很差。
并且这样也不能很好的使用根据不同属性进行查询,如果我们需要根据多个属性进行查询(比如:查询绿色的重苹果),那更是天方夜谭了。
下面我们来解决这个问题:
2 行为参数化
我们需要我们适应各种各样的属性来查询。
我们可以考虑根据Apple的属性来返回一个boolean值。我们把它称为谓词(即一个返回boolean值的函数)。下面我们定义一个接口来实现:
// 判断型接口
@FunctionalInterface
public interface ApplePredicate {boolean test(Apple apple);
}
现在我们就可以用 ApplePredicate 的多个实现代表不同的查询标准了,例如:
// 查询出重苹果
public class AppleHeavyWeightPredicate implements ApplePredicate {@Overridepublic boolean test(Apple apple) {return apple.getWeight() > 150;}
}// 查询出绿苹果
public class AppleGreenColorPredicate implements ApplePredicate {@Overridepublic boolean test(Apple apple) {return "green".equals(apple.getColor());}
}
我们可以把这些标准看作filter方法的不同行为。刚做的这些和“策略设计模式”相关,它让你定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个算法。在这里,算法族就是ApplePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。
但是,该怎么利用ApplePredicate的不同实现呢?
我们需要 filterApples方法接受 ApplePredicate对象,对Apple做条件测试。这就是行为参数化:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为。
个人对于行为参数化的理解:
所谓行为参数化,就是将行为(一般封装成方法的形式)以参数的形式传递到其他方法中执行。
如何实现行为参数化:
我们要给filterApples方法添加一个参数,让它接受 ApplePredicate对象。
行为参数化的好处:
我们把filterApples方法迭代集合的逻辑与我们应用到集合中每个元素的行为(这里是一个谓词,即我们根据Apple的属性查询的行为)区分开了。
2.1 第四次尝试:根据抽象条件筛选
利用 ApplePredicate 修改后的 filter方法如下:
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;
}
2.2 传递代码/行为
现在,这段代码以及比我们前面写的方法灵活多了,代码的可读性也高了。比如,我们要查询红的重苹果,只需要创建一个类实现现ApplePredicate接口即可(甚至可以使用Lambda表达式):
public class AppleRedAndHeavyPredicate implements ApplePredicate{@Overridepublic boolean test(Apple apple) {return "red".equals(apple.getColor()) && apple.getWeight() > 150;}
}// 调用
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
现在,我们的filterApples方法完成了行为参数化。
但是我们现在发现这样还是很麻烦,我们要完成一个查询功能,为此我们定义了一个类,里面有一个方法,完成对查询结果的判断,这样有很多的无用代码,代码可读性还是不高。
我们可以使用lmabda表达式简化不必要的代码,直接把表达式"red".equals(apple.getColor())
&&apple.getWeight() > 150传递给filterApples方法,无需定义一个类
// 使用Lambda表达式实现传递代码
List<Apple> redAndHeavyApples = filterApples(inventory, (apple) ->"red".equals(apple.getColor()) && apple.getWeight() > 150);
2.3 多种行为,一个参数
行为参数化的好处在于你可以把迭代要筛选的集合的逻辑与对集合中每个元素应用的行为区分开来。这样你可以重复使用同一个方法,给它不同的行为来达到不同的目的。
演示:编写灵活的prettyPrintApple方法
编写一个prettyPrintApple方法,它接受一个Apple的List,并可以对它参数化,以多种方式根据苹果生成一个String输出(有点儿像多个可定制的toString方法)。
例如,你可以告诉 prettyPrintApple 方法,只打印每个苹果的重量。此外,你可以让 prettyPrintApple方法分别打印每个苹果,然后说明它是重的还是轻的。
@FunctionalInterface
public interface AppleFormatter{ String accept(Apple a);
}public class AppleFancyFormatter implements AppleFormatter { // 后续我们可以通过Lambda简化,以省略这样的类public String accept(Apple apple) {String characteristic = apple.getWeight() > 150 ? "heavy" :"light";return "A " + characteristic +" " + apple.getColor() + " apple";}
}public class AppleSimpleFormatter implements AppleFormatter {public String accept(Apple apple) {return "An apple of " + apple.getWeight() + "g";}
}public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter) {for (Apple apple : inventory) {String output = formatter.accept(apple);System.out.println(output);}
}
使用
// 你首先要实例化AppleFormatter的实现,然后把它们作为参数传给prettyPrintApple方法
prettyPrintApple(inventory, new AppleFancyFormatter());
现在,我们已经将行为抽象出来了,这样使我们的代码适应不同的需求,但是这样很繁琐
因为我们需要声明很多个只使用一次的类,下面通过匿名内部类、Lambda表达式等方式进行简化
3 简化代码
在前面,当要把新的行为传递给 filterApples方法的时候,我们不得不声明好几个实现ApplePredicate接口的类,然后实例化好几个只会提到一次的ApplePredicate对象。这真是很啰嗦,很费时间!
3.1 匿名类
匿名类和我们熟悉的 Java局部类(块中定义的类)差不多,但匿名类没有名字。它允许我们同时声明并实例化一个类。换句话说,它允许你随用随建
3.2 第五次尝试:使用匿名类
下面我们将通过创建匿名类的方式实现ApplePredicate的对象,重写筛选的例子(以查询绿色苹果为例):
List<Apple> greenApples = filterApples(inventory, new ApplePredicate() { // 直接内联参数化filterapples方法的行为@Overridepublic boolean test(Apple apple) {return "red".equals(apple.getColor());}
});
但是匿名类的方式还是不够好:
- 它往往很笨重,因为它占用了很多空间
- 代码阅读性较差
总的来说,使用匿名类的方式,不仅代码编写、维护比较费时间,可读性也不太好。
接下来,我们使用Java 8中引人的Lambda表达式——一种更简洁的传递代码的方式。
3.3 第六次尝试:使用 Lambda 表达式
使用Lambda表达式重写上面的代码:
List<Apple> greenApples = filterApples(inventory, (apple) ->"red".equals(apple.getColor()));
到目前为止,区别于以往的值参数传递,我们已经实现了将类、匿名类、Lambda表达式等行为参数化传递到了方法中
3.4 第七次尝试:将 List 类型抽象化
在通往抽象的路上,我们还可以更进一步。目前,filterApples方法还只适用于Apple。
我们还可以将List类型抽象化,从而超越你眼前要处理的问题:
@FunctionalInterface
public interface Predicate<T>{ boolean test(T t);
}public static <T> List<T> filter(List<T> list, Predicate<T> p){ // 引入类型参数T,即泛型List<T> result = new ArrayList<>(); for(T e: list){ if(p.test(e)){ result.add(e); } } return result;
}
现在,我们的 filter 方法能更好的适应不同的查询了,可以用在香蕉、桔子、Integer或是String的列表等等上了。
例如:
List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor())); // 从numbers中筛选出偶数
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
4 真实的例子
到现在,我们已经清除的知道了行为参数化是一个很有用的模式,它能够轻松地适应不断变化的需求。这种模式可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为(例如对Apple的不同谓词)将方法的行为参数化。前面提到过,这种做法类似于策略设计模式。你可能已经在实践中用过这个模式了。Java API中的很多方法都可以用不同的行为来参数化。这些方法往往与匿名类一起使用。
我们会展示两个例子,这应该能帮助你巩固传递代码的思想了:用一个Comparator排序,用Runnable执行一个代码块
4.1 用 Comparator 来排序
例如,根据苹果的重量对库存进行排序,或者希望你根据颜色对苹果进行排序。听起来有点儿耳熟?是的,
你需要一种方法来表示和使用不同的排序行为,来轻松地适应变化的需求。
在Java 8中,List自带了一个sort方法(你也可以使用Collections.sort)。sort的行为可以用java.util.Comparator
对象来参数化,如下:
package java.util;@FunctionalInterface
public interface Comparator<T> {int compare(T o1, T o2);...
}
因此,我们可以随时创建Comparator的实现,用sort方法表现出不同的行为。
比如,你可以 使用匿名类,按照苹果的重量升序对库存排序:
inventory.sort(new Comparator<Apple>() {@Overridepublic int compare(Apple o1, Apple o2) {return o1.getWeight() - o2.getWeight();}
});
后续,我们可以随时创建一个Comparator来满足新要求,并把它传递给 sort方法。而如何进行排序这一内部细节都被抽象掉了。用Lambda表达式的话,看起来就是这样:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight() - a2.getWeight());
4.2 用 Runnable 执行代码块
在 Java里,你可以使用Runnable接口表示一个要执行的代码块
package java.lang;@FunctionalInterface
public interface Runnable {public abstract void run();
}
我们可以像下面这样,使用这个接口创建执行不同行为的线程:
Thread t = new Thread(new Runnable() { @Overridepublic void run(){ System.out.println("Hello world"); }
});
使用Lambda表达式简化:
Thread t = new Thread(() -> System.out.println("Hello world"));
5 小结
-
行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
-
行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
-
传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
-
Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。
相关文章:

01-将函数参数化进行传递
项目源码:https://github.com/java8/ 1 应对不断变化的需求 在我们进行开发中,经常需要面临需求的不断变更,我们可以将行为参数化以适应不断变更的需求。 行为参数化就是可以帮助我们处理频繁变更的需求的一种软件开发模式 我们可以将代码…...

数据结构【栈和队列】
第三章 栈与队列 一、栈 1.定义:只允许一端进行插入和删除的线性表,结构与手枪的弹夹差不多,可以作为实现递归函数(调用和返回都是后进先出)调用的一种数据结构; 栈顶:允许插入删除的那端&…...

MATLAB | 产生阿尔法稳定分布噪声并作出概率密度函数
一、问题描述 想产生不同特征参数的α稳定随机变量,并且作出其概率密度函数进行对比。 二、解决思路 运行了MATLAB的官方实例代码: openExample(‘stats/ComparePDFsOfStableDistributionsExample’) (1)使用makedist()函数生成…...

深入浅出Pytorch函数——torch.softmax/torch.nn.functional.softmax
分类目录:《深入浅出Pytorch函数》总目录 相关文章: 机器学习中的数学——激活函数:Softmax函数 深入浅出Pytorch函数——torch.softmax/torch.nn.functional.softmax 深入浅出Pytorch函数——torch.nn.Softmax 将Softmax函数应用于沿dim的…...

Vue2学习笔记
vue是根据数据来构建用户界面的一套框架 创建一个vue实例 <!-- 1.创建一个容器 2.引入vue.js开发版本(全局的) 3.创建实例对象 4.配置选项 > 完成渲染 --> <div id"app">{{ msg }} </div> <script srcvue.js><…...

Java 悲观锁 乐观锁
锁可以从不同的角都分类。其中乐观锁和悲观锁是一种分类方式 一、悲观锁、乐观锁定义 悲观锁就是我们常说到的锁。对于悲观锁来说,他总是认为每次访问共享资源时会发生冲突,所以必须每次数据操作加上锁,以保证临界区的程序同一时间只能有一个…...

优惠券秒杀(二)
库存超卖问题分析 库存超卖问题其本质就是多个线程操作共享数据产生的线程安全问题,即当一个线程在执行操作共享数据的多条代码的过程中,其他线程也参与了进来,导致了线程安全问题的产生。例如:线程1发送请求,查询库存…...

selenium的java方式打开IE浏览器
1.下载软件Selenium Driver 官方下载地址: https://www.selenium.dev/downloads/解压selenium-java-3.141.59.zip文件到java项目 seleniumDemo,并降解压的文件放入依赖中(1)双击项目的src打开项目结构,或右键-打开…...

分类评估指标
文章目录 1. 混淆矩阵2. Precision(精准率)3. Recall(召回率)4. F1-score5. ROC曲线和AUC指标5.1 ROC 曲线5.2 绘制 ROC 曲线5.3 AUC 值6. API介绍6.1 **分类评估报告api**6.2 **AUC计算API**练习-电信客户流失预测1. 数据集介绍2. 处理流程3. 案例实现4. 小结1. 混淆矩阵 …...

OpenCV:图像直方图计算
图像直方图为图像中像素强度的分布提供了有价值的见解。通过了解直方图,你可以获得有关图像对比度、亮度和整体色调分布的信息。这些知识对于图像增强、图像分割和特征提取等任务非常有用。 本文旨在为学习如何使用 OpenCV 执行图像直方图计算提供清晰且全面的指南。…...

用QFramework来重构 祖玛游戏
资料 Unity - 祖玛游戏 GitHub 说明 用QF一个场景就够了,在UIRoot下切换预制体达到面板切换。 但测试中当然要有一个直接跳到测试面板的 测试脚本,保留测试Scene(不然初学者也不知道怎么恢复测试Scene),所以全文按S…...

生活杂记-显示器尺寸
以下是常见显示器尺寸的对角线长度换算成厘米的结果(已经四舍五入到最接近的厘米数): 19英寸显示器 ≈ 48.26厘米21.5英寸显示器 ≈ 54.61厘米24英寸显示器 ≈ 60.96厘米27英寸显示器 ≈ 68.58厘米32英寸显示器 ≈ 81.28厘米34英寸显示器 ≈…...

在CSDN学Golang云原生(Kubernetes Pod无状态部署)
一,静态pod Kubernetes中的Pod是可以动态创建、销毁的,如果希望Pod只使用静态的IP地址而不是自动生成一个IP地址,那么就需要使用静态Pod。 静态Pod是在kubelet启动时通过指定文件夹路径来加载的。当kubelet检测到这些配置文件变化后&#x…...

@Bean的作用
Bean通常和Configuration注解一起使用 Bean可以用在方法上,方法返回的对象交给spring容器管理,和提供给其他程序组件使用 Bean是一个注解,用于将方法标记为Spring容器中的一个Bean。具体来说,Bean注解可以用于方法上,…...

【论文阅读22】Label prompt for multi-label text classification
论文相关 论文标题:Label prompt for multi-label text classification(基于提示学习的多标签文本分类) 发表时间:2023 领域:多标签文本分类 发表期刊:Applied Intelligence(SCI二区࿰…...

EasyExcel数据导出功能封装
起因: 最近需要用到excel导出功能,使用EasyExcel可以快速实现导出,又需要优雅的对EasyExcel进行封装,在实现自己的导出功能时又可以制定一定的规则,让其他同事方便使用,最近研究了下网上的常规写法,站在巨人的肩上重新添加了自己的思路,供大家参考,有任何问题请多指教…...

通过web.xml来配置servlet程序
IDEA 2022.3.3 tomcat-9.0.27 Java EE8 JDK-16 配置访问的虚拟路径 web.xml <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.org/2001/XMLSchema-insta…...

umi 创建的项目中,如何配置多个环境变量
创建env.js 在config.js中配置 在页面中使用 env.js和config.js的目录顺序 package.json中的配置...

Mysql 5.7 连接数爆满 清理连接数
Mysql 5.7 连接数爆满 清理连接数 我在做项目的时候遇到了这个报错,然后搜了半天也没有在网上找到mysql清理连接数的方案,后面还是自己写了一个 打开MySQL命令行或客户端,并使用管理员权限登录到MySQL服务器。 我这里使用的是navicat 输入…...

HTTPS工作原理
先简述一下什么是HTTPS,HTTPS就是在HTTP的基础上增加了SSL/TLS来完成加密传输,以免敏感信息被第三方获取,所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。 一、客户端发起HTTPS请求 这个没什么好说的,就是…...

十大基础算法
一、选择排序 过程简单描述: 首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二…...

Java---第八章(字符串-----String,StringBuilder 和 StringBuffer)
Java---第八章 字符串String字符串的常用方法StringBuilder和StringBuffer常用方法 对比String 和StringBuilder 和 StringBuffer 字符串 String 特性: String 类位于java.lang包中,无需引入,可直接使用String 类是由final修饰的ÿ…...

k8s集群的部署
【1】安装docker systemctl enable docker所有节点均需要安装docker,并且使其开机自启,每个节点均部署镜像加速器 【2】配置k8s的yum文件 [rootk8s1 ~]# cd /etc/yum.repos.d/ [rootk8s1 yum.repos.d]# vim k8s.repo [rootk8s1 yum.repos.d]# cat k8s.repo [k8s…...

设计模式——观察者模式
文章目录 1 概述2 实现3 总结 1 概述 观察者模式可以分为观察者和被观察者,观察者通过注册到一个被观察者中,也可视为订阅,当被观察者的数据发生改变时,会通知到观察者,观察者可以据此做出反应。 可以类比订阅报纸&am…...

在Debian 12 上安装 PHP 5.6, 7.4
环境:Debian 12 Debian 12 默认的PHP版本为 8.2 如果直接安装php7.4就出现下面的报错: sudo apt-get install libapache2-mod-php7.4 php7.4 php7.4-gd php7.4-opcache php7.4-mbstring php7.4-xml php7.4-json php7.4-zip php7.4-curl php7.4-imap p…...

微服务——统一网关Getway
为什么需要网关? 网关的两种实现: 网关Getway——快速入门 步骤一 网关背身也是一个微服务,需要注册到nacos中去 步骤二 成功运行后 可以通过网关进行请求转发到对应服务。 流程如下: 路由断言工厂 网关路由可以配置的东西有如下。 spri…...

[ELK安装篇]:基于Docker虚拟容器化(主要LogStash)
文章目录 一:前置准备-(参考之前博客):1.1:准备Elasticsearch和Kibana环境:1.1.1:地址:https://blog.csdn.net/Abraxs/article/details/128517777 二:Docker安装LogStash(数据收集引擎ÿ…...

纪录片《打铁文艺社》:从全美高中生电影节到多项国际赞誉,聚焦城市公共艺术的蜕变之路
7月21日,在全美高中生电影节(All American High School Film Festival,AAHSFF)公布的入围名单中,一部取材于中国深圳的纪录片《打铁文艺社Datie: The Art Tribe of Tiegang》以其深刻的主题和精良的制作,引…...

VLAN---虚拟局域网
VLAN— 虚拟局域网 LAN—局域网 MAN—城域网 WAN—广域网 1.一个VLAN相当于是一个广播域 VLAN—通过路由器和交换机协同工作后,将原本的一个广播域逻辑上,拆 分为多个虚拟的广播域。 VLAN配置: 1.创建VLAN VID—VLAN ID------用来区分和…...

新的CoolSiC™槽沟MOSFET技术,用于低栅氧化物应力和高性能
标题:The new CoolSiC™ Trench MOSFET Technology for Low Gate Oxide Stress and High Performance UPS(Uninterruptible Power Supply)系统也称不间断电源系统,是一种能够提供电力备用的设备,当主电源出现故障或停…...