简体   繁体   中英

Checking null fields of an object in JAVA

Let's assume I have an object with x amount of fields. Two are allowed to be non-null and the rest has to be null. I don't want to do a null check field by field, so I wonder if there is a smart way to do this null check with some of the features of the latest java versions.

I don't want to do a null check field by field

You could avoid writing the check yourself but you necessary need to "mark" the constraints on the fields.
For that you could use the Validator API ( JSR 380 which Hibernate 6 provides the default implementation ) to annotate your class fields with @Null and @NotNull .

And validate the instance explicitly with a Validator.
Note that annotated fields could be as mandatory null in a context and not necessarily null in another. And it is compatible with this way as the validator will validate the object only as it is requested : that is on demand.


According to your comment :

I am working on a codebase and your solution is invasive. I would have to get my hands on the low level json parser that creates that pojo. I don't want to do that

In this case you could use a Map external to the current class that you want to validate.
It would allow to maintain the name of the field that you validate and using it in your error message (useful for debugging).

For example :

Foo foo = new Foo();
// set foo fields...

// expected null but was not null
Map<String, Object> hasToBeNullMap = new HashMap<>();
hasToBeNullMap.put("x", foo.getX());
hasToBeNullMap.put("y", foo.getY());
hasToBeNullMap.put("z", foo.getZ());
String errrorMessageForNullExpected = getErrorFieldNames(hasToBeNullMap, Objects::nonNull);

// expected not null but was null
Map<String, Object> hasToBeNotNullMap = new HashMap<>();
hasToBeNotNullMap.put("z", foo.getZ());
String errrorMessageForNotNullExpected = getErrorFieldNames(hasToBeNotNullMap, o -> o == null);

private static String getErrorFieldNames(Map<String, Object> hasToBeNullMap, Predicate<Object> validationPred) {
    return hasToBeNullMap.entrySet()
                         .stream()
                         .filter(validationPred::test)
                         .map(Entry::getKey)
                         .collect(joining(","));
}

You can create stream for all the fields in POJO and can check for null

return Stream.of(id, name).anyMatch(Objects::isNull);

or

return Stream.of(id, name).allMatch(Objects::isNull);

If there are only a few fields in the object, and you know it won't change frequently, you could list them as Stream.of arguments as per Deadpool's answer. The drawback is violation of the DRY principle: you are repeating the field names: once in the POJO definition and again in the argument list.

If you have many fields (or don't want to repeat yourself) you could use reflection:

boolean valid = Stream.of(YourPojoClass.class.getDeclaredFields())
    .filter(f -> !(f.getName().equals("fieldname allowed to be null") || f.getName.equals("the other field name")))
    .allMatch(f -> {
        f.setAccessible(true);
        try {
            return f.get(o) == null;
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    });

Note that use of reflection can have a small performance penalty, likely to be insignificant compared to parsing a JSON string obtained from a web service.

If you have primitive fields (eg int , boolean , char ) and you want to include them in the checks, restricting them to default values ( 0 , false , '\\0' ) then use the following code:

    .allMatch(f -> {
        f.setAccessible(true);
        try {
            return (f.getType() == boolean.class && f.getBoolean(o) == false)
                    || (f.getType().isPrimitive() && f.getDouble(o) == 0)
                    || f.get(o) == null;
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    });

When precisely do you want to do the check? The best solution is to create an immutable class and only provide 1 constructor. Don't add setters.

private String name;
private String nickname;
// Rest of the fields with default (null) values
public Person(String name, String nickname) {
 this.name = name;
 this.nickname = nickname;
}
private Person(){}
// Getters, but no setters

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM