I cooked up a toy Java 5 class that generates warnings of the "unchecked" family when compiled:
package pholser.util; import java.util.Arrays; public class Stack<T> { private final Object[] storage; private int top; public Stack() { this.storage = new Object[ 5 ]; } public Stack( final Stack<T> other ) { this.storage = (Object[]) other.storage.clone(); this.top = other.top; final T currentTop = (T) storage[ top ]; } public void push( final T item ) { checkPrecondition( isFull(), "can't push a full stack" ); storage[ top++ ] = item; } public T pop() { checkPrecondition( isEmpty(), "can't pop an empty stack" ); return (T) storage[ --top ]; } public T peek() { checkPrecondition( isEmpty(), "can't peek an empty stack" ); return (T) storage[ --top ]; } @Override public boolean equals( final Object that ) { if ( this == that ) return true; if ( that == null || !getClass().equals( that.getClass() ) ) return false; final Stack<?> other = (Stack<?>) that; return top == other.top && Arrays.deepEquals( storage, other.storage ); } @Override public int hashCode() { int hashValue = 17; hashValue = 37 * hashValue + top; hashValue = 37 * hashValue + Arrays.deepHashCode( storage ); return hashValue; } private void checkPrecondition( final boolean precondition, final String message ) { if ( !precondition ) throw new IllegalStateException( message ); } private boolean isEmpty() { return top == 0; } private boolean isFull() { return top == storage.length; } }
This yields the following warnings when compiled (Sun javac
1.5.0_06):
[javac] C:\java\scratchpad\src\pholser\util\Stack.java:16: warning: [unchecked] unchecked cast [javac] found : java.lang.Object [javac] required: T [javac] final T currentTop = (T) storage[ top ]; [javac] ^ [javac] C:\java\scratchpad\src\pholser\util\Stack.java:28: warning: [unchecked] unchecked cast [javac] found : java.lang.Object [javac] required: T [javac] return (T) storage[ --top ]; [javac] ^ [javac] C:\java\scratchpad\src\pholser\util\Stack.java:34: warning: [unchecked] unchecked cast [javac] found : java.lang.Object [javac] required: T [javac] return (T) storage[ --top ];
Note that each of the casts we attempt should be legitimate; if Java's type system is sound, there should be nothing but elements of at least type T
in the Stack
's storage.
I then attempted to suppress the warnings by applying the @SuppressWarnings( "unchecked" )
annotation to various elements of the class. Firstly, applying it to the class itself should do the trick:
@SuppressWarnings( "unchecked" ) public class Stack<T> {
Sure enough, no warnings. However, to quote the javadoc for @SuppressWarnings
:
As a matter of style, programmers should always use this annotation on the most deeply nested element where it is effective. If you want to suppress a warning in a particular method, you should annotate that method rather than its class.
Fair enough. Tacking the annotation on the declarations of pop()
and peek()
:
@SuppressWarnings( "unchecked" ) public T pop() { @SuppressWarnings( "unchecked" ) public T peek() {
suppresses the warnings on those methods. We should do the same for the "copy" constructor:
@SuppressWarnings( "unchecked" ) public Stack( final Stack<T> other ) {
Golden. It's nice to be able to apply @SuppressWarnings
to so many different kinds of program elements. We've seen it applied so far at the class, method, and constructor level.
By un-inlining ("out-lining?") the return values of pop()
and peek()
, and the local currentTop
in the "copy" constructor, we can show off application of @SuppressWarnings
on locals:
@SuppressWarnings( "unchecked" ) final T currentTop = (T) storage[ top ]; @SuppressWarnings( "unchecked" ) final T returnValue = (T) storage[ --top ]; return returnValue;
Would an astute reader be so kind as to augment this example to demonstrate how @SuppressWarnings
can effectively stifle "unchecked"-flavored warnings on fields or parameters? Marking the that
parameter of Stack.equals()
and then attempting a dubious cast of that
to Stack<T>
didn't stifle the warning. Also, marking field storage
didn't prevent warnings, perhaps because the program element in question in pop()
, peek()
, and the copy ctor is an element of an array field, not the array field itself.
In Jaggregate there were a few spots in the code that were generating "unchecked"-flavored warnings--about 20 or so--even though it was demonstrable that the code was correct and would not lead to bad casts. Once @SuppressWarnings
became supported in Java 5 update 6, I undertook an effort to eliminate and/or suppress the remaining warnings. I found that pretty much all of these warnings were in places where I was attempting a cast to a generic type. Marking methods and local variables with @SuppressWarnings( "unchecked" )
did the trick but seemed to clutter up the code quite a bit. So, I refactored the casts to a method:
class Objects { @SuppressWarnings( "unchecked" ) static <T> T cast( final Object target ) { return (T) target; } }
Now that's minimizing the scope of the suppression. 8^)
So now, instead of:
@SuppressWarnings( "unchecked" ) private void repopulateBucket( final Object bucket ) { for ( Node<K, V> node = (Node<K, V>) bucket; node != null; node = node.nextNode ) { put( node.key(), node.value() ); } }
or
private void repopulateBucket( final Object bucket ) { for ( @SuppressWarnings( "unchecked" ) Node<K, V> node = (Node<K, V>) bucket; node != null; node = node.nextNode ) { put( node.key(), node.value() ); } }
we have:
private void repopulateBucket( final Object bucket ) { for ( Node<K, V> node = Objects.cast( bucket ); node != null; node = node.nextNode ) { put( node.key(), node.value() ); } }
Note how the type inference on generic methods really helps--we don't have to tell the compiler what we want to cast to, it's kind of a no-brainer in this case. In other cases, especially when trying to cast to a type specified by a type parameter, you have to give the compiler a hint:
public E at( final int index ) { checkIndexIsValid( index, "search" ); return Objects.<E>cast( storage[ index ] ); }
It's not crystal clear to me what are the exact circumstances that require the extra hint, but in any event I was able to suppress the warnings in the Jaggregate code with a minimum of clutter.