带有谓词和流的实际Java

Java是一种比过去更具表现力的语言。 如果您仍在使用Java 7惯用语(无论您是否实际在8+中进行编译),那么值得一试一下当今Java的一些强大语言功能。

在本文中,我将举一个例子来说明您的胃口:谓词。

本文针对那些使用较旧代码库,或者只是暂时没有尝试新Java功能的Java开发人员。 它将展示用Java完成任务的经典方法,然后展示现代方法。

New job at the candy factory

您已被美国最小的糖果制造商CandyCorp录用。 作为一家新兴公司,我们的工厂需要大量软件。 欢迎上车!

为了使自己适应目标,首先应该知道的是可以打电话给糖果工厂。糖果袋()生产一袋糖果:

Collection<Candy> bagOfCandy = CandyFactory.bagOfCandy();

现在,我们只制造一种糖果:在硬糖壳中的一小块盘形巧克力。 即使这是我们唯一的产品,我们也会为您提供多种颜色。 每袋糖果都包含各种红色,蓝色,绿色和黄色的糖果。

让我们摆脱积压的第一个故事。

Count how many pieces of candy with a given color are in a bag

作为工厂生产线质量控制经理,我想随机选择几袋从生产线出来的糖果,并把颜色分开。 这样,我可以更快地执行任务。我的任务之一是计算每个袋子中多少个糖果具有给定的颜色。这将帮助我确保每个包包平均具有足够的任何给定颜色,以使该颜色的粉丝满意。

好的,这很容易。 作为经典的Java程序员,您可以做到这一点。 您将把这个想法概括为一种方法,filterByColor,这将需要一袋糖果和给定的颜色,然后将所有与该颜色匹配的碎片分离到自己的新系列中。 有了这种方法,质量控制经理就可以执行许多任务。 为了满足给定的示例,他们可以致电尺寸()新集合中的方法,以找出有多少个。

Collection<Candy> bagOfCandy = CandyFactory.bagOfCandy();
Collection<Candy> redCandies = filterByColor(bagOfCandy, Color.RED);
int numberOfReds = redCandies.size();

这是Java-8之前的经典命令式实现filterByColor方法。

Collection<Candy> filterByColor(Collection<Candy> assortedCandy, Candy.Color color) {
    Collection<Candy> results = new ArrayList<>();
    for(Candy candyPiece : assortedCandy) {
        if(candyPiece.getColor().equals(color)) {
            results.add(candyPiece);
        }
    }
    return results;
}

为了完成其任务,filterByColor方法执行以下步骤。

它:

  1. 创建一个新集合,结果,以容纳与给定颜色匹配的糖果。遍历candy的主要集合,该集合位于名为变量的变量中什锦的糖果。Checks if the given piece of candy is the given color。Adds the piece to the new collection if it is the given color。Returns the new collection。

以前您可能已经看过很多类似的代码,因为这是一个非常常见的用例。

您将此代码发送到生产环境后,QC经理很高兴他们可以更轻松地执行工作。

Expanding the line

一段时间后,我们公司考虑扩大产品线。 我们决定尝试两种新型糖果:

  1. 花生椒盐脆饼

目前,这两种新型糖果将以“垃圾袋”包装提供,这意味着饥饿的顾客会得到一袋糖果,其中包含所有三种(常规,椒盐脆饼和花生)糖果,作为特殊促销的一部分。 如果促销效果良好,我们知道需求很好,我们可以开始制作独立袋装的花生或椒盐脆饼糖果。

您的团队已经添加了一种新方法,getType()到糖果 class. When the 糖果Factory makes a grab bag, we can get the color and type of each piece of candy with code like:

Collection<Candy> bagOfCandy = CandyFactory.grabBag();
for(Candy candyPiece : bagOfCandy) {
    Candy.Color color = candyPiece.getColor();
    Candy.Type type = candyPiece.getType();
    // now use the color and/or type in some way
}

QC经理希望具有与以前类似的功能,以计算此促销活动的每个袋子中的糖果类型。 他们喜欢您上次冲刺实现的颜色过滤方法。 如果您可以快速为他们提供一种类型过滤方法,那就太好了。 他们想帮助回答诸如“手提包中有多少椒盐脆饼糖果?”之类的问题。

您复制/粘贴先前的方法并更改了几件事。 。 。 好的,实际上您只是到处都说“颜色”改为“类型:”

Collection<Candy> filterByColor(Collection<Candy> assortedCandy, Candy.Type type) {
    Collection<Candy> results = new ArrayList<>();
    for(Candy candyPiece : assortedCandy) {
        if(candyPiece.getType().equals(type)) {
            results.add(candyPiece);
        }
    }
    return results;
}

并且您提供了一个示例用法,如下所示:

Collection<Candy> bagOfCandy = CandyFactory.grabBag();
Collection<Candy> pretzelCandies = filterByType(bagOfCandy, Candy.Type.PRETZEL);
int numberOfPretzels = pretzelCandies.size();

...质量控制经理又高兴了!

A nagging feeling

发布新代码的第二天,您正在考虑如何从旧方法中复制/粘贴新方法。 感觉。 。 。 不知何故。 并排观察这两种方法,很明显,应该有某种方法在它们之间共享功能,因为它们实际上几乎相同。

考虑如何编写一个可以同时说明两个用例的方法似乎很自然。 (甚至还有其他必将出现的。)

像这样:

Collection<Candy> filter(Collection<Candy> candies, Object attribute) {
    Collection<Candy> results = new ArrayList<>();
    for(Candy candyPiece : candies) {
        if(/* condition matching the corresponding attribute of the candy to the attribute variable */) {
            results.add(candyPiece);
        }
    }
    return results;
}

但是您无法想到一种简单的方法来执行此操作,因为您需要在此处概括的内容不能存储在变量中。 这是代码! 每次if语句中的布尔条件必须实际上比较Candy对象的不同属性(例如颜色或类型)。

通常,您编写一种共享代码的方法。 有没有办法将另一种方法传递到提议的新方法中过滤方法,然后在if语句中调用该方法?

Classic Java: S.A.M. interfaces

在Java 8之前,功能只能存在于方法中,而方法始终是类的成员。

A special pattern was used to share functionality for use cases like this, the Single Abstract Method interface, or S.A.M. Just like it sounds, its simply an interface with a single method. It was used all the time in classic Java. One well-known example is the Comparator interface used to provide ordering criteria to sort algorithms.

我们可以重构我们的两种方法,filterByType和filterByColor, into one method by using a S.A.M. The S.A.M. can have a boolean method,和the for loop in the filter method can call the S.A.M.'s boolean method as it iterates through the collection of candy.

CandyMatcher将是我们的S.A.M. 看起来像这样:

interface CandyMatcher {
    boolean matches(Candy candy);
}

使用这种方法,我们编写了一个更通用的新方法过滤方法:

Collection<Candy> filter(Collection<Candy> candies, CandyMatcher matcher) {
    Collection<Candy> results = new ArrayList<>();
    for(Candy candyPiece : candies) {
        if(matcher.matches(candyPiece)) {
            results.add(candyPiece);
        }
    }
    return results;
}

我们可以将上述方法重新用于按颜色和类型进行过滤,只需传递一个不同的实例即可。CandyMatcher带有特定于所需用例的代码。

为了按颜色过滤,我们创建了一个新类来实现CandyMatcher并提供了与给定颜色匹配的特定功能:

class ColorMatcher implements CandyMatcher {
    private final Candy.Color color;

    ColorMatcher(Candy.Color color) {
        this.color = color;
    }

    @Override
    public boolean matches(Candy c) {
        return c.getColor().equals(this.color);
    }
}

最后,我们将ColorMatcher传递给过滤 method. We know that 过滤ing by color is already being used, so we can rewrite the 过滤ByColor method now in terms of the 过滤 method that uses the CandyMatcher:

Collection<Candy> filterByColor(Collection<Candy> candies, Candy.Color color) {
    ColorMatcher matcher = new ColorMatcher(color);
    return filter(candies, matcher);
}

现在您已经看到了filterByColor和配色器代码,尝试实现类型匹配器在继续之前可以用来匹配常规,花生或椒盐脆饼糖果。

The drawback of the S.A.M. approach

如果像我一样实现该方法,则它看起来像这样:

    class TypeMatcher implements CandyMatcher {
        private final Candy.Type type;

        TypeMatcher(Candy.Type type) {
            this.type = type;
        }

        @Override
        public boolean matches(Candy c) {
            return c.getType().equals(this.type);
        }
    }

从查看新代码可以看到,代码比以前更多了,而不是更少。 我们获得了可扩展性(可以扩展代码以适应将来的用例),但由于代码既冗长又复杂,因此我们也失去了可读性。

解决此问题的一种经典方式是改为使用匿名类。 而不是写一个整体类型匹配器类,只需在需要时创建它:

Collection<Candy> filterByType(Collection<Candy> candies, String type) {
    return filter(candies,
                  new CandyMatcher() {
                      @Override
                      public boolean matches(Candy c) {
                          return c.getType().equals(type);
                      }
                  });
}

这是非常不满意的。 匿名类占用5-6行代码,具体取决于您的操作方式。 仍然可以说,它比只使用一种全班更好。

但是,这里的真正问题是,无论您采用哪种方式,都很难做到。 匿名类不能用于比这更复杂的任何事情,而创建整个其他类对于您将不只使用一次的东西来说是高开销的。 仅使用这些选项,即使它具有重复的代码给我们带来a的感觉,也很想回到简单的第一个示例。

Modern Java: lambdas

两者都是现代Java中解决的问题。 解决方法是Lambdas。 使用lambda,您只需要一行代码即可表达匹配的概念!

Lambda syntax

这是一个lambda:

c -> c.getColor().equals(color)

这是一个lambda:

c -> c.getType().equals(type)

松散地说,lambda具有以下语法:

  • A parenthetical set of variables which match a S.A.M. interface method's parameters. When there isonly a single variable, the parentheses can be omitted.一个“箭头”,它是一个破折号,后跟大于号:->(可选)打开的花括号(仅在随后出现多行时使用)实施代码(可选)右花括号

You can find the formal definition for lambda expressions in section 15.27 of the Java Language Specification.

Lambda usage

使用lambda代替匿名类,filterByType现在变成:

Collection<Candy> filterByType(Collection<Candy> candies, Candy.Type type) {
    return filter(candies, c -> c.getType().equals(type));
}

需要注意的一件事是,许多Java IDE现在都可以对此更改进行重构。 为了从前面提到的匿名类转到此处的lambda,我只是在IntelliJ IDEA中应用了重构,而不是自己执行方法的重写。

Lambdas and Functional interfaces in the standard library

现在,我们正在使用lambda,但我们的代码更加简洁了,但新的错误开始困扰我们。 我们的S.A.M.还有剩余的人工制品 实施:CandyMatcher接口。 这仍然感觉像我们为了使用lambda而需要的一些样板文件。

也是解决的问题!

The Java standard library actually provides a number of interfaces for this very purpose, called functional interfaces. Functional interfaces are covered in section 9.8 of the Java Language Specification, and are defined thus:

一种功能界面是一个只有一个抽象方法(除了Object的方法之外)的接口,因此表示单个功能协定。

有时我认为功能接口是可以用作lambda类型的接口。 例如,为了将lambda传递给方法,您需要创建一个方法参数以接受它。 该参数是什么类型? 功能界面!

的CandyMatcher接口实际上符合功能接口的技术定义,这就是为什么我们能够保留过滤执行重构时,仅使用此方法。

此方法签名:

Collection<Candy> filter(Collection<Candy> candies, CandyMatcher matcher)

而且它仍然能够将lambda传递给它匹配器变量。

But considering that there's an interface for this purpose already provided by the standard library, let's go with that and eliminate the CandyMatcher. The interface the standard library provides is Predicate.

The Predicate javadoc says it "represents a predicate (boolean-valued function) of one argument."

正是我们的用例! 因此,我们现在可以将过滤器的方法签名及其实现更改为使用谓词:

Collection<Candy> filter(Collection<Candy> candies, Predicate<Candy> predicate) {
    Collection<Candy> results = new ArrayList<>();
    for(Candy candyPiece : candies) {
        if(predicate.test(candyPiece)) {
            results.add(candyPiece);
        }
    }
    return results;
}

的谓语有一个测试我们通过传递lambda来提供其身体的方法。

当然,我们不想在这里调用变量谓语告诉我们怎么样该代码正在执行其工作,但没有告诉我们它在做什么以及为什么。 一个更好的名字是我们使用的名字candyMatcher。 也许candySelector或类似的东西也很好。 但是我选择了谓语在上面的示例中,您可以确切地看到在哪里将新概念付诸实践。

现在,调用代码如下所示:

Collection<Candy> candies = CandyFactory.grabBag();
Collection<Candy> redCandies = filter(candies, c -> Color.RED.equals(c.getColor()));
int numberOfRedCandies = redCandies.size();
Collection<Candy> pretzelCandies = filter(candies, c -> Type.PRETZEL.equals(c.getType());

我们的代码是现代的。 这比较简单。 它更具表现力。 也许,最重要的是,它是惯用的。 这是其他Java开发人员将容易理解的东西。

我们现在甚至不需要过滤ByColor和过滤ByType方法。 我们删除它们。 的过滤该方法非常简单,只需一个lambda,我们不需要这些额外的代码。

A small sidebar about variables in lambdas

您可能已经注意到我们正在使用单字符变量名称C. This may make you squirm, beCause variables are supposed to have meaningful names and it makes Code more readable when we think about variable naming.

不过,类似于单字符变量名称如何一世和Ĵ are Commonly used 一世n for loops, lambdas (espeC一世ally one-l一世ners w一世th a s一世ngle 一世nput var一世able) are a speC一世al Case。 The type和mean一世ng of the var一世able 一世s very Clear,和the sCope 一世s very small。 Therefore, 一世t 一世s Common for programmers us一世ng lambdas to use s一世ngle-CharaCter names l一世ke here, where we use C代表Candy。

Modern Java: Streams

说到惯用的Java,实际上有一种更好的方法来实现filter方法:使用流。

实际上,Stream类的方法之一是过滤它正是我们所做的过滤方法可以做到,尽管方式略有不同。

不过,在我们直接跳到那里之前,让我们谈谈流。

Definition of a stream

A Stream is similar to a collection of objects, but the Javadoc notes that it "differs from collections in several ways." You can read the Stream javadoc to see those differences if you want, but I think it's easier (though incomplete) to think of a Stream as data to which a series of operations can be applied, terminating in some result. This is done with a fluent style where you literally just call one Stream method after another. For example, if you had a Stream you might start by asking for only 100 pieces from that stream, then get the color of those pieces, resulting in a Stream by getting the color of each one, and then finally printing all the colors out, like this:

candyStream.limit(100)
           .map(c -> c.getColor())
           .forEach(c -> System.out.println(c));

如您所见,Stream与集合不同。 对于集合,您始终必须决定如何遍历集合,并逐个应用不同的操作。 使用Stream,您只需要考虑要应用的一系列操作。

我们可以使用糖果用例来举例说明。 糖果从一个“抓包”开始,里面有多种颜色和类型。 我们应用操作 to filter the candy down to only the red pieces. Then we apply a terminal 操作 to create a new collection of only those pieces. We can even just total up the number of pieces as the terminal 操作 to our pipeline, to meet the given use case.

在Java中,这看起来像:

bagOfCandy.stream()
       .filter(c -> Color.RED.equals(c.getColor()))
       .count();

我们只需要。流()请致电此处,因为我们从一个集合开始(bagOfCandy)。 如果已经是Stream,那么就没有必要了。

Refactoring our last example to Stream usage

命令式的过滤现在看起来像这样:

static Collection<Candy> filter(Collection<Candy> candies, Predicate<Candy> candyMatcher) {
        Collection<Candy> results = new ArrayList<>();
        for(Candy candyPiece : candies) {
            if(candyMatcher.test(candyPiece)) {
                results.add(candyPiece);
            }
        }
        return results;
    }

我们可以删除此方法。 现在就使用流。

Collection<Candy> bagOfCandy = CandyFactory.grabBag();

long numberOfRedCandies = candies.stream()
                         .filter(c -> Color.RED.equals(c.getColor()))
                         .count();
long numberOfPretzelCandies = candies.stream()
                            .filter(c -> Type.PRETZEL.equals(c.getType())
                            .count();

我们意识到,从标准Ja​​va库中获取现代Java一直是QC管理器用例所需要的!

Why a Stream is better than its imperative cousin

像我们之前那样消除代码是选择Streams的一个很好的理由,但是还有一个更重要的理由。 我们正在消除可能掩盖错误的代码,并用标准库中的代码取代了已被尝试和正确的代码。 这也减轻了 数据转换的认知开销,以便我们可以从更大的角度关注自己。

另一种表达方式是流是陈述式而不是势在必行. That is, we're instructing the computer what to do with the data而不是performing the lower-level steps.

通过告诉计算机我们想要什么((对其进行流处理,对其进行过滤,对其进行收集),我们不再编写我们认为可以做一件事而实际上又可以做另一件事的代码。 我们并没有陷入杂草丛生的一系列步骤,并希望结果正确。 我们让计算机来处理。

Conclusion

希望您喜欢这篇文章。 您已经通过一个真实示例了解了lambda,谓词和流。 请耐心等待,如果有足够的文章,我将添加更多带有实际示例的文章,其中Java中的流可以简化和改善您的代码。

from: https://dev.to//scottshipp/real-world-java-with-predicates-and-streams-2jlo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值