views:

608

answers:

4

Named local classes are very rarely used, usually local classes are anonymous. Does anybody know why the code below generates a compiler warning?

public class Stuff<E> {
  Iterator<E> foo() {
    class InIterator implements Iterator<E> {
      @Override public boolean hasNext() { return false; }
      @Override public E next() { return null; }
      @Override public void remove() { }
    }
    return new InIterator();
  }
}

The warning is in new InIterator() and it says

[unchecked] unchecked conversion
found   : InIterator
required: java.util.Iterator<E>

If the class, unchanged, is made anonymous, or if it is made a member, the warning goes away. However, as a named local class, it requires a declaration class InIterator<E> implements ... for the warning to go away.

What's going on?

+3  A: 

I believe what's happening is you are ignoring the generic type argument by naming InIterator without a reference to the generic in the signature (even though it is present in the interface).

This falls into the category of silly compiler warnings: you've written the class so that 100% InIterator instances will implement Iterator<E>, but the compiler doesn't recognize it. (I suppose this depends on the compiler. I don't see the warning in my Eclipse compiler, but I know that the Eclipse compiler handles generics slightly differently than the JDK compiler.)

I argue that this is less clear, and less close to what you mean, but perhaps more compiler friendly, and ultimately equivalent:

public class Stuff<E> {
  Iterator<E> foo() {
    class InIterator<F> implements Iterator<F> {
      @Override public boolean hasNext() { return false; }
      @Override public E next() { return null; }
      @Override public void remove() { }
    }
    return new InIterator<E>();
  }
}
David Berger
Thanks David. IntelliJ IDEA also didn't show any warnings, just Sun javac. I know how to fix this, I am simply wondering if there is some strange but legitimate reason for this compiler behavior, or it's a bug.
Yardena
Ahh, compiler differences between IDE and javac... how annoying :(
Jorn
It's not entirely clear to me whether it's a bug that InIterator is not a Iterator<E> or that InIterator implements Iterator<E>.
Tom Hawtin - tackline
It is legitimate if you have low expectations of the compiler, which is what I've come to have regarding generics. If you expect the compiler to only do a quick logical pass-through when it asks "Is this guaranteed to be the right generic type?" then this is what you expect. If you expect it to look back a little farther, then no, not legitimate.
David Berger
Hi Jorn - I seriously suspect a compiler bug. JLS doesn't seem to differentiate named and anonymous local classes with regards to generics, so the code should compile. And why adding an unused parameter makes the warning go away?! like:public class Stuff<E> { E bar; Iterator<E> foo() { class InIterator<Z> implements Iterator<E> { ... @Override public E next() { return bar; } ... } return new InIterator<E>(); }}
Yardena
+1  A: 

Hmm, no warnings here.

import java.util.Iterator;

public class Stuff<E> {
    Iterator<E> foo() {
        class InIterator implements Iterator<E> {
            public boolean hasNext() {
                return false;
            }

            public E next() {
                return null;
            }

            public void remove() {
            }
        }
        return new InIterator();
    }

    public static void main(String[] args) {
        Iterator<String> i = new Stuff<String>().foo();
    }
}
Mark Renouf
Hm, what compiler are you using?
Yardena
Tested inside of Eclipse (3.4), as well as with Sun JDK 1.5.0.18 and 1.6.0.13
Mark Renouf
there is a warning for Sun 1.6.0_11 and 1.6.0_12, I will check 13, thank you!
Yardena
A: 

I am now convinced that it's a javac bug. The solutions above that add a generic parameter to InIterator that either hides or replaces E aren't helpful, because they preclude the iterator from doing something useful, like returning an element of type E - Stuff's E.

However this compiles with no warnings (thanks Jorn for the hint):

public class Stuff<E> {
  E bar;
  Iterator<E> foo() {
    class InIterator<Z> implements Iterator<E> {
      @Override public boolean hasNext() { return false; }
      @Override public E next() { return bar;  }
      @Override public void remove() { }
    }
    return new InIterator<Void>();
  }
}

Definitely a bug.

Yardena
Can you file a bug and post the link? You've gotten me all curious.
Michael Myers
well, according to tweakt below there's no warning in update 13. I will check, but assuming he's right, it means it WAS a bug :-)
Yardena
Ah, ok. Have you checked to see if it's in the bug list somewhere?
Michael Myers
Didn't find it in http://bugs.sun.com nor in http://java.sun.com/javase/6/webnotes/6u13.html
Yardena
Dunno about tweakt, for me 1.6.0_13 shows the same warning. Submitting a bug.
Yardena
+1  A: 

Yeah, I also agree that this should be a bug. If you "lift" the local class out of the method into a member class, it works fine too. And there isn't much difference between the two except different scoping and access to local variables.

public class Stuff<E> {
  class InIterator implements Iterator<E> {
    @Override public boolean hasNext() { return false; }
    @Override public E next() { return null; }
    @Override public void remove() { }
  }
  Iterator<E> foo() {
    return new InIterator();
  }
}
newacct