I was reading about unknown types and raw types in generics, and this question came to mind. In other words, is...
Set<?> s = new HashSet<String>();
and
Set s = new HashSet<String>();
... one and the same?
I tried it out, and they both seem to accomplish the same thing, but I would like to know if they are any different to the compiler.
No, they are not the same. Here's the basic difference:
Set<?> s = HashSet<String>();
s.add(2); // This is invalid
Set s = HashSet<String>();
s.add(2); // This is valid.
The point is, the first one is a unbounded parameterized type Set
. Compiler will perform the check there, and since you can't add anything but null
to such types, compiler will give you an error.
While the second one being a raw type, the compiler won't do any check while adding anything to it. Basically, you lose the type safety here.
And you can see the result of loosing type safety there. Adding 2
to the set will fail at compile time for Set<?>
, but for a raw type Set
, it will be successfully added, but it might throw exception at runtime, when you get the element from the set, and assign it to say String
.
Differences apart, you should avoid using raw types in newer code. You would rarely find any places where you would use it. Few places where you use raw type is to access static
fields of that type, or getting Class
object for that type - you can do Set.class
, but not Set<?>.class
.
The first one create a Set<?>
, which means: "a generic Set of some unknown class". You won't be able to add anything to this set (except null) because the compiler doesn't know what its generic type is.
The second creates a raw, non generic set, and you can add anything you want to it. It doesn't provide any type-safety.
I don't see why you would use any of them. Set<String>
should be the declared type.
The first one uses generics and the second one uses the raw form of Set
.
The first one uses a wildcard as the generic type parameter. It means, "a Set
of some specific yet unknown type", so you won't be call methods such as add
that take a generic parameter, because the compiler doesn't know which specific type it really is. It maintains type safety by disallowing such a call at compile time.
The raw form removes all generics and provides no strong typing. You can add anything to such a Set
, even non-String
s, which makes the following code not type-safe:
Set<String> genericSet = new HashSet<String>();
Set rawSet = genericSet;
rawSet.add(1); // That's not a String!
// Runtime error here.
for (String s : genericSet)
{
// Do something here
}
This would result in a runtime ClassCastException
when the Integer
1
is retrieved and a String
is expected.
Maintaining as much generic type information as possible is the way to go.
Set<String> s = HashSet<String>();
Set<?>
tells the compiler that the set contains a specific type, but the type is unknown. The compiler uses this information to provide errors when you attempt to invoke a method with a generic parameter, like add(T)
.
Set
tells the compiler that the set is a "raw" type, where no generic type parameter is given. The compiler will raise warnings, rather than errors, when the object's generic methods are invoked.
In order to add elements to the set without warnings, you need to specify the generic type information on the variable. The compiler can infer the type parameters for the constructor. Like this:
Set<String> s = new HashSet<>();
This information allows the compiler to verify that the Set
is used in a type safe way. If your code compiles without type safety warnings, and you don't use any explicit casts, you can be assured that there will be no ClassCastException
raised at runtime. If you use generics, but ignore type safety warnings, you might see a ClassCastException
thrown at a point where you don't have a cast in your source code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With