Java 在对象中只允许一个非空字段的好方法是什么
我想编写一个包含1个以上不同类型字段的类,但在任何时候,实例对象中只有一个字段具有非null值 到目前为止,我所做的看起来并不干净Java 在对象中只允许一个非空字段的好方法是什么,java,Java,我想编写一个包含1个以上不同类型字段的类,但在任何时候,实例对象中只有一个字段具有非null值 到目前为止,我所做的看起来并不干净 类独占字段{ 私有BigInteger numericParam; 私有字符串stringParam; 私有LocalDateTime-dateParam; 公共void setNumericParam(BigInteger numericParam){ 取消全部(); this.numericParam=Objects.requirennull(numericPa
类独占字段{
私有BigInteger numericParam;
私有字符串stringParam;
私有LocalDateTime-dateParam;
公共void setNumericParam(BigInteger numericParam){
取消全部();
this.numericParam=Objects.requirennull(numericParam);
}
public void setStringParam(字符串stringParam){
取消全部();
this.stringParam=Objects.requirennull(stringParam);
}
public void setDateParam(LocalDateTime dateParam){
取消全部();
this.dateParam=Objects.requirennull(dateParam);
}
私有所有{
this.numericParam=null;
this.stringParam=null;
this.dateParam=null;
}
}
Java是否以某种方式支持此模式,或者是否有更合适的方式支持此模式?将
unsetAll
方法更改为setAll
:
private void setAll(BigInteger numericParam, String stringParam, LocalDateTime dateParam) {
this.numericParam = numericParam;
this.stringParam = stringParam;
this.dateParam = dateParam;
}
然后从公共设置器调用,如:
public void setNumericParam(BigInteger numericParam) {
setAll(Objects.requireNonNull(numericParam), null, null);
}
请注意,Objects.requirennoull
是在setAll
之前计算的,因此如果要传入null
numericParam
,这将在不更改任何内部状态的情况下失败。为什么不简单
public void setNumericParam(BigInteger numericParam) {
this.numericParam = Objects.requireNonNull(numericParam);
this.stringParam = null;
this.dateParam = 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);
}
}
前言:我的回答更具理论性,它所描述的实践在Java中并不实际。他们只是没有得到很好的支持,按照惯例,你会“逆潮流而行”。不管怎样,我认为这是一个很好的模式,我想我会分享
Java的类是。当类C
包含类型为T1
,T2
,…,Tn
的成员时,类C对象的有效值为T1
,T2
,…,Tn
的值。例如,如果类C
包含bool
(具有2
值)和byte
(具有256
值),则C
对象可能具有512
值:
(false,-128)
(false,-127)
(false,0)
(false,127)
(true,-128)
(true,-127)
(true,0)
(正确,127)
ExclusiveField
的理论可能值等于numberOfValuesOf(biginger.class)*numberOfValuesOf(String)*numberOfValuesOf(LocalDateTime)
(注意乘法,这就是为什么它被称为产品类型),但这并不是您真正想要的。您正在寻找消除大量这些组合的方法,以便只有一个字段为非null,而其他字段为null时才有值。有numberOfValuesOf(biginger.class)+numberOfValuesOf(String)+numberOfValuesOf(LocalDateTime)
。注意这个加法,这表示您要查找的是“总和类型”
正式地说,您在这里寻找的是一个(也称为变量、变量记录、选择类型、有区别的并集、不相交的并集或求和类型)。标记的联合是一种类型,其值是成员的一个值之间的选择。在前面的示例中,如果C
是一个求和类型,则只有258个可能的值:-128
,-127
,…,0
,127
,true
,false
我建议你去看看,了解一下它是如何工作的。C的问题是,它的工会无法“记住”哪个“案例”在任何给定点都是活跃的,这在很大程度上违背了“总和类型”的整体目的。为了解决这个问题,您需要添加一个“标记”,它是一个枚举,其值告诉您联合的状态。“Union”存储有效负载,“tag”告诉您有效负载的类型,因此是“taged Union”
问题是,Java实际上没有内置这样的功能。幸运的是基本上,每次需要时,您都必须自己滚动,这是一种痛苦,因为它需要大量的样板文件,但概念上很简单:
- 对于
不同的情况,可以创建n
不同的私有类,每个私有类存储与该情况相关的成员n
- 您可以将这些私有类统一到公共基类(通常是抽象类)或接口下
- 您将这些类包装在一个转发类中,该类公开一个公共API,同时隐藏私有内部(以确保没有其他人可以实现您的接口)
n
方法,每个方法类似于getXYZValue()
。这些方法可以设置为,其中默认实现返回null
(对于对象
值,但不适用于基本体,可选.empty()
(对于可选
值),或者抛出异常(总的,但对于int
等基本体值,没有更好的方法).我不喜欢这种方法,因为接口是不真实的。一致类型并不真正符合接口,只符合接口的1/n次
相反,您可以使用模式匹配uhh,pattern。您可以创建一种方法(例如,匹配
),该方法采用不同的函数
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);
}
}
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);
}
}
}
public void SetExclusiveValue(String param, Object val){
this.UnsetAll();
Class cls = this.getClass();
Field fld = cls.getDeclaredField(param); //Maybe need to set accessibility temporarily? Or some other kind of check.
//Also need to add check for fld existence!
fld.set(this, Objects.requireNonNull(val));
}
private void UnsetAll(){
Class cls = this.getClass();
Field[] flds = cls.getDeclaredFields();
for (Field fld : flds){
fld.set(this,null);
}
}
public abstract class Field
{
private int id;
private Class<?> type;
private Object value;
public Field(int id, Object value) {
this.id = id;
this.type = value.getClass();
this.value = value;
}
public abstract int getPosition();
}
import java.math.BigInteger;
public class BigIntegerField extends Field
{
public BigIntegerField(int id, BigInteger numericParam) {
super(id, numericParam);
}
@Override
public int getPosition() {
return 0;
}
}
public String toSQL(int columnsCount) {
List<String> rows = new ArrayList<>(Collections.nCopies(columnsCount, "NULL"));
rows.set(getPosition(), String.valueOf(value));
return String.format("SOME SQL COMMAND (%d, %s, %s)", id, type.getName(), String.join(", ", rows));
}
package com.stackoverflow.legacy_field;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public abstract class Field
{
private int id;
private Class<?> type;
private Object value;
public Field(int id, Object value) {
this.id = id;
this.type = value.getClass();
this.value = value;
}
public abstract int getPosition();
public static void main(String[] args) {
List<Field> fields = Arrays.asList(new BigIntegerField(3, BigInteger.TEN),
new StringField(17, "FooBar"),
new DateTimeField(21, LocalDateTime.now()));
for (Field field : fields) {
System.out.println(field.toSQL(3));
}
}
public String toSQL(int columnsCount) {
List<String> rows = new ArrayList<>(Collections.nCopies(columnsCount, "NULL"));
rows.set(getPosition(), String.valueOf(value));
return String.format("SOME SQL COMMAND (%d, %s, %s)", id, type.getName(), String.join(", ", rows));
}
}
package com.stackoverflow.legacy_field;
import java.math.BigInteger;
public class BigIntegerField extends Field
{
public BigIntegerField(int id, BigInteger numericParam) {
super(id, numericParam);
}
@Override
public int getPosition() {
return 0;
}
}
package com.stackoverflow.legacy_field;
public class StringField extends Field
{
public StringField(int id, String stringParam) {
super(id, stringParam);
}
@Override
public int getPosition() {
return 1;
}
}
package com.stackoverflow.legacy_field;
import java.time.LocalDateTime;
public class DateTimeField extends Field
{
public DateTimeField(int id, LocalDateTime value) {
super(id, value);
}
@Override
public int getPosition() {
return 2;
}
}
SOME SQL COMMAND (3, java.math.BigInteger, 10, NULL, NULL)
SOME SQL COMMAND (17, java.lang.String, NULL, FooBar, NULL)
SOME SQL COMMAND (21, java.time.LocalDateTime, NULL, NULL, 2019-05-09T09:39:56.062)