2

I am trying to understand why the compiler doesn't print a compile-time error in this code below. It compile, but obviously won't work.

Someone know why the compiler allow it?

public class Tests {
    public static void main(String... args){
        // Lines below are acceptable for the compiler and work well in runtime.
        GenericClass<FooClassWithFooInterface> genericClass1 = new GenericClass();
        genericClass1.print(new FooClassWithFooInterface());

        // Lines below are oddly acceptable for the compiler and, obviously, won't work in runtime.
        GenericClass genericClass2 = new GenericClass();
        genericClass2.print(new FooClassWithFooInterface());
        genericClass2.print(new FooClass()); // why the compiler not throw a compile-time error?
    }
}


class GenericClass<T extends FooClass & FooInterface>{
    public void print(T t){
        t.fooMethod();
    }
}

class FooClass{

}

interface FooInterface{
    public void fooMethod();
}

class FooClassWithFooInterface extends FooClass implements FooInterface{
    @Override
    public void fooMethod() {
        System.out.println("foo");
    }   
}

Console output:

foo
foo
Exception in thread "main" java.lang.ClassCastException: FooClass cannot be cast to FooInterface
    at GenericClass.print(Tests.java:18)
    at Tests.main(Tests.java:11)

I have created fooMethod() just to force this runtime error.

I think that the compiler can check that new FooClass() doesn't match with <? extends FooClass & FooInterface> and force a compile-time error.

if we change the GenericClass to T extends FooClassWithFooInterface, instead of T extends FooClass & FooInterface, the compiler show finally the compile-time error :

class GenericClass<T extends FooClassWithFooInterface>{
    public void print(T t){
        t.fooMethod();
    }
}

Also, I did not find any restriction related to this issue in Restrictions on Generics(The Java Tutorial)

user207421
  • 305,947
  • 44
  • 307
  • 483
Paulo
  • 2,956
  • 3
  • 20
  • 30
  • Are you getting any compiler _warnings_? Looks like your doing unsafe assignments. – Mick Mnemonic Apr 10 '15 at 18:13
  • 2
    Because you're using raw types. Don't. See http://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it – JB Nizet Apr 10 '15 at 18:13
  • I am getting warnings. But not errors. I think that the compiler can check that `new FooClass()` doesn't match with ` extends FooClass & FooInterface>` and force an error. – Paulo Apr 10 '15 at 18:19
  • 1
    Java will let you do things that will fail with warnings. That's what the warnings are for. – Louis Wasserman Apr 10 '15 at 18:22
  • The important point is, a *raw type* like `GenericClass` is **not** the equivalent to `GenericClass extends FooClass & FooInterface>`! In the case of the latter you couldn’t pass anything (besides `null`) to `print`. – Holger Apr 10 '15 at 19:00
  • Exceptions are thrown. Compile errors are printed. – user207421 Apr 10 '15 at 23:04

2 Answers2

0

When you use multiple bounds, after compile-time type erasure, the first bound is retained in type signatures. Casts are inserted as needed for the subsequent bounds. So, if you were to look at the compiled GenericClass, you would see something like

class GenericClass {
    public void print(FooClass t){
        ((FooInterface) t).fooMethod();
    }
}

Because the compiler sees that GenericClass has a print(FooClass) method, it doesn't complain. But at runtime, the cast inside the method fails.

Why does the compiler allow this, when a human can reason that this is bound to fail? Well, the compiler isn't as smart as you. It can only find problems when you restrict yourself to typesafe code, which means never using raw types or suppressing type warnings.

There are a couple of other situations where a person, looking at context, can reason that other operations are safe, but the compiler will complain. The compiler just uses declared information, and looks at one expression at a time; it doesn't consider the entire context of the generic types.

erickson
  • 265,237
  • 58
  • 395
  • 493
0

You are explicitly telling the compiler not to check for types here:

GenericClass genericClass2 = new GenericClass();

By omitting the generic type parameter, you're forcing the compiler into legacy mode (its there so code written for java prioer to 5 compiles with JDK5+). If the generic type parameter isn't given you get a warning and the compiler accepts any types for the generic type.

So basically you're shooting yourself in the foot and now complain the compiler didn't stop you, after explicitly telling him to shut up :)

Durandal
  • 19,919
  • 4
  • 36
  • 70