What Is a Raw Type and Why Shouldn't We Use It

How to avoid the use of raw type and use generics instead when the type is unknown?

It's not possible to remove this warning (or at least not without @SuppressWarning). The only way to get rid of it is by doing 2 things:

  1. Making your code type proof: so basically if the invoking code does not use raw type, you are type safe or the invoking code is using rawtypes and it it's fault if the code end up being no type safe
  2. Add the @SuppressWarning("unchecked") on your code.

The warning is there so that you can easily identify where you have a weakness in terms of type safety, in your code. If you use the annotation correctly, then your code is safe and you're sure of not getting any unpleasant ClassCastException, unless you willingly added an @SuppressWarning("unchecked") in a place where you are actually not all that sure that the type is safe.

See some demo code illustrating my point:

import java.util.HashMap;
import java.util.Map;

public class TestGenerics {

private Map<Class<?>, EventHandler<?>> handlers = new HashMap<Class<?>, TestGenerics.EventHandler<?>>();

public interface EventHandler<T> {

boolean handleEvent(T message);
}

// Here you force the invoker to provide the correct type of event handler
// with the given type of klass
// If he wants to make this fail, then he will have to use rawtype
public <T> void registerHandler(Class<T> klass, EventHandler<T> handler) {
handlers.put(klass, handler);
}

public <T> void handle(T message) {
@SuppressWarnings("unchecked") // Here you can add this annotation since you are forcing any invoker to provide a correct EventHandler
EventHandler<T> handler = (EventHandler<T>) handlers.get(message.getClass());
if (handler != null) {
handler.handleEvent(message);
}
}

public static void main(String[] args) {
TestGenerics test = new TestGenerics();
test.registerHandler(Long.class, new EventHandler<Long>() {
@Override
public boolean handleEvent(Long message) {
System.out.println("Received a long " + message);
return true;
}
});
// Here I use raw type but this also means that I created a weak spot in
// terms of type safety
EventHandler handler2 = new EventHandler<String>() {
@Override
public boolean handleEvent(String message) {
System.out.println("THis will never print " + message);
return false;
}
};
test.registerHandler(Integer.class, handler2); // This is where the
// problem comes from
test.handle(3L); // OK
test.handle(1); // ClassCastException

}

}

Java Generics - How is a raw type different from a non generic type

The concept of raw type is only relevant to generic types due to the issue that the raw type is considered to be assignment-compatible with the generic type, but doing such assignment opens a loophole in type safety otherwise guaranteed for the generic type. For example, consider a method void workWith(A<Integer> a). You will be allowed to pass in your a variable, resulting in a type safety incident.

As non-generic types cannot suffer from such issues, they are not termed "raw types".

How raw type works in Java?

The list.get(0).getClass().getName() will access an element in the List, and find out at runtime that it's indeed an integer. It could be anything else, though.

What the compiler knows at compile time is that your list contains items of type Object, see the signature of List.get(int). So it can't prove that int value = list.get(0) would always be correct. You have to explicitly cast.

The whole generics thing in Java is compile-time only. It's a way to tell the compiler how to limit type-parametrized classes only to particular types, and thus guarantee at compile time that operations on them are indeed correct. That is, the compiler won't allow you to put a String into a List<Integer>.

In runtime, the type parametrization information is erased. The JVM operates on by relying on the compiler having done the job of generating correct code. There's nothing at runtime that allows the JVM to know what type the content of a List is; ArrayList has a single implementation for any class parameters. (This is unlike C# or C++.)

Raw types and type safety

Your issue is the use of a raw type in this declaration: List<? extends List>. The second List is raw. Your issue has nothing to do with type erasure.

When entering the following code in Eclipse, I get warnings.

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

public class Test {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
List<List<Integer>> listOfListOfInt = new ArrayList<>();
listOfListOfInt.add(intList);
method1(listOfListOfInt);
System.out.println(listOfListOfInt);
}
private static void method1(List<? extends List> cont) { // [1]
List<? super Date> data = cont.get(0); // [2]
data.add(new Date());
}
}

Warnings from Eclipse

[1] List is a raw type. References to generic type List<E> should be parameterized
[2] Type safety: The expression of type capture#1-of ? extends List needs unchecked conversion to conform to List<? super Date>

When compiling with jdk1.8.0_91, I get:

Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Adding -Xlint:unchecked, I get:

Test.java:17: warning: [unchecked] unchecked conversion
List<? super Date> data = cont.get(0); // [2]
^
required: List<? super Date>
found: CAP#1
where CAP#1 is a fresh type-variable:
CAP#1 extends List from capture of ? extends List
1 warning

As you can see, you have been warned, both by the IDE (Eclipse in my case) and by the Java compiler.

Can we use raw types for <T extends SomeClass>?

Consider this:

class SomeExtendingClass extends SomeClassWithSomeInterface {}
class SomeOtherExtendingClass extends SomeClassWithSomeInterface {}

class Test {
public static void main() {
GenericClass<SomeExtendingClass> generic1 = new GenericClass<SomeExtendingClass>();
generic1.set(new SomeExtendingClass()); // ok
generic1.set(new SomeOtherExtendingClass()); // error (as expected)

GenericClass generic2 = new GenericClass<SomeExtendingClass>();
generic2.set(new SomeExtendingClass()); // ok
generic2.set(new SomeOtherExtendingClass()); // ok (but shouldn't be!)
}
}

What is the difference between raw types and generic types in this declaration

When you say ArrayList<>(), the type in angle brackets is inferred from the declaration, so it resolves to ArrayList<Integer> at compile time.

If you omit angle brackets, then the assignment uses raw type, i.e. collection type without any information of the element type. No type inference is done by the compiler for this assignment.

Both lines will not compile:

list1.add("hello")
list2.add("hello")

because both list1 and list2 variables are declared as ArrayList<String>.

The point of specifying <> is that the assignment of ArrayList() to ArrayList<String> should generate a compiler warning of unchecked assignment, because the compiler can't verify that the raw typed collection to the right of the = sign contains only Integers. In a simple case such as yours, you can control the correctness of the code visually. But consider a more complex scenario:

ArrayList rawList = new ArrayList();
rawList.add("one");

ArrayList<Integer> list = rawList;

Here you break the generic system by assigning a raw list that contains a String to a typed list variable that should contain only Integers. That's where the compiler warning of unchecked assignment comes in handy.

Use of raw types when other generic information is known

Using the raw type means using the erasure of that type. This means that all generic type information is erased (an interesting question would be why this is not the case for static fields). As that part of the JLS that you are referring to indicates, raw types are to be used in legacy (pre-1.5) code only (bar some specific cases where you have no other option). That legacy code won't be able to use any generic type information anyway, so there would be no point in keeping the information that could in theory be known.

In Java, can one get away with using raw unparameterised class-es instead of using dummy interfaces?

Recall that Integer as well as Double both extend Number and in turn, Number extends Object.

A List<Number> is in no way usable as a List<Integer> and vice versa. List<Number> x = new ArrayList<Integer> does not compile. This is called 'invariance', and it is 'correct', because if it did compile, you could add doubles to a list of integers which is obviously not right. It tends to throw people off, though, which is why I mention it. If you want covariance or contravariance, you must opt into this: List<? extends Number> list = new ArrayList<Integer>(); does compile. But then list.add(5); won't, because if it did, you would again be able to add doubles to a list of integers and that wouldn't be right.

Once you entirely omit generics on a thing, all type checking on generics is right out the door, and you don't want that. Can you 'get away with it'? Well, uh, the compiler will toss a bunch of warnings in your face and you turn off quite a bit of type checking. If you mean with 'get away with it': "Does it compile"? Yes, it does. If you mean: "Would this code pass any reasonable java coder's code review"? No, it won't.

Simple solution: What's wrong with using MyGenericType<?> here?

NB: Keys in maps should be immutable; list is not. Using it is therefore quite a bad plan; what is that supposed to represent?



Related Topics



Leave a reply



Submit