2

I have this code, which fails to compile:

import java.util.ArrayList;
import java.util.List;

public class TypeSafetyTest {

    public static void main(String[] args) {
        TypeSafetyTest test = new TypeSafetyTest();
        test.run();
    }

    private void run() {
        Car car = new Car();
        List<String> wheelWeights = car.getWheelWeights();
    }

    private class Car {
        List<Double> wheelWeights = new ArrayList<Double>();

        public List<Double> getWheelWeights() {
            return wheelWeights;
        }

        public void setWheelWeights(List<Double> wheelWeights) {
            this.wheelWeights = wheelWeights;
        }
    }
}

It gives an "Incompatible types" error on the line:

List<String> wheelWeights = car.getWheelWeights();

However, if I change the line:

private class Car {

to

private class Car<T> {

then the code compiles successfully with the warning "Unchecked assignment" on the line that used to have the compile error. Why is this so? I was expecting it to give a compile error in both cases.

Thomas
  • 1,103
  • 3
  • 13
  • 25
  • Trying to understand Java Generics leads only to madness. I've been looking through the Java Language Specification and I still can't figure out the answer to this question. – Antimony Dec 04 '12 at 22:08
  • Not a duplicate, but closely related: [What is a raw type and why shouldn't we use it?](http://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it) Basically, once you declare your class using a raw type, you've explicitly opted out of all generics for the the entire class. – Daniel Pryden Dec 04 '12 at 22:22

2 Answers2

3

One issue I see is:

 public List<Double> getWheelWeights() {
            return wheelWeights;
        }

returns Double type List, but you are assigning it to List<String>

The reason why you are not seeing compile error when you change Car<T> is, because your instance creation is raw type, which is telling compiler that ignore type safety at compile time, due to backward compatability.

Car<Object> car = new Car<Object>();

If you change your object like this, then you will see compiler throwing the message again.

Please refer JLS 4.8 for more information.

Here is example from JLS:

    class Outer<T>{
        class Inner<S> {
            S s;
        }
    }

Outer.Inner<Double> x = null;  // illegal

It is not possible to access Inner as a partially raw type (a "rare" type), because Outer itself is raw, hence so are all its inner classes including Inner, and so it is not possible to pass any type arguments to Inner

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

I would suggest reading Generics (Updated)

kosa
  • 65,990
  • 13
  • 130
  • 167
  • He knows that.. he is asking why it doesn't give an error when he uses a parametric type. My answer is purely an educated guess. I figure you can give a more correct answer. – Lews Therin Dec 04 '12 at 21:59
  • I see. If the compiler is really ignoring type safety because the Car object is not initialized with a type then I would expect the compiler to only ignore type safety for the fields in Car that actually use the type declaration. – Thomas Dec 04 '12 at 22:18
  • 1
    @Thomas Raw types are basically a compatibility measure for legacy code, so very little type checking is applied. – Antimony Dec 04 '12 at 22:19
  • @ThomasRyabin: raw types are there for To facilitate interfacing with non-generic legacy code, the warning message you are getting there tells you that if you want type safety at compile time make sure you are always abide to generics. – kosa Dec 04 '12 at 22:21
  • @ThomasRyabin: That is a common misconception. But that is not how the Java language is specified. Raw types should only ever be used for compatibility with pre-generics code -- any other use is almost certainly an error, for this very reason. – Daniel Pryden Dec 04 '12 at 22:26
  • @ThomasRyabin: I have added link to java language specification which talks about raw types and generics. – kosa Dec 04 '12 at 22:26
  • If I don't know what type to make the Car object when I initialize it, should I just make the type > so I can get the benefits of generics? – Thomas Dec 04 '12 at 22:36
  • @ThomasRyabin: If your class is not generic, only method is generic that's ok. Don't even add to Car class declaration. Just change List to List, but if you define class as generic, but instantiated as rawtype then you will see the issue. When initialize you can't leave type as generic type (T, ?, etc.,), it should be concrete – kosa Dec 04 '12 at 22:38
  • @Nambari In my case I do want to make Car generic. – Thomas Dec 04 '12 at 22:41
  • @ThomasRyabin: If that is the case then remove from Car and change List to List, that should resolve the issue. I have updated answer with a link to generics tutorial, that should clarify lot of questions you have. – kosa Dec 04 '12 at 22:43
  • @Nambari No I want to keep the in the Car class. I was just wondering if I should initialize the car object with the type > in this case. – Thomas Dec 04 '12 at 22:50
  • @ThomasRyabin: as said in previous comment, while instantiating car, you need to provide concrete type. If you are not sure what it is, but want to keep , then as shown in my answer, just use Object. – kosa Dec 04 '12 at 22:53
  • @ThomasRyabin: if you want the `car` variable to be type `Car>`, yes, that would be fine. However, when creating the `Car` object, you need to explicitly specify a type parameter (anything will do, even a random dummy class you create) or in Java 7, use the diamond operator; and then you can assign it to a variable of type `Car>` if you want. But if you're doing that, it doesn't make sense -- what is the point of parameterizing `Car` with `T` if you're not going to use it? – newacct Dec 05 '12 at 01:02
  • @newacct: In other code I might want to specify the type for a Car object, it's just that in this case I don't care what the type of the Car object is. – Thomas Dec 05 '12 at 13:51
3

When you change Car to Car<T>, Car becomes a generic class. Therefore, your car object now has the raw type Car, since you didn't provide any type parameters. And nonstatic non inherited members of a raw type are also a raw type. This means that getWheelWeights() is now returning a raw type. And since it's a raw type, it does an unchecked conversion, meaning you only get a warning, not an error.

You can fix this by providing a type argument for car. For example

Car<Object> car = new Car();

Note that if you want the type safety benefits of generics, you shouldn't use raw types at all. They are just a compatibility fix for pre-generic code, hence the contagiousness and lack of checking for raw types.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • 3
    Your "fix" will still cause an unchecked conversion warning, since the right-hand side is still a raw type (but at least usages of the `car` variable will not be). Much better would be to specify the type argument on both sides, like so: `Car car = new Car();`. – Daniel Pryden Dec 04 '12 at 22:27
  • @Daniel Good point. I just meant "fix" as in make the compiler error return, but you're right that it's better to use a type argument on both sides. – Antimony Dec 05 '12 at 01:34