Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Java allow this code with generics to compile?

Tags:

java

generics

I was recently caught flat-footed by the following Java code:

interface Common {}

interface A extends Common {}

static class B implements Common {}

static class Impl {
  private A a;

  public <T extends A> T translate() {
    return (T) a;
  }
}

static class Usage {
  public void use() {
    Impl impl = new Impl();
    B b = impl.translate(); // Why does this compile?
  }
}

I would have expected that the type constraint on Impl.translate would not allow storing the result in type B to be accepted by the compiler, considering that B does not extend A. Instead of a compiler error, the code throws an UncheckedCastException at runtime.

This only happens when the method returns the type T; if it is the method parameter instead:

  public <T extends A> void translate(T t) {}

Then instances of B are not allowed as parameters to translate, as expected.

What's going on here? Why is this allowed by Java's type system?

like image 595
moople Avatar asked Jul 16 '20 15:07

moople


People also ask

How generics are compiled in Java?

To implement generics, the Java compiler applies type erasure to: Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.

Why does Java prohibits generic array creation?

If generic array creation were legal, then compiler generated casts would correct the program at compile time but it can fail at runtime, which violates the core fundamental system of generic types.

What is generics in Java and how it works?

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.

How do I restrict a generic type in Java?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.


Video Answer


2 Answers

This compiles because it is, in principle, possible for an object to be both a B and an A. For example, an instance of this class:

static class C extends B implements A {}

It doesn't matter to the compiler that no such class exists. It might be possible for someone else to import this code from a dependency and define C themselves, and that is valid and must be allowed to work. It doesn't matter that the actual class of the object is trivial to find at compile time because the compiler does not do that kind of analysis. Modifiers that forbid the existence of such a class, such as adding final to B, are also not considered, though I'm not sure about why. It could be just to reduce the complexity of compiler logic.

Changing A to also be a class makes this a compile error because Java does not allow a class to extend multiple classes.

like image 154
Douglas Avatar answered Oct 10 '22 05:10

Douglas


The T of your generic isn't getting assigned. Since it is possible to have a value that is both B and A the compiler assumes you're ok.

If you assign the type to B it you get the error.

B b = impl.<B>translate(); 

Testy.java:16: error: method translate in class Impl cannot be applied to given types; B b = impl.translate(); ^
required: no arguments
found: no arguments
reason: explicit type argument B does not conform to declared bound(s) A

That is a similar error you get when the type is determined by the argument.

public <T extends A> T translate(T t) {
    return (T) a;
  }

B b = impl.translate(new B());

The type is being assigned by the argument, and the argument is a B this will not compile.

As mentioned in the other answer, if both A and B are classes then there will not be a T that can satisfy both.

Testy.java:15: error: incompatible types: inference variable T has incompatible upper bounds B,A
B b = impl.translate(); // Why does this compile?
^
where T is a type-variable:
T extends A declared in method translate()

like image 43
matt Avatar answered Oct 10 '22 06:10

matt