Why Won't This Generic Java Code Compile

Why won't this generic java code compile?

Got it. This actually isn't a bug, strange as it might seem.

From section 4.8 (raw types) of the JLS:

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.

So even though the method's type signature doesn't use any type parameters of the class itself, type erasure kicks in and the signature becomes effectively

public Map getMap()

In other words, I think you can imagine a raw type as being the same API as the generic type but with all <X> bits removed from everywhere (in the API, not the implementation).

EDIT: This code:

MyClass unchecked = new MyClass();
Map<String, String> map = unchecked.getMap(); // Unchecked warning, why?
String s2 = map.get("");

compiles because there's an implicit but unchecked conversion from the raw Map type to Map<String, String>. You can get the same effect by making an explicit conversion (which does nothing at execution time) in the last case:

// Compiles, but with an unchecked warning
String x = ((Map<String, String>)fail.getMap()).get("");

Why does this generic code compile in java 8?

If you declare a type parameter at a method, you are allowing the caller to pick an actual type for it, as long as that actual type will fulfill the constraints. That type doesn’t have to be an actual concrete type, it might be an abstract type, a type variable or an intersection type, in other, more colloquial words, a hypothetical type. So, as said by Mureinik, there could be a type extending String and implementing List. We can’t manually specify an intersection type for the invocation, but we can use a type variable to demonstrate the logic:

public class Main {
public static <X extends String&List<Integer>> void main(String[] args) {
String s = Main.<X>newList();
System.out.println(s);
}

private static <T extends List<Integer>> T newList() {
return (T) new ArrayList<Integer>();
}
}

Of course, newList() can’t fulfill the expectation of returning such a type, but that’s the problem of the definition (or implementation) of this method. You should get an “unchecked” warning when casting ArrayList to T. The only possible correct implementation would be returning null here, which renders the method quite useless.

The point, to repeat the initial statement, is that the caller of a generic method chooses the actual types for the type parameters. In contrast, when you declare a generic class like with

public class SomeClass<T extends List<Integer>> {
public void main(String[] args) {
String s = newList(); // this doesn't compile anymore
System.out.println(s);
}

private T newList() {
return (T) new ArrayList<Integer>();
}
}

the type parameter is part of the contract of the class, so whoever creates an instance will pick the actual types for that instance. The instance method main is part of that class and has to obey that contract. You can’t pick the T you want; the actual type for T has been set and in Java, you usually can’t even find out what T is.

The key point of generic programming is to write code that works independently of what actual types have been chosen for the type parameters.

But note that you can create another, independent instance with whatever type you like and invoke the method, e.g.

public class SomeClass<T extends List<Integer>> {
public <X extends String&List<Integer>> void main(String[] args) {
String s = new SomeClass<X>().newList();
System.out.println(s);
}

private T newList() {
return (T) new ArrayList<Integer>();
}
}

Here, the creator of the new instance picks the actual types for that instance. As said, that actual type doesn’t need to be a concrete type.

Java Generics won't compile

The problem you're experiencing is because there is no + operator defined for Number, only specific subclasses of Number. For example, + is defined for Integer, Double etc, but not BigInteger, BigDecimal or any other non-standard implementation of Number.

There is no good way to do generic addition. You end up having to provide a BinaryOperator<S>, so your code looks like:

sum(1, 2, Integer::sum);
sum(1.0, 2.0, Double::sum);

which is more verbose than just:

1 + 2
1.0 + 2.0

The compiler requires + to be defined for the compile-time types of v1 and v2. It doesn't matter if they are Integer (or whatever) at runtime: the decision as to whether to allow the + is made by the compiler, because it has to be able to guarantee that the method is type-safe for any arguments.

The method above is compiled to this:

 public static final Number sum(Number v1, Number v2){
System.out.printf("v1=%1$s,v2=%2$s%n",v1.getClass(),v2.getClass());
return v1+v2;
}

This is called type erasure.

If + isn't defined for a Number, this code isn't allowed.

Why does this generic code compile?

The code won't compile with the next JDK version 1.6.0_22-b04. Error is:

Type mismatch: cannot convert from Number to String

I guess your suggestion of a compiler bug may be true.

Why the code with such generic bound is compiled

There could be a T that both extends List<Integer> and extends MyClass (for example a class MyListClass extends MyClass implements List<Integer>.

The compiler doesn't look into the createList method to figure out what it returns, it just trusts that it does what the prototype describes.

Since you use an unchecked cast inside the method body, you are allowed to break the type system this way. And that's why you get a warning about that unchecked cast.

Effectively the unchecked cast means you disable the type check at that line and therefore can implement a return type that you don't actually fulfill (you don't actually return a MyListClass as described above, you just claim that you do).

The reason this doesn't work for non-interface types is that the type system doesn't allow a type that is two unrelated non-interface types at the same time (a thing can't be a String and a MyClass at the same type).

Compiler error on Java generic interface with a List method

What is happening is when use use a class (or interface) with a generic parameter <T> but refer to and instance of the without <T> (ie. that raw type) the compiler erases all generic type information from the class. This is likely due to compatibility with pre-1.5 source code where you wouldn't be able to use generic type information at all.

Consider the situation where you are writing code and compiling on a Java 1.4 compiler. You want to use a library which makes use of generics. When you refer to a type from that library which has generic parameters as a raw type, the compiler enforces the use of no generic parameters.

EDIT:

The JLS-4.8-210 alludes to this when it mentions (credit: zhong-j-yu):

The type of a constructor (§8.8), instance method (§8.4, §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 raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

This still feels like a gotcha, but it is likely for some reason.

Why won't my Java generic array type transfer method call compile?

It appears that what you need is

public static <From, To> ArrayPP<To> transfer(
// vvvvvvvvv
Iterable<? extends From> origin,
FactoryDelegate<From, To> factory
)

Because you only care that origin produces From when you iterate over it, not that it is necessarily an Iterable<From>.

See also

  • Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?
  • What is PECS (Producer Extends Consumer Super)?

Java compiler error with generics

Generics inheritance is different from our regular understanding of OO inheritance. Please read this tutorial.

To make your code compile, you may need to change your method syntax like below:

void set(Map<String, ?> foo) { }

EDIT: As dasblinkenlight commented, if you have any plans to do modifications to the Map inside the set method, it won't work unless you have concrete type defined.



Related Topics



Leave a reply



Submit