我想编写一个类,其中包含不止一种类型的多个字段,但是在任何时候,实例对象中只有一个字段具有非null值。
到目前为止,我所做的事情看起来并不十分干净。
class ExclusiveField {
private BigInteger numericParam;
private String stringParam;
private LocalDateTime dateParam;
public void setNumericParam(BigInteger numericParam) {
unsetAll();
this.numericParam = Objects.requireNonNull(numericParam);
}
public void setStringParam(String stringParam) {
unsetAll();
this.stringParam = Objects.requireNonNull(stringParam);
}
public void setDateParam(LocalDateTime dateParam) {
unsetAll();
this.dateParam = Objects.requireNonNull(dateParam);
}
private void unsetAll() {
this.numericParam = null;
this.stringParam = null;
this.dateParam = null;
}
}
Run Code Online (Sandbox Code Playgroud)
Java是否以某种方式支持此模式,或者有更合适的方法来实现?
Hol*_*ger 29
一个对象只有一个非null字段的最简单方法是实际上只有一个字段并假定所有其他字段都是null隐式的。您只需要另一个标签字段即可确定哪个字段不是- null。
由于在您的示例中,所有替代方案似乎都与值的类型有关,因此类型本身可以是标记值,例如
class ExclusiveField {
private Class<?> type;
private Object value;
private <T> void set(Class<T> t, T v) {
value = Objects.requireNonNull(v);
type = t;
}
private <T> T get(Class<T> t) {
return type == t? t.cast(value): null;
}
public void setNumericParam(BigInteger numericParam) {
set(BigInteger.class, numericParam);
}
public BigInteger getNumericParam() {
return get(BigInteger.class);
}
public void setStringParam(String stringParam) {
set(String.class, stringParam);
}
public String getStringParam() {
return get(String.class);
}
public void setDateParam(LocalDateTime dateParam) {
set(LocalDateTime.class, dateParam);
}
public LocalDateTime getDateParam() {
return get(LocalDateTime.class);
}
}
Run Code Online (Sandbox Code Playgroud)
如果类型不是唯一的区分符,则需要定义不同的键值。An enum是自然的选择,但是不幸的是,enum常量不能提供类型安全性。因此,替代方案如下所示:
class ExclusiveField {
private static final class Key<T> {
static final Key<String> STRING_PROPERTY_1 = new Key<>();
static final Key<String> STRING_PROPERTY_2 = new Key<>();
static final Key<BigInteger> BIGINT_PROPERTY = new Key<>();
static final Key<LocalDateTime> DATE_PROPERTY = new Key<>();
}
private Key<?> type;
private Object value;
private <T> void set(Key<T> t, T v) {
value = Objects.requireNonNull(v);
type = t;
}
@SuppressWarnings("unchecked") // works if only set() and get() are used
private <T> T get(Key<T> t) {
return type == t? (T)value: null;
}
public void setNumericParam(BigInteger numericParam) {
set(Key.BIGINT_PROPERTY, numericParam);
}
public BigInteger getNumericParam() {
return get(Key.BIGINT_PROPERTY);
}
public void setString1Param(String stringParam) {
set(Key.STRING_PROPERTY_1, stringParam);
}
public String getString1Param() {
return get(Key.STRING_PROPERTY_1);
}
public void setString2Param(String stringParam) {
set(Key.STRING_PROPERTY_2, stringParam);
}
public String getString2Param() {
return get(Key.STRING_PROPERTY_2);
}
public void setDateParam(LocalDateTime dateParam) {
set(Key.DATE_PROPERTY, dateParam);
}
public LocalDateTime getDateParam() {
return get(Key.DATE_PROPERTY);
}
}
Run Code Online (Sandbox Code Playgroud)
And*_*ner 21
将您的unsetAll方法更改为setAll:
private void setAll(BigInteger numericParam, String stringParam, LocalDateTime dateParam) {
this.numericParam = numericParam;
this.stringParam = stringParam;
this.dateParam = dateParam;
}
Run Code Online (Sandbox Code Playgroud)
然后从您的公共设置者中调用,例如:
public void setNumericParam(BigInteger numericParam) {
setAll(Objects.requireNonNull(numericParam), null, null);
}
Run Code Online (Sandbox Code Playgroud)
请注意,该值Objects.requireNonNull是在之前评估的setAll,因此如果要传递,则在null numericParam不更改任何内部状态的情况下将失败。
前言:我的回答是理论上的,它所描述的实践在Java中并不实际。他们根本没有得到很好的支持,按照惯例,您将“违背原则”。无论如何,我认为这是一个整洁的模式,我想我会分享。
Java的类是产品类型。当class C包含类型的成员T1,T2,..., Tn,则类对象的有效值C是笛卡尔乘积的值T1,T2,..., Tn。例如,如果class C包含一个bool(具有2值)和byte(具有256值),则512可能存在C对象的值:
(false, -128)(false, -127)(false, 0)
...(false, 127)(true, -128)(true, -127)(true, 0)
...(true, 127)在您的示例中,的理论可能值ExclusiveField等于numberOfValuesOf(BigInteger.class) * numberOfValuesOf(String) * numberOfValuesOf(LocalDateTime)(请注意乘积,这就是为什么它被称为乘积类型),但这并不是您真正想要的。您正在寻找消除大量这些组合的方法,以便唯一的值是一个字段为非空值而其他字段为null时。有numberOfValuesOf(BigInteger.class) + numberOfValuesOf(String) + numberOfValuesOf(LocalDateTime)。请注意附加项,这表明您要查找的是“求和类型”。
正式而言,您在这里寻找的是带标签的联合(也称为变体,变体记录,选择类型,有区别的联合,不相交的联合或求和类型)。带标记的联合是一种类型,其值是在成员的一个值之间进行选择。在前面的例子,如果C是和类型,将只有258可能的值:-128,-127,..., ,0,127,。truefalse
我建议您检查C中的并集,以了解其工作原理。C的问题在于,它的联合体无法“记住”在任何给定时间都处于活动状态的“案例”,这在很大程度上违背了“求和类型”的整个目的。为了解决这个问题,您可以添加一个“标签”,它是一个枚举,其值告诉您联合状态是什么。“联合”存储有效载荷,“标签”告诉您有效载荷的类型,因此“标记为联合”。
问题是,Java确实没有内置这样的功能。幸运的是,我们可以利用类层次结构(或接口)来实现此功能。从本质上讲,您每次需要时都必须自己滚动,这很痛苦,因为它需要很多样板,但是从概念上讲很简单:*对于n不同的情况,您将创建n不同的私有类,每个私有类都存储与该情况相关的成员*您可以将这些私有类统一在一个通用的基类(通常是抽象类)或接口下*将这些类包装在一个转发类中,该类公开了所有公共API,同时隐藏了私有内部(以确保没有其他人可以实现您的接口)。
您的界面可能包含n方法,每个方法都类似getXYZValue()。这些方法可以作为默认方法使用,其中默认实现返回null(对于Object值,但不适用于基元,Optional.empty()(对于Optional<T>值)或throw异常(粗略,但没有更好的方法来处理诸如int)。我不喜欢这种方法,因为接口相当不精巧。符合类型并不真正符合接口,只是接口的1 / n。
相反,您可以使用与uhhh模式匹配的模式。您将创建一个match采用n不同Function参数的方法(例如),其类型与已区分联合的案例的类型相对应。要使用已区分联合的值,请对其进行匹配并提供nlambda表达式,每个表达式的行为都类似于switch语句中的情况。当调用时,动态调度系统将调用match与特定storage对象关联的实现,该对象将调用正确的n功能之一并传递其值。
这是一个例子:
import java.util.Optional;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Consumer;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.math.BigInteger;
class Untitled {
public static void main(String[] args) {
List<ExclusiveField> exclusiveFields = Arrays.asList(
ExclusiveField.withBigIntegerValue(BigInteger.ONE),
ExclusiveField.withDateValue(LocalDateTime.now()),
ExclusiveField.withStringValue("ABC")
);
for (ExclusiveField field : exclusiveFields) {
field.consume(
i -> System.out.println("Value was a BigInteger: " + i),
d -> System.out.println("Value was a LocalDateTime: " + d),
s -> System.out.println("Value was a String: " + s)
);
}
}
}
class ExclusiveField {
private ExclusiveFieldStorage storage;
private ExclusiveField(ExclusiveFieldStorage storage) { this.storage = storage; }
public static ExclusiveField withBigIntegerValue(BigInteger i) { return new ExclusiveField(new BigIntegerStorage(i)); }
public static ExclusiveField withDateValue(LocalDateTime d) { return new ExclusiveField(new DateStorage(d)); }
public static ExclusiveField withStringValue(String s) { return new ExclusiveField(new StringStorage(s)); }
private <T> Function<T, Void> consumerToVoidReturningFunction(Consumer<T> consumer) {
return arg -> {
consumer.accept(arg);
return null;
};
}
// This just consumes the value, without returning any results (such as for printing)
public void consume(
Consumer<BigInteger> bigIntegerMatcher,
Consumer<LocalDateTime> dateMatcher,
Consumer<String> stringMatcher
) {
this.storage.match(
consumerToVoidReturningFunction(bigIntegerMatcher),
consumerToVoidReturningFunction(dateMatcher),
consumerToVoidReturningFunction(stringMatcher)
);
}
// Transform 'this' according to one of the lambdas, resuling in an 'R'.
public <R> R map(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return this.storage.match(bigIntegerMatcher, dateMatcher, stringMatcher);
}
private interface ExclusiveFieldStorage {
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
);
}
private static class BigIntegerStorage implements ExclusiveFieldStorage {
private BigInteger bigIntegerValue;
BigIntegerStorage(BigInteger bigIntegerValue) { this.bigIntegerValue = bigIntegerValue; }
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return bigIntegerMatcher.apply(this.bigIntegerValue);
}
}
private static class DateStorage implements ExclusiveFieldStorage {
private LocalDateTime dateValue;
DateStorage(LocalDateTime dateValue) { this.dateValue = dateValue; }
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return dateMatcher.apply(this.dateValue);
}
}
private static class StringStorage implements ExclusiveFieldStorage {
private String stringValue;
StringStorage(String stringValue) { this.stringValue = stringValue; }
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return stringMatcher.apply(this.stringValue);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3042 次 |
| 最近记录: |