Kshitiz Sharma
Kshitiz Sharma

Reputation: 18627

Are generic type parameters converted to Object for raw types?

Consider this piece of code:

public class Main {
    public static void main(String[] args) {
        Cat<Integer> cat = new Cat();
        Integer i= cat.meow();
        cat.var = 6;
    }
}
public class Cat<E> {
    public E var;
    public E meow() {
        return null;
    }
}

As per my understanding since I've not specified the type parameter on LHS, it would be taken as Object. And Cat should become Cat<Object> because for the variable declaration to make any sense T must translate to a Class/Interface reference. Is this a correct understanding of how it works? How is type parameter T handled in case of raw types?

I've discussed this on chat and got following explanation which is way over my head:

Generics works because types are erased. Consider T an erasure of #0-capture-of-Object. When T isn't specified (rawtyped), it is #0-capture-of-(nothing)

What does #0-capture-of-(nothing) mean?

Side note: since generic types are implemented as compile time transformations it would be easier to understand them if one could see the final translated code. Does anyone know a way to see the translated code (not byte code) for a class?

Upvotes: 2

Views: 895

Answers (7)

Cephalopod
Cephalopod

Reputation: 15145

No,

raw types are not as if they are paramterized with Object, nor are they like wildcard types (<?>).
For raw types, generics are turned off.

This code is compiles (with warnings):

Cat c1 = new Cat<String>();
Cat<Integer> c2 = c1;

This code does not:

Cat<? extends Object> c1 = new Cat<String>(); // btw: this is the same as Cat<?>
Cat<Integer> c2 = c1; // error

neither does this:

Cat<Object> c1 = new Cat();
Cat<Integer> c2 = c1; // error

As for the type of var:

the type of the field after compilation is whatever the upper-bound of the parameter is (Object if none is specified). But what does the compiler do if we access var?

Cat<String> c1 = ...
String c1Var = c1.var;

This code compiles without error, but what the compiler will actually compile is this:

Cat c1 = ...
String c1Var = (String) c1.var;

As you can see, var is always treated as a field of type Object, but with generics, the compiler automatically inserts type-safe casts. That's all. If you use raw types, you have to do it yourself. Either way, when you put a Cat that stores an integer in a Cat<String> variable, you will only get a runtime exception if you try to read var.

A quiz

Now look at the declaration of Collections.max. Why do you think the parameter is defined as T extends Object & Comparable<? super T>?

Answer encoded in rot13:

Fb gung nsgre pbzcvyngvba gur erghea glcr vf Bowrpg, abg Pbzcnenoyr. Guvf vf arrqrq sbe onpxjneqf pbzcngvovyvgl (gur zrgubq vgfrys vf byqre guna trarevpf).

Edit:

Here is another good example that I just stumbled upon:

class Foo<T> {
    public <V> V bar(V v) { return v; }
}

//compiles
Foo<Object> foo = new Foo<Object>();
Integer i = foo.bar(1);

//compiles
Foo<?> foo = new Foo<String>();
Integer i = foo.bar(1);

// fails
Foo foo = new Foo();
Integer i = foo.bar(1); // error: Object cannot be converted to Integer

Using no parameters disables generics entirely.

Upvotes: 2

Kshitiz Sharma
Kshitiz Sharma

Reputation: 18627

@Cephalopod has provided the correct answer, however I'd like to expand on that with some of my own explanation.

for the variable declaration to make any sense T must translate to a Class/Interface reference.

That is correct. Generics are a compile time transformation. Runtime system has no notion of abstract types. So before the class is loaded into memory the abstract type T must be replaced by an actual type reference.

Run the following code:

System.out.println(Cat.class.getMethod("meow").getReturnType());
System.out.println(Cat.class.getField("var").getType());

The output is:

class java.lang.Object 
class java.lang.Object

The formal type parameter E has been replaced with Object.

Cat should become Cat<Object>

Wrong. Cat will stay Cat. Why? Look at the decompiled class file for Main:

public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Integer i = (Integer)cat.meow();
        cat.var = Integer.valueOf(6);
    }
}

The purpose of specifying formal type parameter with <> is to enable compiler to generate explicit casts.

When you say new Cat() it doesn't have to turn into anything, the compiler simply won't generate a cast and the method call would look like:

Integer i = cat.meow(); // No cast at all

Are generic type parameters converted to Object for raw types?

To clarify what is being asked here, the above questions means: Is E replaced with java.lang.Object if I don't specify anything when instantiating Cat.

Actually E would be replaced with java.lang.Object even if you specified <Integer> when instantiating Cat. The replacement/transformation is done at compile time while the instantiation is at runtime. How you use the type isn't going to change its class definition.

Upvotes: 1

Svend Hansen
Svend Hansen

Reputation: 3333

This code is valid:

Cat c = new Cat<Integer>();

c is now of the Raw Type Cat. This is not valid:

Cat<Object> c = new Cat<Integer>(); // Compiler error

So, it's not exactly the same. Though you can, after the first line, do things like:

c.var = 5;
System.out.println(c.var);
c.var = 1;
System.out.println(c.var);
c.var = "test";
System.out.println(c.var);

Outputs:

5
1
test

Upvotes: 1

hetec
hetec

Reputation: 359

I think Cat c is a RAW type and could be considered as a "wildcard type" like Cat<?>. Since Cat<?> is the supertype of each type of Cat including Cat<Integer>, Cat c may take a new Cat<Integer> object.

This is also mentioned here: Interoperating with Legacy Code

"Most people's first instinct is that Collection really means Collection. However, as we saw earlier, it isn't safe to pass a Collection in a place where a Collection is required. It's more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection."

...

"So raw types are very much like wildcard types, but they are not typechecked as stringently. This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code."

Upvotes: 0

Blip
Blip

Reputation: 3171

I actually Do not know How it is actually implemented in the bytecode But from my understanding Cat c = new Cat<Integer>(); Stores the new instance of Cat created by new Cat<Integer>() in the variable c. Now if you query c to know what is the type of var it will reply Integer and not Object because the instance that was created has a type of Integer.

Now If you execute c.var = "Text"; and query c to know what is the type of var. It would reply String. This does not means that by default it is converting <T> to Object. It means that c does not know what is the type of var.

I feel that is why the <?> wild card is used. Cat<?> c = new Cat<Integer>(); it would always convert <T> to Object. That is the reason why it is always advised not the use raw types for generics.

Upvotes: 0

Kelo
Kelo

Reputation: 463

I have no links to back this up, but I'd rather assume that c becomes raw type Cat, not Cat<Object>. Raw types don't handle parameter T, which is why they are prone to errors. javadoc says: A raw type is the name of a generic class or interface without any type arguments.

That chat log seems to mean exactly that, but in a confusing manner.

Upvotes: 0

redge
redge

Reputation: 1192

Generic types defined in objects like the

Cat c = new Cat<Integer>();

are only intended to provide the compiler with the chance to check that the types will match at runtime.

Generic types assigned in class definitions are retained in the compiled class.

public class Cat<T extends Number>{}

Or

public class Intcat extends Cat<Integer>{}    

The runtime knows that the generic argument is bound by Number in the first case and is Integer in the first case.

Upvotes: 0

Related Questions