Reputation: 2334
I have a class with a hasField
function that checks if a field is present and not null, and a getField
function that returns the value of the field (or null if not present).
In my code when I call getField
right after checking hasField
, I know that getField is not going to return null, but the IDE Inspection (Constant Conditions and Exceptions) doesn't know that. I get a bunch of method method name
may produce a NullPointerException
I'm trying to find a clean way to make this warning go away.
Workarounds
Here are some workarounds I could do but I find all of these hacky:
getField
with Objects.requireNotnull
, the code would be no-op. Would prefer not doing that as it makes the code slightly less readable.Ideal solution
Would I be able to somehow set up the warnings in such a way that if hasField
is true, then getField
will return a non-null? I looked into JetBrains Contract Annotations but doing what I want here seems to be beyond what is supported with @Contract
Code Sample
Here's a minimum working code sample that demonstrates the issue:
import javax.annotation.Nullable;
public class Hello {
private Hello(){}
public static void main(String[] args) {
TestClass test1 = new TestClass(null);
if (test1.hasSample()) {
System.out.println(test1.getSample().equals("abc"));
}
}
}
class TestClass {
private final String sample;
TestClass(String field) { this.sample = field; }
boolean hasSample() { return sample != null; }
@Nullable public String getSample() { return sample; }
}
I get the following warning
Method invocation
equals
may produceNullPointerException
I'd ideally want to be able to tell IDE that getSample is not null when hasSample is true.
Upvotes: 2
Views: 911
Reputation: 100149
Disclosure I'm IntelliJ IDEA developer responsible for this subsystem
No, it's not possible now. There's no better solution than possible workarounds you already listed, assuming that you cannot change the API. The closest thing we have is the inlining of very trivial methods. However, it works only if:
hasSample()
and getSample()
are called from the same classE.g. this feature works in the following code:
final class TestClass { // if final is removed, the warning will appear again
private final String sample;
TestClass(String field) { this.sample = field; }
boolean hasSample() { return sample != null; }
@Nullable
public String getSample() { return sample; }
@Override
public String toString() {
if (hasSample()) {
return "TestClass: "+getSample().trim(); // no warning on trim() invocation here
}
return "TestClass";
}
}
For now, I can only suggest refactoring your APIs to Optionals like this:
import java.util.Optional;
public class Hello {
private Hello(){}
public static void main(String[] args) {
TestClass test1 = new TestClass(null);
test1.getSample().ifPresent(s -> System.out.println(s.equals("abc")));
// or fancier: test1.getSample().map("abc"::equals).ifPresent(System.out::println);
}
}
final class TestClass {
private final String sample;
TestClass(String field) { this.sample = field; }
public Optional<String> getSample() { return Optional.ofNullable(sample); }
}
Upvotes: 2