JDK-6817013 : Iterative for loop fails to find generics information of cast classes
  • Type: Enhancement
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 6u10
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: linux
  • CPU: x86
  • Submitted: 2009-03-13
  • Updated: 2011-02-16
  • Resolved: 2009-03-16
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_12"
Java(TM) SE Runtime Environment (build 1.6.0_12-b04)
Java HotSpot(TM) Client VM (build 11.2-b01, mixed mode, sharing)


ADDITIONAL OS VERSION INFORMATION :
Linux attemptkind-lx 2.6.27-9-generic #1 SMP Thu Nov 20 21:57:00 UTC 2008 i686 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
If you declare a class which includes a generic type, then try to reference a method of that class which uses an unrelated generic type through an iterative for loop, the compiler declares "incompatible types."

I know that sounds confusing, sorry.  Look at the code under the "Source code" section -- it's much clearer in code than in words.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
$ javac isitabug/genericscastfor/Whoa.java
isitabug/genericscastfor/Whoa.java:27: incompatible types
found   : java.lang.Object
required: java.lang.String
    for (String one: other.getThingList())
                                       ^
Note: isitabug/genericscastfor/Whoa.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package isitabug.genericscastfor;

import java.util.LinkedList;
import java.util.List;

public class Whoa<T>
{
  public List<String> getThingList ()
  {
    return _stringList;
  }
  
  public void walkStringListOk (Whoa<T> other)
  {
    // this works fine because the generic T is specified
    for (String one: other.getThingList())
    {
      // ...
    }
  }
  
  public void walkStringList (Whoa other)
  {
    // getting generically outside an iterative for loop works fine
    List<String> myList = other.getThingList();
    // getting with an iterative for loop won't compile
    for (String one: other.getThingList())
    {
      // ...
    }
  }
  
  private List<String> _stringList = new LinkedList<String>();
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
There are several workarounds, and they're all pretty obvious:

1) Cast the item you're looping over.
2) Declare the generic type when referencing the base object (Whoa)
3) Don't use the iterative for loop.
... etc.

Comments
EVALUATION Spoke too fast - again the JLS, section 4.8 mandate that an unchecked method call warning should be issued only if the erasure of the invoked method changes some of the method's formal arguments: "An invocation of a method or constructor of a raw type generates an unchecked warning if erasure changes any of the types of any of the arguments to the method or constructor." In this case, since the invoked method does not declare any arguments, erasing the method's signature has no effect on the method's formals - as such no warning should be issued. I will reopen this issue once a decision on 6785612 is made.
16-03-2009

PUBLIC COMMENTS Q: I don't follow: Why does the lack of the qualifying type in 'Whoa' erase the qualifying type in 'walkStringList'? If you remove typing from 'Whoa' (making it 'public class Whoa' instead of 'public class Whoa<T>'), 'walkStringList' returns the correct type of List<String>. I don't see how the type of the declaring class should impact the type of the method? A: This comes from JLS3, section 4.8 (raw types): "The type of a constructor (��8.8), instance method (��8.8, ��9.4), or non-static field (��8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C." In this case we have that the raw type is 'Whoa' and the member is the method getThingList(). As per JLS3, the type of the raw type member gets erased from ()List<String> to ()List - as a result, assigning from List to List<String> will trigger an unchecked conversion (and the corresponding warning); on the other hand, since List.iterator() will yield Iterator<Object> - the type of the iterated elements (Object) does not conform to the declared type of the for loop's variable. Why are members of a raw type erased? Since a raw type does not specify an instantiation for the generic class' type parameters, it would be possible for programmers to exploit raw types in unsound ways - esp. if the generic class declared a method whose signature contains one (or more) type variables declared by the generic class. In this case the erased method just returns List<String> which obviously does not contain any type-variable; I agree that, in principle, erasure of getThingList() could be avoided as it is safe (the signature of the method is not affected by the fact that Whoa is raw). However this is a language change - and should be regarded as such (it is *not* a compiler bug).
16-03-2009

EVALUATION This is not a bug. Since 'other' in method 'walkStringList' has the raw type 'Whoa' - as a consequence other.getStringList will just return List (and not List<String> as expected). When invoking methods on a raw receiver, the method signature gets erased. This, in turn, makes the iterable for loop invalid, as List.next() yields Object while the expected type is a String (and obviously Object is not a subtype of String). In essence this is a duplicate of 6785612 (which has been deferred). On the other hand I'm leaving this as open as a diagnostic RFE, as the compiler could do better than this in reporting what's going wrong. The point is that the compiler emits an unchecked method call warning when the signature of the method changes because the receiver is a raw type; only - that warning is generated if and only if the arguments change - not the return type. Because of this - javac is not generating a warning for the unchecked method call to other.walkStringList - which would at least explain why javac founds an Object instead of a String. The raw type diagnostic gives some clue about the error - but I recognize this is not enough.
13-03-2009