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.