Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/308.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在对象中只允许一个非空字段的好方法是什么_Java - Fatal编程技术网

Java 在对象中只允许一个非空字段的好方法是什么

Java 在对象中只允许一个非空字段的好方法是什么,java,Java,我想编写一个包含1个以上不同类型字段的类,但在任何时候,实例对象中只有一个字段具有非null值 到目前为止,我所做的看起来并不干净 类独占字段{ 私有BigInteger numericParam; 私有字符串stringParam; 私有LocalDateTime-dateParam; 公共void setNumericParam(BigInteger numericParam){ 取消全部(); this.numericParam=Objects.requirennull(numericPa

我想编写一个包含1个以上不同类型字段的类,但在任何时候,实例对象中只有一个字段具有非null值

到目前为止,我所做的看起来并不干净

类独占字段{
私有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)