I am getting the following (confusing) warning when using annotation-based null analysis where an array is involved:
Null type safety (type annotations): The expression of type 'int[]' needs unchecked conversion to conform to 'int @Nullable[]'
This occurs when I pass an unannotated int[]
to an int @Nullable[]
parameter.
This is surprising. Normally it is only a problem if you take a nullable value and try to pass it to a nonnull parameter, but I am doing the opposite - taking a known non-null (though not annotated) array and passing it to a method (Arrays.equals
) which does accept nulls.
Also it does not seem to be a problem with non-array objects. Normally a variable or return of (unannotated, non-array) type T
can be assigned to a @Nullable T
.
So my question is why does this change when T
is an array type?
My class is a hashable/equality-comparable proxy for a native class that uses an int[]
(taken from a C++ function) as a unique identifier. My package uses annotation-based null analysis and the 3rd-party package (with the native class) does not.
Here's a cut-down version that shows the problem:
TagKey.java:
package example;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import other.Tag;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
public class TagKey {
private TagKey(Tag tag) {
this.tag = tag;
}
public static TagKey forTag(Tag tag) {
TagKey candidateKey = new TagKey(tag);
return INTERNER.intern(candidateKey);
}
@Override
public int hashCode() {
return Arrays.hashCode(this.tag.getUniqueIdentifier());
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
} else if (obj == null) {
return false;
} else if (getClass() != obj.getClass()) {
return false;
} else {
return ((TagKey) obj).hasMatchingIdentifier(this.tag.getUniqueIdentifier()); // Warning appears here
}
}
public boolean hasMatchingIdentifier(int @Nullable[] id) {
return Arrays.equals(this.tag.getUniqueIdentifier(), id);
}
@SuppressWarnings("null") // Guava cannot use type parameter annotations due to backward compatibility with Java 6
private static final Interner<TagKey> INTERNER = Interners.newWeakInterner();
private final Tag tag;
}
package-info.java:
/**
* @author finnw
*
*/
@org.eclipse.jdt.annotation.NonNullByDefault
package example;
Tag.java: (partial)
package other;
public class Tag {
public native int[] getUniqueIdentifier(); // Currently returns a fresh array on each call, but may change in the near future to reuse a single array
}
public boolean hasMatchingIdentifier(@Nullable Object id) {
return id instanceof int[] &&
Arrays.equals(this.tag.getUniqueIdentifier(), (int[])id);
}
I would prefer to avoid these approaches:
@SuppressWarnings("null")
to TagKey
or its equals
method (The production version is somewhat more complex and has a high risk of embarrassing NPEs.) Notes:
@Nullable int[] id
. Surprisingly there was no warning message for this, even though it implies that the primitive int
elements may be null
which is clearly wrong.Add this method:
@SuppressWarnings("null")
public static int @Nullable[] getUniqueIdentifier(Tag tag) {
return tag.getUniqueIdentifier();
}
then:
return ((TagKey) obj).hasMatchingIdentifier(getUniqueIdentifier(this.tag));
This is why I'm ignoring the "unchecked conversion from non-annotated" warning until nullity profiles are supported, you get stuck either suppressing null warnings everywhere (which defeats the point) or making annotated wrapper methods for every library.
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