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
}
The workaround I am currently using:
public boolean hasMatchingIdentifier(@Nullable Object id) {
return id instanceof int[] &&
Arrays.equals(this.tag.getUniqueIdentifier(), (int[])id);
}
I would prefer to avoid these approaches:
- Adding
@SuppressWarnings("null")toTagKeyor itsequalsmethod (The production version is somewhat more complex and has a high risk of embarrassing NPEs.)
Notes:
- My JDT version is 3.10.0.v20140606-1215
- Earlier I made the mistake of declaring
@Nullable int[] id. Surprisingly there was no warning message for this, even though it implies that the primitiveintelements may benullwhich is clearly wrong.