Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different return value types in implementation of generic methods

Today I stumbled upon some working Java code I wouldn't even have expected to compile. Reduced to its bare minimum, it looks like this:

import java.util.List;  interface A {     <T> List<String> foo(); }  interface B {     <T> List<Integer> foo(); }  class C implements A, B {     @Override     public List<?> foo()     {         return null;     } } 

At first sight, the type parameter <T> of the foo methods in A and B look unnecessary since T is not used anywhere else. Anyway, I found out that this is playing a crucial role in allowing the conflicting return value types to coexist in the same implementation: if one or both of the <T>s are left out, the code doesn't compile. Here the non-working version:

import java.util.List;  interface A {     List<String> foo(); }  interface B {     List<Integer> foo(); }  class C implements A, B {     @Override     public List<?> foo()     {         return null;     } } 

I don't need to fix the code snippets above as those are just examples I made up to explain my point. I'm only curious to understand why the compiler is behaving differently with them. Can someone explain what rules exactly are making the difference here?

like image 699
GOTO 0 Avatar asked Dec 05 '13 21:12

GOTO 0


People also ask

What is return type of methods in Java?

A return statement causes the program control to transfer back to the caller of a method. Every method in Java is declared with a return type and it is mandatory for all java methods. A return type may be a primitive type like int, float, double, a reference type or void type(returns nothing).

How do I return a generic class type in Java?

(Yes, this is legal code; see Java Generics: Generic type defined as return type only.) The return type will be inferred from the caller. However, note the @SuppressWarnings annotation: that tells you that this code isn't typesafe. You have to verify it yourself, or you could get ClassCastExceptions at runtime.

Which of these type parameters is used for a generic class to return?

Which of these type parameters is used for a generic class to return and accept a number? Explanation: N is used for Number. Sanfoundry Certification Contest of the Month is Live.


1 Answers

While the first example does compile, it will give an unchecked conversion warning:

// Type safety: The return type List<?> for foo() from the type C needs // unchecked conversion to conform to List<String> public List<?> foo() {     return null; } 

What's happening here is that by declaring type parameters, A.foo() and B.foo() are generic methods. Then, the overriding C.foo() omits that type parameter. This is similar to using a raw type, essentially "opting out" of generic type checking for that method signature. That causes the compiler to use the inherited methods' erasures instead: List<String> foo() and List<Integer> foo() both become List foo(), which can therefore be implemented by C.foo().

You can see that by keeping the type parameter in the C.foo() declaration, there will be the expected compiler error instead:

// The return type is incompatible with A.foo() public <T> List<?> foo() {     return null; } 

Likewise, if either of the interface methods don't declare a type parameter, then omitting a type parameter from the override fails to "opt out" of generic type checking for that method, and the return type List<?> remains incompatible.

This behavior is covered in JLS §8.4.2:

The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.

Angelika Langer's generics FAQ expands on this behavior in her section Can a non-generic method override a generic one?:

Now, let us explore an example where non-generic subtype methods override generic supertype methods. Non-generic subtype methods are considered overriding versions of the generic supertype methods if the signatures' erasures are identical.

Example (of non-generic subtype methods overriding generic supertype methods):

class Super {    public <T> void set( T arg) { ... }    public <T> T get() { ... }  }  class Sub extends Super {    public void set( Object arg) { ... } // overrides    public Object get() { ... }    // overrides with unchecked warning  }  

warning: get() in Sub overrides <T>get() in Super;   return type requires unchecked conversion  found   : Object  required: T          public Object get() {  

Here the subtype methods have signatures, namely set(Object) and get() , that are identical to the erasures of the supertype methods. These type-erased signatures are considered override-equivalent.

There is one blemish in the case of the get method: we receive an unchecked warning because the return types are not really compatible. The return type of the subtype method get is Object , the return type of the supertype method get is an unbounded type parameter. The subtype method's return type is neither identical to the supertype method's return type nor is it a subtype thereof; in both situations the compiler would happily accept the return types as compatible. Instead, the subtype method's return type Object is convertible to the supertype method's return type by means of an unchecked conversion. An unchecked warning indicates that a type check is necessary that neither the compiler nor the virtual machine can perform. In other words, the unchecked operation is not type-safe. In case of the convertible return types someone would have to make sure that the subtype method's return value is type-compatible to the supertype method's return type, but nobody except the programmer can ensure this.

like image 143
Paul Bellora Avatar answered Sep 30 '22 11:09

Paul Bellora