01-将函数参数化进行传递

这篇具有很好参考价值的文章主要介绍了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 {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

// 查询出绿苹果
public class AppleGreenColorPredicate implements ApplePredicate {
    @Override
    public 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{
    @Override
    public 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方法的行为
    @Override
    public 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>() {
    @Override
    public 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() {  
	@Override
    public 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处理。文章来源地址https://www.toymoban.com/news/detail-610818.html

到了这里,关于01-将函数参数化进行传递的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java获取URL地址中传递的参数

    一、 Java获取URL地址中传递的参数 二、获取请求的URL地址 三、获取请求的IP地址 四:判断字符串是否能够转换成指定格式的日期

    2024年02月16日
    浏览(32)
  • 【Java EE】Spring请求如何传递参数详解

    访问不同的路径,就是发送不同的请求.在发送请求时,可能会带⼀些参数,所以我们在学习Spring的请求时,主要是学习如何传递参数到后端以及后端如何接收. 下面博主会对传递参数进行一个详解,咱们主要是使⽤浏览器和Postman来模拟 当我们运行后,用浏览器进行访问 http://127.0.

    2024年04月12日
    浏览(33)
  • Java开发学习(二十五)----使用PostMan完成不同类型参数传递

    学习路线指引(点击解锁) 知识定位 人群定位 🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量

    2023年04月09日
    浏览(34)
  • java 通过行为参数化传递代码,来解决不断增长的需求

    2.1, 匿名内部类 2.2 lamble表达式

    2024年02月08日
    浏览(35)
  • Java后端和前端传递的请求参数的三种类型

    在 HTTP 请求中,常见的请求参数类型有三种:`application/x-www-form-urlencoded`、`multipart/form-data` 和 `application/json`(通常用于 `raw` 类型)。这三种类型主要指的是请求体中的数据格式,其中包括参数的传递方式和编码。 1. **`application/x-www-form-urlencoded`:**    - 这是默认的编码类型

    2024年02月02日
    浏览(32)
  • java restful application/x-www-form-urlencoded 传递参数

            在发送短信的时候,要使用x-www-form-urlencoded的编码格式进行传递参数。  具体要求: 参数名称 说明 备注 userId 用户名 timespan 时间戳 格式为yyyyMMddHHmmss password 密码 此处用原始密码+时间戳 做MD5加密,32位大写格式   phone 手机号 多个用英文逗号隔开 msgType 编码类型 选

    2024年02月14日
    浏览(30)
  • 【Android -- JNI 和 NDK】Java 和 C/C++ 之间传递参数和返回值

    本文主要介绍 JNI 的数据传递上,即 Java 如何传递对象给 C++; 而 C++ 又如何将数据封装成 Java 所需的对象。 1. 基本数据类型 传递 java 的基本类型是非常简单而直接的,一个 jxxx 之类的类型已经定义在本地系统中了,比如: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar 和 jboolean 分别

    2024年02月09日
    浏览(26)
  • Java 【dubbo rpc改feign调用】解决调用服务提供方无法传递完整参数问题

    【框架改造问题点记录,dubbo改为spring cloud alibaba】 【第二篇】feign接口异常解决 【描述】多参数情况下,调用服务提供方无法传递完整参数、改@SpringQueryMap原因是会将实体自动拆分为拼接参数。目前只遇到多参数:实体和单参数情况,持续更新… 汇总: 1.多个普通参数,

    2024年02月16日
    浏览(30)
  • 【C++】STL 算法 ③ ( 函数对象中存储状态 | 函数对象作为参数传递时值传递问题 | for_each 算法的 函数对象 参数是值传递 )

    在 C++ 语言中 , 函数对象 / 仿函数 可以像函数一样被调用 , 并且 其 还具有类的特征 , 可以 通过 继承 和 重载 来 修改 重载函数调用操作符函数 的行为 ; 函数对象 / 仿函数 通常是通过 定义一个类 , 然后为这个类 重载 函数调用操作符 () 来实现的 ; 函数对象的一个重要特性是

    2024年02月02日
    浏览(34)
  • python是如何进行参数传递的?

    在分析python的参数传递是如何进行的之前,我们需要先来了解一下,python变量和赋值的基本原理,这样有助于我们更好的理解参数传递。 python变量以及赋值 数值 从几行代码开始 我们先将1赋值给a,也就是a指向了1这个对象, 在python中一切皆对象 。接着b=a,则表示让b也指向

    2024年02月14日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包