java带缓存的输入流
今天,Java是一种比过去更具表现力的语言。 如果您仍在使用Java 7惯用语(无论您是否实际在8+中进行编译),那么值得一试一下当今Java的一些强大语言功能。
在本文中,我将举一个例子来说明您的食欲: 谓词。
本文针对那些使用较旧代码库或可能只是一段时间没有尝试新Java功能的Java开发人员。 它将展示用Java完成任务的经典方法,然后展示现代的等效方法。
糖果厂的新工作
您已被美国最小的糖果制造商CandyCorp录用。 作为一家新兴公司,我们的工厂需要大量软件。 欢迎上车!
为了使您CandyFactory.bagOfCandy()
目标,首先应该知道可以调用CandyFactory.bagOfCandy()
来生产一袋糖果:
Collection < Candy > bagOfCandy = CandyFactory . bagOfCandy ();
现在,我们只制造一种糖果:在硬糖壳中的一小块盘形巧克力。 即使这是我们唯一的产品,我们也会为您提供多种颜色。 每袋糖果都包含各种红色,蓝色,绿色和黄色的糖果。
让我们摆脱积压的第一个故事。
计算一袋给定颜色的糖果的数量
作为工厂生产线质量控制经理,我想随机选择几袋从生产线出来的糖果,并把颜色分开。 这样,我可以更快地执行任务。
我的任务之一是计算每个袋子中给定颜色的糖果数量。
这将帮助我确保每个袋子平均拥有足够的任何给定颜色,以使该颜色的粉丝满意。
好的,这很容易。 作为经典的Java程序员,您可以做到这一点。 您将把这个想法概括为一个方法filterByColor
,该方法将带一袋糖果和给定的颜色,然后将与该颜色匹配的所有块分离到自己的新集合中。 使用这种方法,质量控制经理可以执行他们拥有的许多任务。 为了满足给定的示例,他们可以在新集合上调用size()
方法以找出有多少个。
Collection < Candy > bagOfCandy = CandyFactory . bagOfCandy ();
Collection < Candy > redCandies = filterByColor ( bagOfCandy , Color . RED );
int numberOfReds = redCandies . size ();
这是filterByColor
方法的经典,命令式Java-8之前的实现。
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方法执行以下步骤。
它:
- 创建一个新的
results
,以保存与给定颜色匹配的糖果。 - 遍历Candy的主要集合,该集合位于名为
assortedCandy
的变量中。 - 检查给定的糖果是否是给定的颜色。
- 如果给定颜色,则将其添加到新集合中。
- 返回新集合。
也许您之前已经看过很多这样的代码,因为这是一个非常常见的用例。
您将此代码发送到生产环境,并且质量控制经理现在很高兴他们可以更轻松地执行工作。
扩大线
过了一会儿,我们公司考虑扩大产品线。 我们决定尝试两种新型糖果:
- 花生
- 椒盐脆饼
目前,这两种新型糖果将以“垃圾袋”包装提供,这意味着饥饿的顾客会得到一袋糖果,其中包含所有三种(常规,椒盐脆饼和花生)糖果,作为特殊促销的一部分。 如果促销做得好,我们知道需求很好,我们可以开始制作独立袋装的花生或椒盐脆饼糖果。
您的团队已经向Candy
类添加了一个新方法getType()
。 当CandyFactory制作一个抓包时,我们可以使用以下代码获取每块糖果的颜色和类型:
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 ();
...质量控制经理又高兴了!
na的感觉
发布新代码的第二天,您正在考虑如何从旧方法中复制/粘贴新方法。 感觉。 。 。 不知何故。 并排看一下这两种方法,很明显,应该有某种方法在它们之间共享功能,因为它们实际上几乎是相同的。
考虑如何编写可以说明这两种用例的单一方法似乎很自然。 (甚至还有其他必将出现的。)
像这样:
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对象的不同属性(例如颜色或类型)。
通常,您编写一种共享代码的方法。 有没有一种方法可以将另一个方法传递到建议的新filter
方法中,然后在if语句中调用该方法?
经典Java:SAM接口
在Java 8之前,功能只能存在于方法中,而方法始终是类的成员。
使用一种特殊的模式来共享诸如此类的用例,单一抽象方法接口或SAM的功能,就像听起来那样,它只是具有单一方法的接口。 在经典Java中一直使用它。 一个众所周知的示例是Comparator接口,用于提供排序标准以对算法进行排序。
我们可以使用SAM将两个方法filterByType
和filterByColor
重构为一个方法filterByColor
可以具有布尔方法,而filter方法中的for循环在遍历糖果集合时可以调用SAM的boolean方法。
CandyMatcher
将成为我们的SAM。它看起来像这样:
interface CandyMatcher {
boolean matches ( Candy candy );
}
使用这种方法,我们编写了一个新的更通用的filter
方法:
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传递给filter
方法。 我们知道已经使用了按颜色过滤,因此我们现在可以根据使用CandyMatcher的过滤器方法重写filterByColor
方法:
Collection < Candy > filterByColor ( Collection < Candy > candies , Candy . Color color ) {
ColorMatcher matcher = new ColorMatcher ( color );
return filter ( candies , matcher );
}
现在您已经看到了filterByColor
和ColorMatcher
代码,在继续之前,尝试实现可用于匹配常规,花生或椒盐脆饼糖果的TypeMatcher
。
SAM方法的缺点
如果您像我一样实现该方法,则如下所示:
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 );
}
}
从查看新代码可以看到,代码比以前更多了,而不是更少。 我们获得了可扩展性(可以扩展代码以适应将来的用例),但由于代码既冗长又复杂,因此我们也失去了可读性。
解决此问题的一种经典方式是改为使用匿名类。 TypeMatcher
编写整个TypeMatcher
类,只需在需要时创建它即可:
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行代码,具体取决于您的操作方式。 尽管如此,它可以说比只使用一种全班更好。
但是,这里真正的问题是,无论您采用哪种方式,都很难做到。 匿名类不能用于比这更复杂的事情,而创建整个其他类对于您将不只使用一次的东西来说是高开销的。 仅使用这些选项,即使它具有重复的代码给我们带来na的感觉,也很想回到简单的第一个示例。
现代Java:lambdas
两者都是现代Java中解决的问题。 解决方法是lambdas 。 使用lambda,您只需要一行代码即可表达匹配的概念!
Lambda语法
这是一个lambda:
c -> c . getColor (). equals ( color )
这是一个lambda:
c -> c . getType (). equals ( type )
松散地说,lambda具有以下语法:
- 与SAM接口方法的参数匹配的一组括号内的变量。 如果只有一个变量,则可以省略括号。
- 一个“箭头”,它是一个破折号,后跟大于号:
->
- (可选)打开的花括号(仅在随后出现多行时使用)
- 实施代码
- (可选)右花括号
您可以在Java Language Specification的15.27节中找到lambda表达式的形式定义。
Lambda用法
使用lambda而不是匿名类, filterByType
现在变为:
Collection < Candy > filterByType ( Collection < Candy > candies , Candy . Type type ) {
return filter ( candies , c -> c . getType (). equals ( type ));
}
需要注意的一件事是,许多Java IDE现在都可以对此更改进行重构。 为了从前面提到的匿名类转到此处的lambda,我只是在IntelliJ IDEA中应用了重构,而不是自己执行方法的重写。
标准库中的Lambda和功能接口
现在,我们正在使用lambda,但是我们的代码更加简洁了,但新的错误开始困扰我们。 我们的SAM实施还有一个剩余的构件: CandyMatcher
接口。 这仍然感觉像我们为了使用lambda而需要的一些样板文件。
也是解决的问题!
为此,Java标准库实际上提供了许多接口,称为功能接口 。 功能接口在Java语言规范的9.8节中进行了定义,因此:
功能接口是仅具有一个抽象方法(除Object的方法之外)的接口,因此表示单个功能协定。
有时我认为功能接口是可以用作lambda类型的接口。 例如,为了将lambda传递给方法,您需要创建一个方法参数以接受它。 该参数是什么类型? 功能界面!
CandyMatcher
接口实际上符合功能接口的技术定义,这就是为什么我们能够在执行重构时仅保留filter
方法的方法签名。
此方法签名:
Collection < Candy > filter ( Collection < Candy > candies , CandyMatcher matcher )
而且它仍然能够将lambda作为matcher
变量传递给它。
但是考虑到标准库已经提供了一个用于此目的的接口,让我们一起去掉CandyMatcher
。 标准库提供的接口是Predicate 。
谓词javadoc表示“代表一个参数的谓词(布尔值函数)”。
正是我们的用例! 因此,我们现在可以将过滤器的方法签名及其实现更改为使用谓词:
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 ;
}
Predicate
具有一种test
方法,我们通过传递lambda来提供其主体。
当然,我们不想在此处predicate
调用变量,因为它可以告诉我们代码是如何工作的,但不能告诉我们它在做什么以及为什么。 一个更好的名字是我们使用的名称, candyMatcher
。 也许candySelector
或类似的东西也可以很好地工作。 但是我在上面的示例中选择了predicate
,因此您可以确切地看到在其中将新概念付诸实践的地方。
现在,调用代码如下所示:
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开发人员将容易理解这一点。
现在,我们甚至不需要filterByColor
和filterByType
方法。 我们删除它们。 filter
方法使用lambda非常简单,因此我们不需要这些额外的代码。
有关lambdas中变量的小工具条
您可能已经注意到我们正在使用单字符变量名称c
。 这可能会让您感到困惑,因为应该假定变量具有有意义的名称,并且当我们考虑变量命名时,它会使代码更具可读性。
尽管如此,类似于for循环中通常使用i
和j
类的单字符变量名称,lambda(尤其是具有单个输入变量的单行)是一种特殊情况。 变量的类型和含义非常明确,范围很小。 因此,对于使用lambda的程序员来说,通常使用像此处所示的单字符名称,此处我们使用c
来表示candy
。
现代Java:流
说到惯用的Java,实际上有一种更好的方法来实现filter方法:使用流。
实际上,Stream类的方法之一是filter
,它的功能与我们的filter
方法完全相同,尽管方式略有不同。
不过,在我们直接跳到那里之前,让我们先谈谈流。
流的定义
流类似于对象的集合,但是Javadoc指出它“以多种方式与集合有所不同”。 您可以阅读Stream javadoc来查看这些差异,但是我认为将Stream
视为可以对其执行一系列操作的数据(尽管会导致某些结果)更容易(尽管不完整)。 这是通过流利的样式完成的,您实际上只是逐个调用一个Stream方法。 例如,如果您有一个Stream,则可以从该流中仅请求100件,然后获取这些件的颜色,然后通过获取每个件的颜色来生成Stream,最后将所有颜色打印出来,像这样:
candyStream . limit ( 100 )
. map ( c -> c . getColor ())
. forEach ( c -> System . out . println ( c ));
如您所见,Stream与集合不同。 对于集合,您始终必须决定如何遍历集合,并逐个应用不同的操作。 使用Stream,您只需要考虑要应用的一系列操作。
我们可以使用糖果用例来举例说明。 糖果从一个“抓包”开始,里面有多种颜色和类型。 我们执行一项操作以将糖果过滤到仅红色部分。 然后,我们应用终端操作来创建仅包含这些片段的新集合。 我们甚至可以将总数作为流水线的终端操作总数来满足给定的用例。
在Java中,这看起来像:
bagOfCandy . stream ()
. filter ( c -> Color . RED . equals ( c . getColor ()))
. count ();
我们这里只需要.stream()
调用,因为我们从一个集合( bagOfCandy
)开始。 如果它已经是Stream,那么就没有必要了。
将我们的最后一个示例重构为流用法
filter
的命令式版本现在看起来像这样:
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 ();
我们意识到,从标准Java库中获取现代Java一直是QC管理器用例所需要的!
为什么Stream比其命令表亲更好
像我们之前那样消除代码是选择Streams的一个很好的理由,但是还有一个更重要的理由。 我们正在消除可能掩盖错误的代码,并用标准库中的代码取代了这些代码,这些代码都是经过实践检验的。 这也减轻了
数据转换的认知开销,以便我们可以从更大的角度关注自己。
另一种表达方式是Stream是声明性的而不是命令性的 。 也就是说,我们是在指示计算机如何处理数据,而不是执行较低级别的步骤。
通过告诉计算机我们想要什么(对其进行流处理,对其进行过滤,对其进行收集),我们不再编写我们认为可以做一件事而实际上却可以做另一件事的代码。 我们并没有陷入杂草丛生的一系列步骤,并希望结果正确。 我们让计算机来处理。
结论
希望您喜欢这篇文章。 您已经通过一个真实示例了解了lambda,谓词和流。 请放心使用本文,如果有足够的篇幅,我将添加更多带有实际示例的文章,其中Java中的流可以简化和改进您的代码。
翻译自: https://dev.to/scottshipp/real-world-java-with-predicates-and-streams-2jlo
java带缓存的输入流