028-86261949

当前位置:首页 > 学科资讯 > 用Java编写函数接口

用Java编写函数接口

2020/05/25 15:09 分类: 学科资讯 浏览:0

在本文中,我们将重点介绍功能接口的用途和可用性,并与几种备选方案进行比较。我们将研究如何将代码从其基本的、僵化的实现演变为基于功能接口的灵活实现。为此,让我们考虑以下几点甜瓜类别
  private final String type;  
  private final int weight;
  private final String origin;
  public Melon(String type, int weight, String origin) {
    this.type = type;
    this.weight = weight;
    this.origin = origin;
  }

  // getters, toString(), and so on omitted for brevity
}

 

假设我们有一个客户--我们叫他马克--他想开一家卖甜瓜的公司。我们根据他的描述塑造了上一堂课。他的主要目标是拥有一个库存应用程序来支持他的想法和决策,因此需要创建一个必须基于业务需求和发展发展的应用程序。在下面的部分中,我们将每天查看开发此应用程序所需的时间。

 

第一天(按类型过滤瓜)

有一天,马克要求我们提供一个功能,以过滤瓜的类型。因此,我们创建了一个名为Filters 并实施了astatic 方法,该方法接受作为参数筛选的瓜和类型的列表。

生成的方法非常简单:

public static List<Melon> filterByType(List<Melon> melons, String type) {

  List<Melon> result = new ArrayList<>();
  for (Melon melon: melons) {
    if (melon != null && type.equalsIgnoreCase(melon.getType())) {
      result.add(melon);
    }  
  }
  return result;
}

搞定了!现在,我们可以很容易地按类型过滤瓜,如下面的示例所示:

List<Melon> bailans = Filters.filterByType(melons, "Bailan");

 

第2天(过滤一定重量的瓜)

虽然马克对结果感到满意,但他要求另一个过滤器,以获得一定重量的瓜(例如,所有的瓜是1,200克)。我们刚刚为甜瓜类型实现了这样一个过滤器,所以我们可以想出一个新的static 一定重量的瓜的方法如下:

public static List<Melon> filterByWeight(List<Melon> melons, int weight) {

  List<Melon> result = new ArrayList<>();
  for (Melon melon: melons) {
    if (melon != null && melon.getWeight() == weight) {
      result.add(melon);
    }
  }
  return result;
}

这类似于filterByType() ,除了它有一个不同的条件/过滤器。作为开发人员,我们开始理解,如果我们继续这样下去,那么Filters 类将得到许多简单重复代码并使用不同条件的方法。我们非常接近样板码案子在这里。

 

第3天(按类型和重量过滤瓜)

事情变得更糟了。马克现在要求我们增加一个新的过滤器,过滤西瓜的类型和重量,他需要这个快速。然而,最快的实现是最丑陋的。看看:

public static List<Melon> filterByTypeAndWeight(

           List<Melon> melons, String type, int weight) {
  List<Melon> result = new ArrayList<>();
  for (Melon melon: melons) {
    if (melon != null && type.equalsIgnoreCase(melon.getType())
        && melon.getWeight() == weight) {
      result.add(melon);
    }
  }
  return result;
}

就我们而言,这是不可接受的。如果我们在这里添加一个新的过滤器标准,代码将变得很难维护,并且容易出错。

 

第4天(将行为推送为参数)

开会时间!我们不能继续添加更多这样的过滤器;我们可以想到的每一个属性的过滤都将以一个巨大的Filters类结束,这个类具有大量的参数和大量的大型复杂方法。样板码

主要的问题是我们有不同的行为样板码。所以,编写样板码只需将行为作为参数推送一次。这样,我们就可以将任何选择条件/标准塑造成行为,并根据需要对它们进行调整。代码将变得更清晰、更灵活、更易于维护,并且具有更少的参数。

这被称为行为参数化,如下图所示(左边显示我们现在拥有的,右侧显示我们想要的):

如果我们认为每个选择条件/标准都是一种行为,那么将每个行为看作一个接口的实现是非常直观的。基本上,所有这些行为都有一些共同之处--选择条件/标准和返回boolean 类型(这称为谓词)。在接口的上下文中,这是一个可以按以下方式编写的合同:

public interface MelonPredicate {
  boolean test(Melon melon);
}

 

此外,我们可以编写不同的实现MelonPredicate 。例如,过滤Gac 西瓜可以写成这样:

public class GacMelonPredicate implements MelonPredicate {

  @Override
  public boolean test(Melon melon) {
    return "gac".equalsIgnoreCase(melon.getType());
  }
}

或者,过滤所有重达5,000 G的瓜,可以写成:

public class HugeMelonPredicate implements MelonPredicate {

  @Override
  public boolean test(Melon melon) {
    return melon.getWeight() > 5000;
  }
}

这种技术有一个名字--策略设计模式。根据戈夫的说法,这个可以“定义一组算法,封装每个算法,并使它们可互换。该策略模式允许算法在不同客户端之间独立变化。"

因此,主要思想是在运行时动态地选择算法的行为。这个MelonPredicate 接口将所有专门用于选择瓜的算法统一起来,并且每个实现都是一种策略。

目前,我们有策略,但我们没有任何方法接收MelonPredicate 参数。我们需要一个filterMelons() 方法,如下图所示:

因此,我们需要一个参数和多个行为。让我们看一下源代码filterMelons() :

public static List<Melon> filterMelons(List<Melon> melons, MelonPredicate predicate) {

  List<Melon> result = new ArrayList<>();
  for (Melon melon: melons) {
    if (melon != null && predicate.test(melon)) {
      result.add(melon);
    }
  }
  return result;
}

这样好多了!我们可以使用不同的行为重用此方法,如下所示(在这里,我们将GacMelonPredicate HugeMelonPredicate ):

List<Melon> gacs = Filters.filterMelons(
  melons, new GacMelonPredicate());
List<Melon> huge = Filters.filterMelons(  
  melons, new HugeMelonPredicate());

 

第5天(实现另外100个过滤器)

马克要求我们再实现100个过滤器。这一次,我们有足够的灵活性和支持来完成这项任务,但是我们仍然需要编写100个策略或类来实现MelonPredicate 对于每个选择标准。此外,我们还必须创建这些策略的实例,并将它们传递给filterMelons() 方法。

这意味着有大量的代码和时间。为了保存这两个类,我们可以依赖Java匿名类。换句话说,如果类没有同时声明和实例化的名称,则会产生如下结果:

List<Melon> europeans = Filters.filterMelons(melons, new MelonPredicate() {

  @Override
  public boolean test(Melon melon) {
    return "europe".equalsIgnoreCase(melon.getOrigin());
  }
});

这里正在取得一些进展,但这并不是很重要,因为我们仍然需要编写大量的代码。检查下图中突出显示的代码(此代码对每个已实现的行为重复):

在这里,代码是不友好的。匿名类似乎很复杂,它们看上去不完整而且奇怪,特别是对于新手来说。

 

第6天(匿名类可以写为lambdas)

新的一天,新的想法!任何智能IDE都可以为我们指明前进的道路。例如,NetBeans IDE将谨慎地警告我们,这个匿名类可以编写为lambda表达式。下面的屏幕截图显示了这一点:

信息非常清楚-这个匿名内部类创建可以转换为lambda表达式。。在这里,手工进行转换,或者让IDE为我们完成转换。

结果如下:

 
List<Melon> europeansLambda = Filters.filterMelons(
melons, m -> "europe".equalsIgnoreCase(m.getOrigin()));

这样好多了!Java 8 lambda表达式这次做得很好。现在,我们可以以更灵活、快速、干净、可读和可维护的方式编写Mark的过滤器。

 

第7天(摘录列表类型)

马克第二天想出了一些好消息--他将扩大业务范围,出售其他水果和西瓜。这很酷,但是我们的谓词只支持Melon 实例。

那么,我们应该如何继续支持其他水果呢?还有几个水果?如果马克决定开始销售其他种类的产品,比如蔬菜呢?我们不能简单地为每个谓词创建一个谓词。这将使我们回到起点。

最明显的解决方案是将List 类型。我们首先定义一个新的接口,这次命名它。Predicate (移除Melon 姓名):

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

 

接下来,我们重写filterMelons() 方法并将其重命名为filter() :

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {

  List<T> result = new ArrayList<>();
  for (T t: list) {
    if (t != null && predicate.test(t)) {
      result.add(t);
    }
  }
  return result;
}

现在,我们可以为Melon :

List<Melon> watermelons = Filters.filter(
  melons, (Melon m) -> "Watermelon".equalsIgnoreCase(m.getType()));

我们也可以对数字做同样的处理:
List<Integer> numbers = Arrays.asList(1, 13, 15, 2, 67);
List<Integer> smallThan10 = Filters.filter(numbers, (Integer i) -> i < 10);

 

 

后退一步,看看我们从哪里开始,现在在哪里。由于Java 8函数接口和lambda表达式,这种差异是巨大的。你有没有注意到@FunctionalInterface 上的注释Predicate 界面?这是一种信息丰富的注释类型,用于标记功能接口。如果标记的接口没有功能,就会发生错误。

从概念上讲,函数接口只有一个抽象方法。此外,Predicate 接口,我们定义的接口在Java 8中已经作为java.util.function.Predicate 接口。这个java.util.function 包包含40多个这样的接口。因此,在定义新包之前,最好检查该包的内容。大多数情况下,六个标准的内置功能接口将完成这项工作。这些措施如下:

Predicate<T>
Consumer<T>
Supplier<T>
Function<T, R>
UnaryOperator<T>
BinaryOperator<T>

 

函数接口和lambda表达式构成了一个伟大的团队。Lambda表达式直接支持函数接口抽象方法的实现。基本上,整个表达式被视为功能接口的具体实现的实例,如下代码所示:

Predicate<Melon> predicate = (Melon m)
  -> "Watermelon".equalsIgnoreCase(m.getType());

 

简单地说

剖析lambda表达式将显示三个主要部分,如下图所示:

以下是lambda表达式的每个部分的描述:

  • 在箭头的左边,我们有这个λ的参数,在Lambda身体中使用。这些是FilenameFilter.accept (File folder, String fileName) 方法。
  • 在箭头的右边,我们有lambda主体,在本例中,它检查文件所在的文件夹是否可以读取,以及文件名是否以.pdf 后缀。
  • 箭头只是Lambda的参数和身体的分隔符。

此lambda的匿名类版本如下:

FilenameFilter filter = new FilenameFilter() {

@Override
public boolean accept(File folder, String fileName) {
return folder.canRead() && fileName.endsWith(".pdf");
  }
};

现在,如果我们看一下lambda和它的匿名版本,那么我们就可以得出结论,lambda表达式是一个简洁的匿名函数,可以作为参数传递给方法,也可以保存在变量中。我们可以得出结论,lambda表达式可以根据下图所示的四个单词来描述:

lambda支持行为参数化,这是一个很大的好处(请查看前面的问题以获得详细的解释)。最后,请记住,lambda只能在功能接口的上下文中使用。

 

#标签:Java,函数接口