Home » Language IDEs » Java Development Tools (JDT) » @NonNullByDefault Questions
@NonNullByDefault Questions [message #1518297] |
Sat, 20 December 2014 01:52 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
When the @NonNullByDefault Javadoc says:
Quote:Note: Since org.eclipse.jdt.annotation 2.0.0, this annotation also applies to field and local variable declarations
Does it really mean to say something more like:
Quote:Note: Since org.eclipse.jdt.annotation 2.0.0, this annotation may also be used to annotate field and local variable declarations
?
I'm asking because I was initially reading that sentence as saying any local variables would actually be considered non-null by default, so I'm then confused by even a simple test case...
import org.eclipse.jdt.annotation.*;
@NonNullByDefault
public class Test {
public static final String test() {
final String s = "test".intern();
return s; // Why do I get a warning here "Null type safety: The expression of type 'String' needs unchecked conversion to conform to '@NonNull String'"?
}
}
Now, if I remove the intern() call, the warning on the return disappears, which leads me to believe the nullability of s is determined by some inferencing, which wouldn't be needed if it were actually affected by the @NonNullByDefault?
Thanks!
[Updated on: Sat, 20 December 2014 05:31] Report message to a moderator
|
|
| | |
Re: @NonNullByDefault Questions [message #1519553 is a reply to message #1519498] |
Sat, 20 December 2014 18:55 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
Stephan Herrmann wrote on Sat, 20 December 2014 13:15IOW: We defined a @NNBD scoped to this particular variable (field or local), but the effect is constraint to the select elements of type DefaultLocation
That clarifies things, thanks.
I was aware of DefaultLocation for toggling the effects of @NNBD, but was viewing it more as something providing configuration options for @NNBD behaviour, in places where that behaviour could be customized, not so much as a constraint on it's behavior in other situations.
This makes me a little sad there isn't a DefaultLocation.LOCAL_VARIABLE configured for @NNBD by default, as I'd rather not rely on the inferencing, as the resulting warnings often show up in what feels like the wrong places (e.g. in my example, where I intended for 's' to be non-null), and that means having to annotate all local variables individually to avoid it. I guess it's good that you can use @NNBD as a "shorthand" while doing so, but that still doesn't really appear to save a whole lot. Maybe my desire to annotate local variables will lessen in the future when there's less un-annotated legacy code to throw off the inferencing.
[Updated on: Sat, 20 December 2014 19:10] Report message to a moderator
|
|
| |
Re: @NonNullByDefault Questions [message #1523417 is a reply to message #1519665] |
Mon, 22 December 2014 22:54 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
Heh, I'm still waiting for my brain to merge the git pull request on the patch I sent it for discerning between a TYPE_ARGUMENT and a TYPE_PARAMETER/variable
I've definitely been viewing this in an overly left-hand-centric way, thinking of 'T' and '?' as specifying the type (and where @NNBD would apply), and not lending enough importance to the bound on the right. Your example helps clarify what's going on, and that wildcards are, in fact, not affected by @NNBD. Thanks!
I had been wracking my brain over how @NNBD would impact code like @NonNullByDefault public <T,R> void exampleMethod(final Function<? super T,? extends R> funcArg) { .. } , trying to figure out how, if type bounds are impacted by @NNBD, to keep funcArg unconstrained. Moving from concentrating on the '?' (which I now understand won't be affected), over to concentrating on the 'T' and 'R' instead, I think, with those being variables, that @NNBD shouldn't impact funcArg at all then?
I've also been tripped up a bit by some of the early bugs in this stuff, so I'm hoping with those out of the way it'll be easier to get a solid grip on it. Thanks for all your work and patience w questions
|
|
|
Re: @NonNullByDefault Questions [message #1530471 is a reply to message #1523417] |
Fri, 26 December 2014 23:01 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
import java.io.*;
import java.util.*;
import java.util.function.*;
import org.eclipse.jdt.annotation.*;
@NonNullByDefault
public class Test {
public static final <T,R> Optional<R> applyOptional(final T input, final Function<? super T,? extends R> function) {
return Optional.ofNullable(function.apply(input));
}
public static final <T,R> @NonNull R applyRequired(final T input, final Function<? super T,? extends R> function) { // Warning on '@NonNull R': "The nullness annotation is redundant with a default that applies to this location"
return Objects.requireNonNull(function.apply(input));
}
public static final @Nullable Integer readIntegerFile(final String fileName) {
final File file = new File(fileName);
if (!file.canRead()) return null;
try (Scanner scanner = new Scanner(new FileReader(fileName))) {
return scanner.useDelimiter("\\A").hasNext() ? Integer.valueOf(scanner.next()) : null;
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static final void main(final String[] args) {
if (applyRequired("sun.arch.data.model", Integer::getInteger) >= 64) {
System.err.println(applyOptional("does_not_matter.txt", Test::readIntegerFile).map(Object::toString).orElse("null"));
} else {
System.err.println(applyRequired("we_need_this.txt", Test::readIntegerFile).toString());
}
return;
}
}
I disagree with this warning. Is this a bug ( https://bugs.eclipse.org/bugs/show_bug.cgi?id=438012 )? Why is that @NonNull redundant, given the @NNBD doesn't include DefaultLocation.TYPE_PARAMETER?
Thanks!
[Updated on: Sat, 27 December 2014 19:40] Report message to a moderator
|
|
|
Re: @NonNullByDefault Questions [message #1530938 is a reply to message #1530471] |
Sat, 27 December 2014 05:21 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
I'm curious how null annotations interact with java.util.Optional...
Optional, by it's very nature, is something which is @Nullable (may or may not have a value). One might initially be inclined to think that would indicate it's type parameter normally be a @Nullable type.
But, when you look a bit closer, the Optional API is all about not exposing that type unless it's non-null. The primary get() method will only return the contents when non-null, otherwise throwing a NoSuchElementException. The Optional.ifPresent(Consumer<T>) would only ever see a non-null argument, etc, etc. This makes me think the Optional class's type parameter should normally be a @NonNull type, where the primary constructor method Optional.of(T) would then automatically be restricted to non-null arguments, as per it's current API/implementation restrictions, and methods like Optional.ofNullable(T) would then just have their parameter overriden to be @Nullable. Should Optional generally use a @NonNull type?
That leads me to Optional.orElse(T)...
I've been making increasing use of Optional as a return type, but it seems less suited for use as a parameter type, and there are a lot of (legacy?) methods which simply accept null arguments. As a result, I often find myself needing to call a '@Nullable T' method when I have an 'Optional<T>' parameter, and as a result I've ended up with a substantial number of inline Optional.orElse(null) calls in those places.
The thing is, null analysis obviously doesn't like Optional<@NonNull T>.orElse(null), given the orElse() method expects a parameter of the same @NonNull T type.
So, to rid myself of these warnings, I'm tempted to head down a path like: Optional<@NonNull T>.<@Nullable T> map(Function.identity()).orElse(null). That is, use the map() method to convert it from a '@NonNull T' to a '@Nullable T' before invoking orElse(null) (you can also use Optional<@Nullable T>.<@NonNull R> map(Objects::requireNonNull) to convert the other direction from @Nullable to @NonNull). The ability to do things like this is why I don't think Optional should actually be restricted to only @NonNull types.
But those solutions leave me in a situation where I'm taking code I know already operates perfectly well before null annotations, and adding in extra operations that could have a real and measurable impact on system performance, just to satisfy compiler warnings. I hesitate.
I don't really know where to RTFM on this kind of thing. Advice?
Thanks!
[Updated on: Sun, 28 December 2014 02:00] Report message to a moderator
|
|
| |
Re: @NonNullByDefault Questions [message #1540642 is a reply to message #1540563] |
Thu, 01 January 2015 16:36 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Re Optional:
Disclaimer: I've never actively used Optional, so I might be missing some clever usage thereof.
My understanding is that Optional is intended as a replacement for null altogether. This would let me think: yes, all signatures of Optional should operate on @NonNull something or throw exceptions. So if you have (legacy) parts for the program where null is a legal value, and new parts, where Optional is used consistently instead of null, you'll need some bridge between them two parts (assuming you know where the boarder is drawn).
The naive bridge might look like this:
interface UniverseWithNull {
void method1(@Nullable String s);
}
@NonNullByDefault
class UnviserveWithoutNull {
void methodX(Optional<String> s, UniverseWithNull delegate) {
delegate(s.isPresent() ? s.get() : null);
}
}
Not particularly pretty, so how can we simplify? orElse() is a good candidate, indeed. We only hit the limitation that different occurrences of 'T' in the API of Optional cannot be distinguished, although some explicitly include null while most exclude null. To make the combination of Optional and null annotations useful, we'll need null annotations in the definition of Optional, s.t. like:
class Optional<T extends @NonNull Object> {
@Nullable T value;
private Optional(T value) { this.value = value; }
public static <@NonNull T> Optional<T> of(T value) {
return new Optional<T>(value);
}
public T get() {
@Nullable T t = this.value;
if (t != null) return t;
else throw new NoSuchElementException("No value present");
}
public @Nullable T orElse(@Nullable T other) {
return (this.value != null) ? this.value : other;
}
}
(See https://bugs.eclipse.org/456487 for issues found on my way ...)
One could also try to leave the declaration of <T> unconstrained and only add @NonNull to all regular usage of T (except for orElse etc.).
Anyway, this only takes us back to https://bugs.eclipse.org/331651, so let's push that one with high priority now!
best,
Stephan
|
|
| | |
Re: @NonNullByDefault Questions [message #1544707 is a reply to message #1540642] |
Sat, 03 January 2015 23:49 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
Stephan Herrmann wrote on Thu, 01 January 2015 11:36To make the combination of Optional and null annotations useful, we'll need null annotations in the definition of Optional
As part of my response, I attempted to annotate Optional myself as well, but quickly hit this:
import java.util.*;
import java.util.function.*;
import org.eclipse.jdt.annotation.*;
public class Test {
/**
* A null-annotated version of {@link Objects#requireNonNull(Object)}.
*/
public static final <T> @NonNull T requireNonNull(final @Nullable T obj) {
if (obj == null) throw new NullPointerException();
return obj;
}
/**
* A null-annotated version of {@link Optional#map(Function)}.
*/
public static final <T,U> @NonNull Optional<U> map(final @NonNull Optional<T> optional, final Function<@NonNull ? super T,? extends U> mapper) {
if (!optional.isPresent()) return requireNonNull(Optional.empty());
final T source = optional.get();
final U result = mapper.apply(source);
return requireNonNull(Optional.<U> ofNullable(result));
}
/**
* A method with a {@link NonNull} {@link DefaultLocation#PARAMETER} and {@link DefaultLocation#RETURN_TYPE}.
*/
public static final @NonNull Integer testMethod(final @NonNull String s) {
final Integer r = Integer.valueOf(s);
if (r == null) throw new NullPointerException();
return r;
}
public static void main(final String[] args) {
final @NonNull Optional<@Nullable String> optNullableString = requireNonNull(Optional.ofNullable("-5"));
final Function<@NonNull String,@NonNull Integer> testMethodRef = Test::testMethod;
map(optNullableString, testMethodRef);
map(optNullableString, Test::testMethod); // Error: Null type mismatch at parameter 1: required '@NonNull String' but provided '@Nullable String' via method descriptor Function<String,Integer>.apply(String)
}
}
Is that a bug, as it doesn't appear as though the lambda expression type inferencing from the method reference accounts for null annotations?
Thanks!
|
|
|
Re: @NonNullByDefault Questions [message #1544775 is a reply to message #1544707] |
Sun, 04 January 2015 00:40 |
Stephan Herrmann Messages: 1853 Registered: July 2009 |
Senior Member |
|
|
Chris Hubick wrote on Sun, 04 January 2015 00:49Is that a bug, as it doesn't appear as though the lambda expression type inferencing from the method reference accounts for null annotations?
That error looks weird, let me see, where could the @Nullable come from?
Ah: look:
@NonNull Optional<@Nullable String> optNullableString
From passing this variables as first arg into map(), inference undeniably has to instantiate map's <T> as '@Nullable String'.
That probably makes the second parameter of map:
Function<@NonNull ? super @Nullable String, something>
outch, do we really accept that? This type looks like nonsense: Supertype of a @Nullable type and being @NonNull itself is an unsatisfiable type.
Apparently, the @Nullable part wins and makes the SAM for the method reference have a @Nullable method parameter. Since testMethod requires a @NonNull argument it is not a valid implementation of the SAM. In this vein the error makes sense.
If you change the type of 'optNullableString' to
@NonNull Optional<@NonNull String> optNullableString
the program compiles fine (in HEAD).
Still accepting the strange wildcard looks wrong, perhaps(?) related to https://bugs.eclipse.org/448709
thanks,
Stephan
[Updated on: Sun, 04 January 2015 00:45] Report message to a moderator
|
|
|
Re: @NonNullByDefault Questions [message #1544902 is a reply to message #1544775] |
Sun, 04 January 2015 02:25 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
Stephan Herrmann wrote on Sat, 03 January 2015 19:40That probably makes the second parameter of map:
Function<@NonNull ? super @Nullable String, something>
outch, do we really accept that? This type looks like nonsense: Supertype of a @Nullable type and being @NonNull itself is an unsatisfiable type.
Yeah, I think what I should have written was simply this:
public static final <T,U> @NonNull Optional<U> map(final @NonNull Optional<T> optional, final Function<? super @NonNull T,? extends U> mapper)
What I was aiming for was to have the free-type Optional<T>, but want the 'mapper' function to only have to support accepting a @NonNull parameter, since Optional.map() doesn't actually invoke the mapper function unless it's a non-null value (same for filter() with it's Predicate, etc).
[Updated on: Sun, 04 January 2015 03:09] Report message to a moderator
|
|
|
Re: @NonNullByDefault Questions [message #1545005 is a reply to message #1540642] |
Sun, 04 January 2015 03:54 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
Stephan Herrmann wrote on Thu, 01 January 2015 11:36To make the combination of Optional and null annotations useful, we'll need null annotations in the definition of Optional, s.t. like:
class Optional<T extends @NonNull Object> {
public @Nullable T orElse(@Nullable T other) {
return (this.value != null) ? this.value : other;
}
}
I don't think Optional.orElse() should/could be annotated to always accept/return @Nullable. As convenient as orElse(null) is for dealing with 'legacy' code, you'd create way more problems than you'd solve, since I expect the vast majority of clients in new codebases will likely demand the method return the same nullness of <T> as the host Optional itself is.
Stephan Herrmann wrote on Thu, 01 January 2015 11:36One could also try to leave the declaration of <T> unconstrained and only add @NonNull to all regular usage of T (except for orElse etc.).
I think that's the more flexible approach if you don't want to leave use-cases like orElse(null) dead in the water. Here's a first draft:
import java.util.*;
import java.util.function.*;
import org.eclipse.jdt.annotation.*;
public final class Optional<T> {
private static final @NonNull Optional<?> EMPTY = new Optional<>();
private final @Nullable T value;
private Optional() {
this.value = null;
}
public static <T> @NonNull Optional<T> empty() {
@SuppressWarnings("unchecked")
@NonNull Optional<T> t = (Optional<T>)EMPTY;
return t;
}
private Optional(@NonNull T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> @NonNull Optional<@NonNull T> of(@NonNull T value) {
return new Optional<@NonNull T>(value);
}
public static <T> @NonNull Optional<T> ofNullable(@Nullable T value) {
return value == null ? empty() : of(value);
}
public @NonNull T get() {
final @Nullable T value = this.value;
if (value == null) throw new NoSuchElementException("No value present");
return value;
}
public boolean isPresent() {
return value != null;
}
public void ifPresent(@NonNull Consumer<? super @NonNull T> consumer) {
final @Nullable T value = this.value;
if (value != null) consumer.accept(value);
}
public @NonNull Optional<T> filter(@NonNull Predicate<? super @NonNull T> predicate) {
Objects.requireNonNull(predicate);
final @Nullable T value = this.value;
return (value != null) && predicate.test(value) ? this : empty();
}
public <U> @NonNull Optional<U> map(@NonNull Function<? super @NonNull T,? extends U> mapper) {
Objects.requireNonNull(mapper);
final @Nullable T value = this.value;
return (value != null) ? Optional.<U> ofNullable(mapper.apply(value)) : empty();
}
public <U> @NonNull Optional<U> flatMap(@NonNull Function<? super @NonNull T,@NonNull Optional<U>> mapper) {
Objects.requireNonNull(mapper);
final @Nullable T value = this.value;
return (value != null) ? Objects.requireNonNull(mapper.apply(value)) : empty();
}
public T orElse(T other) {
final @Nullable T value = this.value;
return value != null ? value : other;
}
public T orElseGet(@NonNull Supplier<? extends T> other) {
final @Nullable T value = this.value;
return value != null ? value : other.get();
}
public <X extends Throwable> @NonNull T orElseThrow(@NonNull Supplier<? extends @NonNull X> exceptionSupplier) throws X {
final @Nullable T value = this.value;
if (value != null) return value;
throw exceptionSupplier.get();
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Optional)) return false;
return Objects.equals(value, ((Optional<?>)obj).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@Override
public @NonNull String toString() {
final @Nullable T value = this.value;
return value != null ? String.format("Optional[%s]", value) : "Optional.empty";
}
}
The ofNullable allows clients to choose if they want a @NonNull or @Nullable result, and that let's you use map() to do "conversions" from Optional<@NonNull> to Optional<@Nullable> and then orElse(null) or orElse(anyNullable), etc on the result.
Stephan Herrmann wrote on Thu, 01 January 2015 11:36Anyway, this only takes us back to https://bugs.eclipse.org/331651 so let's push that one with high priority now!
Yeah, I'm just trying to understand this stuff well enough to write code that will correctly anticipate that being fixed
If you get the infrastructure completed and it looks like the textual encoding won't quickly follow, I'd settle for hardcoding annotations for java.util.Optional and java.util.Objects, as even just those two alone would be immensely useful
|
|
|
Re: @NonNullByDefault Questions [message #1546136 is a reply to message #1545005] |
Sun, 04 January 2015 19:13 |
Chris Hubick Messages: 12 Registered: July 2009 |
Junior Member |
|
|
Chris Hubick wrote on Sat, 03 January 2015 22:54Stephan Herrmann wrote on Thu, 01 January 2015 11:36One could also try to leave the declaration of <T> unconstrained and only add @NonNull to all regular usage of T (except for orElse etc.).
I think that's the more flexible approach if you don't want to leave use-cases like orElse(null) dead in the water.
Yeah, playing around with this a bit more, I'm starting to think the whole concept of "conversions" and Optional<@Nullable> just can't be made to work elegantly, and you just shouldn't do things like orElse(null).
The Optional API has of(), ofNullable(), and orElse() - and what it really needed was an orElseNullable() to go with those.
Anyhow, here's a take on the whole thing restricted to @NonNull...
import java.util.*;
import java.util.function.*;
import org.eclipse.jdt.annotation.*;
public final class Optional<T extends @NonNull Object> {
private static final @NonNull Optional<@NonNull ?> EMPTY = new Optional<>();
private final @Nullable T value;
private Optional() {
this.value = null;
}
public static <@NonNull T> @NonNull Optional<T> empty() {
@SuppressWarnings("unchecked")
@NonNull Optional<T> t = (Optional<T>)EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <@NonNull T> @NonNull Optional<T> of(T value) {
return new Optional<T>(value);
}
public static <@NonNull T> @NonNull Optional<T> ofNullable(@Nullable T value) {
return value == null ? empty() : of(value);
}
public T get() {
final @Nullable T value = this.value;
if (value == null) throw new NoSuchElementException("No value present");
return value;
}
public boolean isPresent() {
return value != null;
}
public void ifPresent(@NonNull Consumer<? super T> consumer) {
final @Nullable T value = this.value;
if (value != null) consumer.accept(value);
}
public @NonNull Optional<T> filter(@NonNull Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
final @Nullable T value = this.value;
return (value != null) && predicate.test(value) ? this : empty();
}
public <@NonNull U> @NonNull Optional<U> map(@NonNull Function<? super T,? extends U> mapper) {
Objects.requireNonNull(mapper);
final @Nullable T value = this.value;
return (value != null) ? Optional.<U> ofNullable(mapper.apply(value)) : empty();
}
public <@NonNull U> @NonNull Optional<U> flatMap(@NonNull Function<? super T,@NonNull Optional<U>> mapper) {
Objects.requireNonNull(mapper);
final @Nullable T value = this.value;
return (value != null) ? Objects.requireNonNull(mapper.apply(value)) : empty();
}
public @Nullable T orElse(@Nullable T other) {
final @Nullable T value = this.value;
return value != null ? value : other;
}
public @Nullable T orElseGet(@NonNull Supplier<? extends @Nullable T> other) {
final @Nullable T value = this.value;
return value != null ? value : other.get();
}
public <X extends @NonNull Throwable> T orElseThrow(@NonNull Supplier<? extends X> exceptionSupplier) throws X {
final @Nullable T value = this.value;
if (value != null) return value;
throw exceptionSupplier.get();
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Optional)) return false;
return Objects.equals(value, ((Optional<@NonNull ?>)obj).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@Override
public @NonNull String toString() {
final @Nullable T value = this.value;
return value != null ? String.format("Optional[%s]", value) : "Optional.empty";
}
}
[Updated on: Sun, 04 January 2015 22:31] Report message to a moderator
|
|
| |
Goto Forum:
Current Time: Fri Apr 26 22:18:35 GMT 2024
Powered by FUDForum. Page generated in 0.05717 seconds
|