3

Given the code

abstract class Base<Thistype extends Base>
{
    public void addOnSomethingListener(Consumer<? super Thistype> action)
    {}

    public void foo()
    {
        System.out.println("foo");
    }
}

class Simple<Thistype extends Simple> extends Base<Thistype>
{

}

class Test1 extends Simple<Test1>
{

}

class Test2 extends Simple
{

}

class Test
{
    public static void main(String[] args)
    {
        Test1 test1 = new Test1();
        Test2 test2 = new Test2();

        test1.addOnSomethingListener(test ->
        {
            test.foo(); // VALID as "test" is of type "Thistype" which is "Test1".
        });

        test2.addOnSomethingListener(test ->
        {
            test.foo(); // INVALID as "test" is of type "Thistype" which is "java.lang.Object" instead of "Base" which is the upper bound.
        });
    }
}

Why is the generic type of class Test2 not defaulted to class Base but instead defaulted to class java.lang.Object?

I provided the upper bound, but it seems that that is irrelevant if wildcards are used or when no generic is given at all.

The code in the main function should be able to be compiled if you ask me.

syntagma
  • 23,346
  • 16
  • 78
  • 134
Wietlol
  • 1,001
  • 1
  • 10
  • 25

2 Answers2

4

By declaring Test2 class without specyfing its type argument:

class Test2 extends Simple {}

you are creating a raw type. Therefore the compiler treats it as an instance of Object.

syntagma
  • 23,346
  • 16
  • 78
  • 134
  • Ok, but is there some way to default it to the upper bound... as if im not mistaken, it can never be anything else. That would solve my problem as well. – Wietlol May 24 '17 at 20:56
  • You can just add another class: `class UpperBounded extends Simple {}` and the declare `Test2` using it as a type argument: `class Test2 extends Simple {}`. – syntagma May 24 '17 at 21:04
  • Yes, but that has no difference compared to the Test1 class. – Wietlol May 24 '17 at 21:06
0

It does not "default to Object".

What actually happened is that by using a raw type (Test2 extends the raw type Simple), it "turns off" all generics for that type. So Test2's addOnSomethingListener method (which is inherited from the raw type Simple) actually has signature void addOnSomethingListener(Consumer action) (i.e. the parameter's type is erased to the raw type Consumer).

It just so happens that the raw type Consumer's accept method has signature void accept(Object) (because it's normally void accept(T), but the T in Consumer is unbounded, so when using the raw type Consumer, it gets erased to void accept(Object).

newacct
  • 119,665
  • 29
  • 163
  • 224