Monday, March 21, 2016

Repeating Annotation Effect - How to get the correct annotation at runtime

Since Annotation was introduced in Java 5, Java had also provided the Reflection interface, java.lang.reflect.AnnotatedElement which represents an annotated element of the program at runtime. It allows us to get the program element's annotations reflectively. An annotation might not be directly present on a program element. Hence, getter methods in AnnotatedElement help to detect and return the annotation(s) with certain kind of presence.

Moreover, the kind of annotation presence is further expanded when Java 8 introduced Repeating Annotation. And of course, new methods were added onto AnnotatedElement to make sure the caller is able to retrieve the annotation with new kind of presence.

This post describes:
  • Different kind of annotation presence, prior to and since Java 8.
  • Which AnnotatedElement method deals with which kind of annotation presence.
The following annotation types and classes are used for explanation in this post.

Given annotation types below.
@Retention(RetentionPolicy.RUNTIME) // Annotation available at runtime
@Target(ElementType.TYPE) // Target to Class/Interface element only
@Inherited // Inheritable. In this case, annotation is inherited to Subclass
public @interface Alert {
    String name() default "";
}

@Retention(RetentionPolicy.RUNTIME) // Annotation available at runtime
@Target(ElementType.TYPE) // Target to Class/Interface element only
@Inherited // Inheritable. In this case, annotation is inherited to Subclass
public @interface Role {
    String name() default "";
}

Apply annotation onto the Access classes below.
@Role(name = "superadmin")
@Alert(name = "superadmin") 
abstract class Access { // Superclass
}

@Role(name = "staff") // Overrided superclass @Role annotation
class ReadOnlyAccess extends Access { // Subclass
}

Now we will use the methods in AnnotatedElement to retrieve the @Role and @Alert annotation at runtime.

Prior to Java 8

Prior to Java 8, there are no precise terms and descriptions about the annotation presences. However, they are obvious and  not too complicated to be understood. Basically, an annotation could have 2 kinds of presences on a program element.
  • Directly present
  • Present

Directly Present

An annotation is explicitly specified on an element and this annotation is visible or available at runtime. To get the annotation that direct present on an element, we use getDeclaredAnnotation(Class) method.

Class c = ReadOnlyAccess.class;
Role roleDirectPresent = (Role) c.getDeclaredAnnotation(Role.class); // roleDirectPresent = @Role(staff)
Alert alertDirectPresent = (Alert) c.getDeclaredAnnotation(Alert.class); // alertDirectPresent = null
Annotation[] allDirectlyPresent = c.getDeclaredAnnotations(); // allDirectPresent = {@Role(staff)}

We are able to get the @Role(staff) because it is directly specified on the ReadOnlyAccess class element. On the hand, although @Alert is specified on Access class element, and it is inherited to ReadOnlyAccess, but we never able to get it using getDeclaredAnnotation(Class) because it is not direct present on ReadOnlyAccess class element.

Present

An annotation is either directly present on an element or inherited from the element's parent and this annotation is visible or available at runtime. To get the annotation that present on an element, we use getAnnotation(Class) method.

Class c = ReadOnlyAccess.class;
Role rolePresent = (Role) c.getAnnotation(Role.class); // rolePresent = @Role(staff)
Alert alertPresent = (Alert) c.getAnnotation(Alert.class); // aleartPresent = @Alert(superadmin) - inherited
Annotation[] allPresent = c.getAnnotations(); // allPresent = {@Role(staff), @Alert(superadmin)}

By using getAnnotation(Class) method, we not only able to get the annotation @Role(staff) that is directly present on ReadOnlyAccess, we also able to get the annotation @Alert(superadmin) that is inherited from Access class element.

Note: Getting an inherited annotation does not work for all annotated element. For example, it does not works for METHOD element because the getAnnotation(Class) method in java.lang.reflect.Method still getting the annotation that directly present on the method element.

Since Java 8

Started from Java 8, an annotation type supports repeatable. We are now able to specify zero to many annotations with the same annotation type. Now, let enhance our annotation type to be repeatable.

Repeating Annotation

We first create the containing annotation types.
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Alerts {
    Alert[] value(); // to contain an array of Alert annotations
}

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
    Role[] value(); // to contain an array of Role annotations
}

Then specify the @Repeatable annotation onto the annotation types respectively.
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Alerts.class) // Use @Alerts as container annotation
public @interface Alert {
    String name() default "";
}

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Roles.class) // Use @Roles as container annotation
public @interface Role {
    String name() default "";
}

A few important notes to take:
  • The containing annotation type must have a method named as "value" and return an array of specific annotation. If not, it will be treated as normal annotation type.
  • The characteristic (such as inheritable, runtime visible, target) of the containing annotation type must consistent with the annotation type that it would like to contain. Failing to do so will cause compilation error at annotation type that takes it as annotation container.

Backward Compatibility

Making an annotation type to support repeatable will not break backward compatibility. In the case where the Access and ReadOnlyAccess class remain the same,
@Role(name = "superadmin")
@Alert(name = "superadmin") 
abstract class Access { // Superclass
}

@Role(name = "staff") 
class ReadOnlyAccess extends Access { // Subclass
}

Despite the @Alert and @Role is now repeatable, but we are still able to get the single annotation using the methods below..
Role roleDirectPresent = (Role) c.getDeclaredAnnotation(Role.class); // roleDirectPresent = @Role(staff)       
Role rolePresent = (Role) c.getAnnotation(Role.class); // rolePresent = @Role(staff)

Note: The @Role(staff) annotation that is specified on the ReadOnlyAccess class element will still override the @Role(superadmin) annotation of it superclass Access. They are never accumulated to become repeating annotation

New Kind of Annotation Presence

Due to the existence of the container annotation, new kinds of annotation presence are introduced. What does the methods in AnnotatedElement can do also become more complicated.
  • Directly Present
  • Indirectly Present
  • Present
  • Associated

Let's specify the repeating annotation in our classes. I will then use the AnnotationElement methods to get the annotation and explain the kind of presence accordingly. You may need to come back to here again when you go through the next sections because the example codes in next section are all talking about getting annotation we have specified here.
@Role(name = "superadmin")
@Alert(name = "superadmin") 
@Alert(name = "superuser") // Repeated @Alert
abstract class Access {
}

@Role(name = "staff")
@Role(name = "guest") // Repeated @Role
class ReadOnlyAccess extends Access {
}

Directly Present

An annotation A is directly present on an element E if E has a RuntimeVisibleAnnotations or RuntimeVisibleParameterAnnotations or RuntimeVisibleTypeAnnotations attribute, and the attribute contains A. - AnnotatedElement Oracle Doc

Well, it basically maintains the contract prior to Java 8. However, things work differently due to the existence of repeating annotation. Look at the ReadOnlyAccess class, now we have the repeating annotation, in this case, two @Role annotations are specified on it. What we see is two individual @Role annotations but in fact, they are contained in a container annotation @Roles. Decompile the ReadOnlyAccess class and you will find that, Java Compiler had turned them into a container annotation as below.

@Roles(
    value = {
        @Role(name = "staff"),
        @Role(name = "guest")
    }
)
class ReadOnlyAccess extends Access {
}

That's mean, what directly present on the ReadOnlyAccess class element is container annotation @Roles.

Note: We can specify the repeating annotations by using the containing annotation type. After all, it is also a valid annotation type.

Let's try to get the annotation that directly present on ReadOnlyAccess class element. Again, we use the getDeclaredAnnotation(Class) to do that. Have a look on the variable(s) returned from each method call below, they should help you to understand the idea behind.

Class c = ReadOnlyAccess.class;

// roleDirectPresent = null 
// @Role annotation no longer direct present. It is an attribute in container annotation @Roles.
Role roleDirectPresent = (Role) c.getDeclaredAnnotation(Role.class);

// rolesDirectPresent = @Roles({@Role(staff), @Role(guest)})
// @Roles is the container annotation that direct present on the class element.
Roles rolesDirectPresent = (Roles) c.getDeclaredAnnotation(Roles.class);

// alertDirectPresent = null
// @Alert not explicitly present at all.
Alert alertDirectPresent = (Alert) c.getDeclaredAnnotation(Alert.class);

// allDirectPresent = {@Roles({@Role(staff), @Role(guest)})}
Annotation[] allDirectPresent = c.getDeclaredAnnotations();

// allRolesDirectPresent = @Roles({@Role(staff), @Role(guest)})
// New method in AnnotatedElement since Java 8. Get container annotation that is directly present on the class element
Roles[] allRolesDirectPresent = (Roles[]) c.getDeclaredAnnotationsByType(Roles.class);

The getDeclaredAnnotationsByType(Class) is a new method in AnnotatedElement which was introduce since Java 8. It can be used to get annotation that is directly present on an element. It seems working equivalent to getDeclaredAnnotation(Roles.class), but it actually can do more. See the next Indirectly Present section.

Indirectly Present

An annotation A is indirectly present on an element E if E has a RuntimeVisibleAnnotations or RuntimeVisibleParameterAnnotations or RuntimeVisibleTypeAnnotations attribute, and A 's type is repeatable, and the attribute contains exactly one annotation whose value element contains A and whose type is the containing annotation type of A 's type. - AnnotatedElement Oracle Doc

This is one of the new kinds of annotation presence introduced since Java 8. By using our example code for the explanation, the annotation @Role is indirectly present on a ReadOnlyAccess class element because it is runtime visible, and it resides in the @Roles container annotation which is specified on ReadOnlyAccess.

At first glance on the ReadAccessOnly, it may hard to understand why @Role annotations are not directly present on ReadAccessOnly. They did directly specified on the ReadAccessOnly class element. The reason is what I have described in previous Directly Present section. Whenever more than one repeating annotation specified on an element, Java will use container annotation to host the repeating annotations. Therefore, @Role annotations are indirectly present on ReadAccessOnly class element via container annotation @Roles

The getDeclaredAnnotationByType(Class) method can be used to get the annotations from the container annotation.

// allRoleDirectPresent = {@Role(staff), @Role(guest)}
Role[] allRoleDirectPresent = (Role[]) c.getDeclaredAnnotationsByType(Role.class);

This saves our effort from getting the container annotation first, then look through its value attribute to get the annotations.

Present

An annotation A is present on an element E if either:
  • A is directly present on E; or
  • No annotation of A 's type is directly present on E, and E is a class, and A 's type is inheritable, and A is present on the superclass of E.
AnnotatedElement Oracle Doc

Again, it basically maintains the contract prior to Java 8. The AnnotatedElement methods which able to get annotations that present on an element now also able to get the container annotation which directly present on the element or inherited from the element's parent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// rolePresent = @Role(superadmin)
// An annotation inherited from parent class element.
Role rolePresent = (Role) c.getAnnotation(Role.class);

// rolesPresent = @Roles({@Role(staff), @Role(guest)})
// Container annotation is directly present on class element
Roles rolesPresent = (Roles) c.getAnnotation(Roles.class);

// alertPresent = null
// Not specify in either superclass or subclass.
Alert alertPresent = (Alert) c.getAnnotation(Alert.class);

// alertsPresent = @Alerts({@Alert(superadmin), @Alert(superuser)})
// Container annotation is inherited from parent class element.
Alerts alertsPresent = (Alerts) c.getAnnotation(Alerts.class);

// allPresent = {@Role(staff), @Alerts({Alert(superadmin), @Alert(superuser)})}
Annotation[] allPresent = c.getAnnotations();

The interesting part is at line #3 and #7. The @Role annotation specified on superclass Access is not overridden by the @Roles container annotation specified on subclass ReadOnlyAccess. Well, they are two different annotations in fact. And of course, they will not be accumulated into a container annotation.

Associated

An annotation A is associated with an element E if either:
  • A is directly or indirectly present on E; or
  • No annotation of A 's type is directly or indirectly present on E, and E is a class, and A's type is inheritable, and A is associated with the superclass of E.

Another new kind of annotation presence introduced since Java 8. It covers the broadest annotation presence where an annotation is directly or indirectly present on an element or inherited from the element's parent.

The only method in AnnotatedElement that able to return annotation that associated to an element is getAnnotationsByType(Class).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// roleAssociated = {@Role(staff), @Role(guest)}
// Because @Role is repeatable, hence look through the container annotation and return it value attributes.
Role[] roleAssociated = (Role[]) c.getAnnotationsByType(Role.class);

// rolesAssociated = {@Roles({@Role(staff), @Role(guest)})}
// @Roles container annotation is directly present on class element.
Roles[] rolesAssociated = (Roles[]) c.getAnnotationsByType(Roles.class);

// alertAssociated = {@Alert(superadmin), @Alert(superuser)}
// Because @Alert is repeatable, hence look through the inherited container annotation and return it value attributes.
Alert[] alertAssociated = (Alert[]) c.getAnnotationsByType(Alert.class);

// alertsAssociated = {@Alerts({@Alert(superadmin), @Alert(superuser)})}
// @Alerts container annotation is inhertied from parent class element.
Alerts[] alertsAssociated = (Alerts[]) c.getAnnotationsByType(Alerts.class);

The highlights are at line #3 and #7.

#3 - Because @Role is repeatable, so getAnnotationByType(Role.class) looks through the container annotation @Roles and return all @Role annotations that the container contains. If the container is empty, then it will return the inherited @Role annotation (if any).

#7 - getAnnotationByType(Alert.class) is able to return all @Alert annotation inhertied by the ReadOnlyAccess.

Summary

Creating a repeating annotation type is straightforward, but getting the correct annotation could be a bit complicated. By knowing different kinds of annotation presence, it will help us to get the annotation that we want.

Overview of kind of presence detected by different AnnotatedElement methods - AnnotatedElement Oracle Doc
Kind of Presence
MethodDirectly PresentIndirectly PresentPresentAssociated
TgetAnnotation(Class<T>) X
Annotation[]getAnnotations() X
T[]getAnnotationsByType(Class<T>) X
TgetDeclaredAnnotation(Class<T>) X
Annotation[]getDeclaredAnnotations() X
T[]getDeclaredAnnotationsByType(Class<T>) XX

Besides runtime processing, Java also introduced the javax.lang.model.AnnotatedConstruct interface which represents the annotated source code element and allows us to get the annotation during annotation processing at compile time. The annotation presence terms and concepts are same as the AnnotatedElement. If you are interested in annotation processing, you can learn more from Annotation Processing during Compile Time.

References:
https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/AnnotatedElement.html
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AnnotatedElement.html
https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html
https://docs.oracle.com/javase/8/docs/api/javax/lang/model/AnnotatedConstruct.html

Saturday, February 20, 2016

The Cause and Effect of Java Generics Wildcard

IS-A Relationship

In object-oriented programming, IS-A relationship is as common as breathing for programmers. See the class hierarchy diagram below.

diagram 1: IS-A relationship

Nothing much to say and the code line in diagram 1 above is 100% valid because Phone IS A Device. When the classes above become type argument of parameterized type (e.g, List<Phone>, List<Device>, List<Camera> in diagram 2 below), the IS-A relationship is still applied to determine what type of object can be set/put into the object with parameterized type.

diagram 2: Type argument determines what object can be accepted

Common Misunderstanding

However, when looking into the relationship of the List<Phone>List<Device>List<Camera> we always subconsciously think that List<Device> is the parent of List<Phone> and List<Camera>. This is not true. In fact, three of them are referring to the same class, List. In the List class hierarchy the type Device, Phone and Camera don't even exist. 

This is the very common misunderstanding in Java Generics yet the important concept to learn. You have to aware that the code such as the code line in diagram 3 below is invalid due to incompatible type compilation error.
diagram 3: Common misunderstanding

Wildcard

Is that means they do not have a relationship at all? Not really, they do have a relationship indirectly. Thanks to the Wildcard(appears as ?) which make this relationship happen. In short,
  • The wildcard is a special kind of type argument with unknown type,
  • The wildcard type (e.g. List<?>) is type-safe guaranteed by Java compiler like other parameterized type,
  • The wildcard type is an abstract parameterized type. The actual parameterized type of a wildcard typed variable could be any other normal parameterized type. See the code line in diagram 4 below. The actual type of list variable is List<Phone>.

diagram 4: Common parent of parameterized types

In general, three of them are having the same parent which is List<?>.

This is not the end of the story. Besides unbounded wildcard type as you have seen in diagram 4, the wildcard type can also appear in 2 other forms, upper-bounded wildcard type (<? extends ...>), and lower-bounded wildcard type (<? super ...>). This makes the wildcard types hierarchy (diagram 5) even more interesting.

diagram 5: Wildcard types hierarchy

Upper-bounded Wildcard Type

The upper-bounded wildcard type hierarchy could be illustrated in the following diagram 6.

diagram 6: Upper-bounded wildcard type hierarchy
Take a look at the List<? extends Phone>. This wildcard type basically claims that "whichever List with type argument IS A Phone is under my territory". In other words, any List with type argument IS A Phone become a subtype of List<? extends Phone>.

If we look at the List<? extends Device>, not only the actual parameterized type List<Phone>, List<Camera> and List<Device>, the abstract parameterized type List<? extends Phone> and List<? extends Camera> also is its subtypes. Because all of their type argument IS A Device.

The assignment statements in the following code snippet are all valid.

List<Phone> phones = new ArrayList<>();
List<Camera> cameras = new ArrayList<>();
List<Device> devices = new ArrayList<>();

List<? extends Phone> phonesWildcard;
List<? extends Camera> cameraWildcard;
List<? extends Device> deviceWildcard;

// Assignments

phonesWildcard = phones;
cameraWildcard = cameras;

deviceWildcard = devices;
deviceWildcard = phones;
deviceWildcard = cameras;
deviceWildcard = phonesWildcard;
deviceWildcard = cameraWildcard;

Now, let's recap what does the actual parameterized type could contain. This is important because together the type-safe principle, they make up the upper-bounded wildcard type characteristics. See diagram 7 below.

diagram 7: What makes up upper-bounded wildcard type characteristics

Question 1: What type of object is eligible to be put into List<? extends Device>?

The answer is none. The actual parameterized type of List<? extends Device> is not clear-cut. We never know if its actual parameterized type is List<Phone>, List<Camera> or List<Device>. See the following code snippet.

List<Phone> phones = new ArrayList<>();
phones.add(new Phone());

List<? extends Device> deviceWildcard;
deviceWildcard = phones; // The actual type is List<Phone> that contains Phone object

deviceWildcard.add(new Camera()); // break type-safe. Compilation error!

The actual parameterized type of deviceWildcard above is List<Phone>. Although Camera is a Device, putting it into deviceWildcard breaks the type-safe principle. Java compiler will make sure we get a compilation error for that.

Question 2: What type of object can be retrieved from List<? extends Device>?

The answer is Device. Look at the content object of all the actual parameterized type covered under List<? extends Device> in diagram 7, they have one common fact - they are having the same parent, Device. This means it is safe to assume that whatever object get from the List<? extends Device> is definitely IS A Device.

List<Phone> phones = new ArrayList<>();
phones.add(new Phone());

List<? extends Device> deviceWildcard;
deviceWildcard = phones; // The actual type is List<Phone> that contains Phone object
        
Device device = deviceWildcard.get(0); // OK. Phone is a Device

Example of using Upper-bounded Wildcard Type

If you wonder how could the upper-bounded wildcard type help, here is an example. Given the Device classes below (well, the methods implementation are not important.)

public class Device {
    
    public void on() {...}
    
    public void off() {...}
}

public class Phone extends Device {
    
    public void call() {...}
}

public class Camera extends Device {
    
    public void shootPhoto() {...}
}

Every device can be on() and off(). Now, we need a method to test every device simply by turning it on then off.

public static void testDevices(List<Device> devices) {
    for (Device device : devices) {
        device.on();
        device.off();
    }
}

public static void main(String[] args) {

    List<Device> devices = new ArrayList<>();
    devices.add(new Phone());
    devices.add(new Camera());
    testDevices(devices); // OK
}

The codes above are valid and works fine. We can test a devices list which its content objects could be Phone or Camera typed. Now, how about if we pass phones list or cameras list to the testDevices() method as below?

List<Phone> phones = new ArrayList<>();
phones.add(new Phone());
testDevices(phones); // Compilation error! Incompatible types.

List<Camera> cameras = new ArrayList<>();
cameras.add(new Camera());
testDevices(cameras); // Compilation error! Incompatible types.

Logically, testDevices() should able to accept phones and cameras list. After all, Phone and Camera are Device which can be turned on() and off(). However, from the code, Java Compiler is expecting the actual parameterized type with type argument Device. This restriction causing phones and cameras which logically should be accepted by testDevices() become compilation errors.

In this case, we can use the upper-bounded wildcard type to loose the type restriction. See the following updated testDevices() below.

public static void testDevice(List<? extends Device> devices) {
    for (Device device : devices) {
        device.on();
        device.off();
    }
}

The method parameter becomes an abstract parameterized type and able to accept actual parameterized type as long as its type argument IS A Device.

Lower-bounded Wildcard Type

The lower-bounded wildcard type hierarchy could be illustrated in the following diagram 8.

diagram 8: Lower-bounded wildcard type hierarchy
First, we look at the List<? super Device>. This wildcard type basically claims that "whichever List with type argument IS A Device or Super-type of Device is under my territory". Well, from the Devices class hierarchy, only the Device meets this acceptance criteria, hence only List<Device> is a subtype of List<? super Device>.

If we look at the List<? super Phone>, this time, the actual parameterized type List<Phone> and List<Device> as well as the abstract parameterized type List<? super Device> meet the acceptance criteria and hence, they are subtypes of List<? super Phone>.

The assignment statements in the following code snippet are all valid.

List<Phone> phones = new ArrayList<>();
List<Camera> cameras = new ArrayList<>();
List<Device> devices = new ArrayList<>();

List<? super Phone> phonesWildcard;
List<? super Camera> cameraWildcard;
List<? super Device> deviceWildcard;

// Assignments
deviceWildcard = devices;

phonesWildcard = phones;
phonesWildcard = devices;
phonesWildcard = deviceWildcard;

cameraWildcard = cameras;
phonesWildcard = devices;
cameraWildcard = deviceWildcard;

Again, let's recap what does the actual type could contain. This is important because together the type-safe principle, they make up the lower-bounded wildcard type characteristics. See diagram 9 below.

diagram 9: What makes up lower-bounded wildcard type characteristics

Question 1: What type of object is eligible to be put into List<? super Phone>?

The answer is any object that IS A Phone. Refer to diagram 9, the actual parameterized type for List<? super Phone> is List<Phone> and List<Device>. Both can definitely keep any object that IS A Phone.

Question 2: What type of object can be retrieved from List<? super Phone>?

The answer is the highest type - Object. This is because the actual parameterized type of List<? super Phone> is not clear-cut. It could be List<Phone> or List<Device>. Whereby the List<Device> could contain an object with type other than Phone. See the following code snippet.

List<Device> devices = new ArrayList<>();
devices.add(new Camera()); // devices list that contains Camera object

List<? super Phone> phonesWildcard;
phonesWildcard = devices;

Phone phone = phonesWildcard.get(0); // Compilation error! Incompatible types.

phonesWildcard never able to confirm whether its content object is a Phone. Doing that will get a compilation error. Therefore, the safest way is to return the highest type - Object.

Example of using Lower-bounded Wildcard Type

If you also wonder how could the lower-bounded wildcard help, here is an example. Let's say we need respective methods to create and test a specific device 's functionality then put it into the given List parameter.

public static void packPhone(List<Phone> phones) {
    Phone phone = new Phone();
    phone.call(); // test Phone functionality
    phones.add(phone); // put Phone into given list
}

public static void packCamera(List<Camera> cameras) {
    Camera camera = new Camera();
    camera.shootPhoto(); // test Camera functionality
    cameras.add(camera); // Put Camera into given list
}

public static void main(String[] args) {

    List<Phone> phonesList = new ArrayList<>();
    packPhone(phonesList);

    List<Camera> camerasList = new ArrayList<>();
    packCamera(camerasList);
}

The codes above are valid and works fine. Now, how about if we have a devices list that would like to pack Phone and Camera?

List<Device> devicesList = new ArrayList<>();
packPhone(devicesList); // Compilation error! incompatible types.
packCamera(devicesList); // Compilation error! incompatible types.

By right, we can put Phone and Camera object into devicesList. However, from the code, Java Compiler is expecting the actual parameterized type with type argument Phone for packPhone() and Camera for packCamera(). This restriction makes both methods can't accept deviceList.

In this case, we can use the lower-bounded wildcard type to loose the type restriction. See the following updated packPhone() and packCamera() below.

public static void packPhone(List<? super Phone> phonesWildcard) {
    Phone phone = new Phone();
    phone.call(); // test Phone functionality
    phonesWildcard.add(phone); // put Phone into given list
}

public static void packCamera(List<? super Camera> camerasWildcard) {
    Camera camera = new Camera();
    camera.shootPhoto(); // test Camera functionality
    camerasWildcard.add(camera); // Put Camera into given list
}

The method parameter becomes an abstract parameterized type and able to accept the parameterized type as long as its type argument IS A super-type of Phone and Camera respectively.

Unbounded Wildcard Type

The unbounded wildcard type is an abstract parameterized type and the common parent of all parameterized type, either actual or abstract. It could contain a object of any type (like List<Object>); the actual parameterized type that it referencing to is never clear-cut (like List raw type). However, it is loose restriction compare to the actual parameterized type List<Object> and type-safe compare to raw type List.

Example of using Unbounded Wildcard Type

If you also wonder how could the unbounded wildcard help, here is an example. Let's say we want to compare two devices lists to check if they are holding the same quantity.

public static boolean isContainSameQuantity(List<Device> list1, List<Device> list2) {
    return list1.size() == list2.size();
}

public static void main(String[] args) {
    List<Device> devices1 = new ArrayList<>();
    List<Device> devices2 = new ArrayList<>();

    isContainSameQuantity(devices1, devices2);
}

The codes above are valid and works fine. Now, how about if we want the comparison happen between list with different types?

List<Phone> phones = new ArrayList<>();
List<Camera> cameras = new ArrayList<>();
List<Device> devices = new ArrayList<>();

isContainSameQuantity(phones, cameras); // Compilation error! Incompatible type.
isContainSameQuantity(phones, devices); // Compilation error! Incompatible type.
isContainSameQuantity(devices, cameras); // Compilation error! Incompatible type.

Java Compiler will not let us to do so as it so concern about the type argument, Device that has been assigned to the method parameters of isContainSameQuantity(). In fact, the isContainSameQuantity() implementation does not deal with the object with its type is genericized in List generic type at all. Therefore, we can use unbounded wildcard type to loose the restriction to the max. See the following update version of isContainSameQuantity().

public static boolean isContainSameQuantity(List<?> list1, List<?> list2) {
    return list1.size() == list2.size();
}

public static void main(String[] args) {
    List<Phone> phones = new ArrayList<>();
    List<Camera> cameras = new ArrayList<>();
    List<Device> devices = new ArrayList<>();

    isContainSameQuantity(phones, cameras); // OK
    isContainSameQuantity(phones, devices); // OK
    isContainSameQuantity(devices, cameras); // OK
}

Summary

Wildcard type helps us to make our code type-safe yet flexible. It could be a hard time to pick up this tool but once you familiar with it, it will become a great tool in your hand. Below is the summary of when I should use which wildcard form. Hope it helps.

Upper-bound Wildcard Type <? extend ...>
  • All I need is to get a specific type of objects from a generic typed object and I never need to put an object into it.
Lower-bounded Wildcard Type <? super ...>
  • All I need is to put a specific type of objects into a generic typed object and I am aware that it will only return the object with the Object type. I take the risk to cast the return object to a specific class.
Unbounded Wildcard Type <?>
  • I will not deal with the object with its type is genericized in the generic typed object. And I am aware that it will only return the object with the Object type. I take the risk to cast the return object to a specific class.

References:
https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
Book: Effective Java 2nd Edition by Joshua Blosh - Item 28