en_Knight
en_Knight

Reputation: 5381

Unclear Compile-time Java Error

I'm running into peculiar behavior in terms of compile time errors with the following code (I'm using JDK7):

public class classA { public void foo( List<Object> o ){} }

public class classB<T>{ public void bar( List<Object> o ){} }

We consider the the following test object

List<String> o = new ArrayList<String>();

There is no way to get java to compile by passing o as a parameter to the method foo of class classA, and as far as I can figure, there shouldn't be.

Now say we're in the main method of classB and try to just call bar without instantiating an instance of classB to call it on. I might expect to get a non-static method can't be called from static context compilation error like I would if I tried to pull that in classA, but instead I get a conversion invocation error. That, makes sense - the types don't line up.

However if I try call bar from a nonstatic context, as in

ClassB b = new classB(); 
b.bar( o );

Java seems to forgive me for not lining up the types and runs the code no problem. I haven't done anything to fix the issue of typcasting, so why does Java let this code execute, where it wouldn't with classA?

Edit: In response to some questions. classA is given just for reference - it isn't supposed to compile and I don't expect it to, so I can't offer code that compiles with it. The code for classB that DOES compile and execute might be given by:

public class classB<T> {
    public void bar( List<Object> o ){}

    public static void main( String[] args ){ 
        classB b = new classB();
        List<String> o = new ArrayList<String>();
        b.bar( o );
    }

}

This code compiles and executes. The exact same code without the generic declaration in the class declaration line does not work. I understand type erasure, which someone eluded to, but how does it help, since T is not references in the method bar or the main code

Also, there are tons of ways to make this code better. I'm really just looking for an explanation of its behavior

Upvotes: 2

Views: 214

Answers (4)

Thilo
Thilo

Reputation: 262494

This is a limitation in how generics are implemented.

They are opt-in.

classB b = new classB();

Here, you are opting out of generics, and you do get a warning.

Note: classB.java uses unchecked or unsafe operations.

When you opt-out of generic type checking, you don't get any of it, for the whole class, even for methods that do not use the bound type T.

As @vandale points out, with generics turned off, you could even get the code compiled with

 public void bar( List<Float> o );  

If you do a

classB<Object> b = new classB<Object>();

it will not compile anymore.

Upvotes: 4

Ravi K Thapliyal
Ravi K Thapliyal

Reputation: 51711

When you define the class as

class ClassB<T>

but instantiate it as

new ClassB().bar(new ArrayList<String>());

you're actually using a raw type (without generics) version of it. If you notice the warning about the type safety; the method signature is bar(List) instead of bar(List<Object>):

Type safety: The method bar(List) belongs to the raw type ClassB. References to generic type ClassB should be parameterized.

If you pass the parameterized type T as, say, String

new ClassB<String>().bar(new ArrayList<String>());

it doesn't compile with the error (notice the method signature again)

The method **bar(List<Object>)** in the type ClassB<String> is not applicable for the arguments (ArrayList<String>)

Upvotes: 4

Michael Aaron Safyan
Michael Aaron Safyan

Reputation: 95489

Java still produces an "unchecked" warning, but it is able to compile because of erasure; that is, the type List<T> and List<E> use the same, non-generic underlying type in the Java virtual machine. You can think of generics as merely syntactic sugar for inserting casts at the call sites and for doing some additional checking, but in the bytecode that the compiler emits, it's as if T and E were replaced with Object everywhere, and so the compiler is able to treat this as a warning and not an error.

Upvotes: 0

paulotorrens
paulotorrens

Reputation: 2321

Try using this instead:

public void method(List<? extends Object> o) { /* body */ }

This way it will accept any list whose generic type argument is a descendent of Object, the way you want. :)

Upvotes: 0

Related Questions