Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do these three parameterized variables differ?

Tags:

java

generics

Given AGenericClass declared as below:

public class AGenericClass<T> {
  T subject;
  public void setSubject(T subject) {
    this.subject = subject;
  }
}

What are the differences between variables a, b, and c?

AGenericClass<String> a = new AGenericClass<>();
AGenericClass<?> b = new AGenericClass<String>();
AGenericClass c = new AGenericClass<String>();

a.setSubject("L"); // OK.

b.setSubject("M"); // Error: setSubject(capture<?>) cannot be
                   // applied to (java.lang.String)

c.setSubject("N"); // Warning: Unchecked call to 'setSubject(T)'
                   // as a member of raw type 'AGenericClass'

a b and c all are declared without complaint from the IDE, but they all behave differently when setSubject is called.

like image 746
Stephen Avatar asked Mar 17 '16 05:03

Stephen


2 Answers

The differences between the three largely have to do with the checks that you get at compile time. Given that generics were meant to guard the user from an unsafe cast at runtime, the critical and major differences between them are rooted there.


AGenericClass<String> declares an instance of a generic class specifically of type String. Any operation done with this generic that requires a generic parameter will have that parameter bound to String, and will enforce type checking and type safety at compile time.

That is to say, if you have AGenericClass<String> a = new AGenericClass<>(); and you try to call a.setSubject(3), Java will not allow the application to compile since the types don't match up.

AGenericClass<?> declares an instance of a generic class with unknown types. Formally, ? is a wildcard in which it could be any type, which is fine if you want to retrieve elements from it, but not fine if you want to add elements from it.

The reason? AGenericClass<?> is actually AGenericClass<? extends Object>, and that's important because of the in and out principle. Generally (although this isn't a strict guarantee), anything generic with extends implies a read-only operation.

As I said before, it's fine to read from, since you're guaranteed a class that is at most an Object, but you can't write to it since you don't know, nor can you guarantee, what kind of object you're adding to it from any given call.

AGenericClass without the type declarations is a raw type. You can read more about them here, but know that they exist for legacy compatibility reasons. If your code makes use of raw types, you lose the compile time checks and your code may wind up throwing a ClassCastException during runtime, which is far more difficult to debug, diagnose, and resolve.

like image 118
Makoto Avatar answered Nov 01 '22 22:11

Makoto


AGenericClass<String> a = new AGenericClass<>();

So called "Diamond" generic instantiating. When you set empty <> on constructor of generic type, compiler will try determine type from context. In example case compiler will use <String> because a declared with <String>.


AGenericClass<?> b = new AGenericClass<String>();

This named "Unbounded Wildcards". b will be created as generic of unknown type. When you are trying to use setSubject(), compiler can't check the type of argument. This why you get exception. To resolve this issue, you can create helper method with defined generic type.


AGenericClass c = new AGenericClass<String>();

In this case c declared as "Raw type". c will be created as generic with <Object> parameter. Because you can assign String to Object, method setSubject() will work with a String. Actually, it will work with any type, that inherited from Object, and potentially this could be cause of error. And compiler warns you about it.

like image 20
Ken Bekov Avatar answered Nov 01 '22 20:11

Ken Bekov