Transformer模式是Java(以及可能仅具有使用场所差异和不变参数类型的其他OO语言)的设计模式,可帮助子类型层次结构内的对象将自己流畅地转换为任何类型的对象。
语境
我一直在关注与Jim Laskey发行的JDK-8203703相关的OpenJDK线程( 9月18-21日 , 11月12-13日, 11月13-30日, 12月3-4日 ),然后我想到了一个主意。 让我回顾一下讨论的相关部分。
String.transform的建议
根据JDK-8203703的提案可归结为以下新增内容:
public final class String implements /*...*/ CharSequence {// ...public <R> R transform(Function<? super String, ? extends R> f) {return f.apply(this);}// ...
}
如您所见,此方法仅自身调用给定的Function
即可。 但是,它对于链接实用程序方法非常有用,例如Apache Commons的 StringUtils中的方法:
String result = string.toLowerCase().transform(StringUtils::stripAccents).transform(StringUtils::capitalize);
通常,我们必须写:
String result = StringUtils.capitalize(StringUtils.stripAccents(string.toLowerCase()));
考虑CharSequence.transform
在某个时候,艾伦·贝特曼(Alan Bateman) 提出了在CharSequence
中潜在定义transform
的问题 :
<R> R transform(Function<? super CharSequence, ? extends R> f)
这将具有能够在任何CharSequence
上应用基于CharSequence
的实用程序方法(例如StringUtils.isNumeric )的好处,例如:
boolean isNumeric = charSequence.transform(s -> StringUtils.defaultIfBlank('0')).transform(StringUtils::isNumeric);
但是,正如RémiForax所指出的 ,此签名的问题在于:
- 如果它是由
String
继承的:大多数实用程序方法都将String
作为参数–这样的方法将不起作用(例如StringUtils :: capitalize ), - 如果它被
String
覆盖 :由于以下原因,无法进行有用的覆盖:Function<? super String, R>
结果, CharSequence.transform
的主题已被删除。
问题
总而言之,问题在于能够进行转换 :
- 一个
CharSequence
,使用Function
即需要CharSequence
或Object
(? super CharSequence
), - 一个
String
,使用接受String
或其任何父类型(? super String
)的Function
。
当我在这里查看这些下 限时 ,我意识到我已经看到了这种问题(参见Filterer Pattern )。
因此,这个问题归结为:如何协变地指定Function
的反 变界。
解
Java不支持逆变参数类型 ,并且它的语法也没有提供一种方法来协变( ? extends
)指定在单个声明中绑定的逆变( ? super
)。 然而,有可能做到这一点在两个分开的宣言,通过中间辅助类型的装置。
假设我们要为泛型Function<? super T, ? extends R>
Function<? super T, ? extends R>
Function<? super T, ? extends R>
,我们需要:
- 将上面的
Function
参数移动到参数为T
的辅助接口中 , - 将此辅助接口与上限 (
? extends T
)一起用作返回类型。
变压器接口
我定义了这样的帮助程序接口(我称之为Transformer
),如下所示:
@FunctionalInterface
interface Transformer<T> {<R> R by(Function<? super T, ? extends R> f);
}
可转换的接口
定义了Transformer
,我们可以定义以下称为Transformable
基本接口:
interface Transformable {Transformer<?> transformed();
}
该接口本身并不能做很多事情,但我将其视为以下方面的规范 :
- 子类型实现者 :它提醒他们使用适当的上限覆盖已
transformed
方法,并加以实现, - 子类型用户 :提醒他们可以调用
transformed().by(f)
。
总结起来,这对( Transformer
& Transformable
)让我们替换:
-
obj.transform(function)
- 使用:
obj.transformed().by(function)
样例实施
回到String
之前,让我们看看实现这两个接口有多么容易:
class Sample implements Transformable {@Overridepublic Transformer<Sample> transformed() {return this::transform; // method reference}private <R> R transform(Function<? super Sample, ? extends R> f) {return f.apply(this);}
}
如您所见,所需要的只是对transform
的方法引用 。
transform
方法被设为私有,因此当子类型定义自己的(适当地, 下界 ) transform
时,它们之间不会发生冲突。
上下文中的解决方案
上下文中的实现
它如何应用于CharSequence
和String
? 首先,我们将CharSequence
扩展为Transformable
:
public interface CharSequence extends Transformable {// ...@OverrideTransformer<? extends CharSequence> transformed();// ...
}
然后,我们transformed
在String
实现transformed
,返回对public transform
方法的方法引用(已在JDK 12中添加 ):
public final class String implements /*...*/ CharSequence {// ...@Overridepublic Transformer<String> transformed() {return this::transform;}// ...
}
请注意,我们对transformed
的返回类型进行了协变更改: Transformer<? extends CharSequence>
Transformer<? extends CharSequence>
→ Transformer<String>
。
相容性风险
我认为添加CharSequence.transformed
的兼容性风险很小。 仅对于那些已经具有无参数transformed
方法的CharSequence
子类,它可能会破坏向后兼容性(这似乎不太可能)。
上下文中的用法
对于使用String
不会改变,因为有呼吁没有一点transformed().by()
在transform()
但是,通用CharSequence
的用法将需要诉诸transformed().by()
因为它可能有很多实现,因此transform
方法必须是private
:
boolean isNumeric = charSequence.transformed().by(s -> StringUtils.defaultIfBlank('0')).transformed().by(StringUtils::isNumeric);
性能
如果您不熟悉JVM (最常表示HotSpot )及其JIT编译器的工作方式,那么您可能想知道这种明显的额外对象创建( Transformer
in transformed
)是否不会影响性能。
幸运的是,由于有了转义分析 *和标量替换 ,该对象从未在堆上分配。 答案是:不会,不会。
* 此Wikipedia条目包含错误的陈述:“ 因此,编译器可以安全地在堆栈上分配这两个对象。 ”正如 AlekseyShipilёv解释的那样 ,Java不会在堆栈上分配整个对象。
基准测试
如果您需要证明,这里有一些基准(使用AlekseyShipilёv出色的JMH基准线束 )。 因为我不能(容易),添加必要的方法,以String
,我创建了一个简单的包装过String
,并实现了在它之上的标杆。
基准测试toLowerCase()
操作:
- 在两个字符串上:
-
"no change"
(无操作) -
"Some Change"
-
- 使用三种通话类型:
- 直接(基准)
-
transform()
-
transformed().by()
您可以在GitHub gist中找到此基准测试的完整源代码。
结果如下(在Oracle JDK 8上运行,花费了50分钟):
Benchmark (string) Mode Cnt Score Error UnitsTransformerBenchmark.baseline no change avgt 25 22,215 ± 0,054 ns/op
TransformerBenchmark.transform no change avgt 25 22,540 ± 0,039 ns/op
TransformerBenchmark.transformed no change avgt 25 22,565 ± 0,059 ns/opTransformerBenchmark.baseline Some Change avgt 25 63,122 ± 0,541 ns/op
TransformerBenchmark.transform Some Change avgt 25 63,405 ± 0,196 ns/op
TransformerBenchmark.transformed Some Change avgt 25 62,930 ± 0,209 ns/op
如您所见,对于这两个字符串,这三种调用类型之间没有性能差异。
摘要
我意识到, Transformable
可能太“奢侈”了,无法真正将其纳入JDK。 实际上,即使仅由CharSequence
和String
返回的Transformer
也不值得。 这是因为对CharSequence
的一元运算似乎并不常见(例如StringUtils仅包含少数几个)。
但是,我发现“ Transformer
和“ Transformable
的基本概念很诱人。 因此,我希望您喜欢阅读,并在某些情况下会发现它很有用
翻译自: https://www.javacodegeeks.com/2019/02/transformer-pattern.html