I want to write a class with more than 1 fields of different types but at any time, there is one and only one field of an instance object having non null value.
What I did so far does not look really clean.
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; } }
Does Java support this pattern somehow or is there a more decent way to do it?
You can ignore null fields at the class level by using @JsonInclude(Include. NON_NULL) to only include non-null fields, thus excluding any attribute whose value is null. You can also use the same annotation at the field level to instruct Jackson to ignore that field while converting Java object to json if it's null.
Defining a field in your schema as NonNull means that GraphQL promises to always return a value when the field is queried. It allows clients to do fewer response validation checks in their code and improves static analysis.
Include. NON_NULL: Indicates that only properties with not null values will be included in JSON. Include. NON_EMPTY: Indicates that only properties that are not empty will be included in JSON. Non-empty can have different meaning for different objects such as List with size zero will be considered as empty.
The simplest approach for an object to have only one non-null
field, is to actually have only one field and assume all others to be null
implicitly. You only need another tag field, to determine which field is non-null
.
Since in your example, all alternatives seem to be about the type of the value, the type itself could be the tag value, e.g.
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); } }
If the type is not the only differentiator, you need to define distinct key values. An enum
would be a natural choice, but unfortunately, enum
constants can not provide the type safety. So, the alternative would look like:
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); } }
Change your unsetAll
method to setAll
:
private void setAll(BigInteger numericParam, String stringParam, LocalDateTime dateParam) { this.numericParam = numericParam; this.stringParam = stringParam; this.dateParam = dateParam; }
Then invoke from your public setters like:
public void setNumericParam(BigInteger numericParam) { setAll(Objects.requireNonNull(numericParam), null, null); }
Note that Objects.requireNonNull
is evaluated before setAll
, so if you were to pass in a null
numericParam
, this would fail without changing any internal state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With