What Are Wildcards in Java Generics and How Do They Work?

What Are Wildcards in Java Generics and How Do They Work?

In Java, generics provide a way to write flexible, reusable, and type-safe code. However, there are situations where we may not know or care about the specific type of a class but still want to work with a certain type of class. This is where wildcards in generics come into play. Wildcards allow for more flexible code that can work with a range of types.

What Are Wildcards in Java Generics?

A wildcard in Java is a special symbol represented by the question mark (?) that can represent any type. It is used in generic types to specify that a parameter can accept any type of object, offering a higher level of flexibility. Wildcards are particularly useful when writing methods that work with generic types, and you don’t care about the exact type but just need to ensure that certain conditions are met.

Why Use Wildcards?

Wildcards offer flexibility when working with generics. Consider the following scenarios:

  • When you want to pass a generic object but don’t care about the type.
  • When you need to ensure that the objects passed are of a certain type or subtype, but the specific class doesn’t matter.
  • When you want to improve the code’s reusability while still maintaining type safety.

Types of Wildcards in Java Generics

In Java, wildcards can be classified into three types based on their usage and constraints:

1. Unbounded Wildcard (?)

The simplest wildcard is the unbounded wildcard, represented by ?. It can represent any type of object, and it is typically used when you want to accept any kind of object in a generic context without any specific constraint. However, this means you lose the ability to perform operations that require knowledge about the type of the object.

List list = new ArrayList<>();
        list.add(new String("Hello"));
        list.add(new Integer(10));

Note: You can’t add elements to a List because the type of elements is unknown.

2. Upper-Bounded Wildcard (? extends T)

The upper-bounded wildcard allows you to specify that the generic type must be a subtype of a particular type T. This is useful when you are working with a class hierarchy and want to restrict the types to subclasses of a given class.

public static void printList(List list) {
            for (Number num : list) {
                System.out.println(num);
            }
        }

        List intList = Arrays.asList(1, 2, 3);
        printList(intList);

Note: With the upper-bounded wildcard, you can read items from the list (since they are guaranteed to be of type Number or its subclasses) but cannot add items to the list.

3. Lower-Bounded Wildcard (? super T)

The lower-bounded wildcard is the opposite of the upper-bounded wildcard. It specifies that the type must be a superclass of a particular type T. This is typically used when you want to allow for any type that is a superclass of T so that you can add instances of T or its subclasses to a collection.

public static void addNumbers(List list) {
            list.add(10);
            list.add(20);
        }

        List numList = new ArrayList<>();
        addNumbers(numList);

Note: With the lower-bounded wildcard, you can add elements of type Integer or its subclasses but you can’t safely retrieve elements from the list.

Practical Examples of Wildcards in Java

Example 1: Using Unbounded Wildcard

The following example shows how the unbounded wildcard can be used when you don’t need to know the exact type of elements in the collection.

public static void printList(List list) {
            for (Object obj : list) {
                System.out.println(obj);
            }
        }

        List stringList = Arrays.asList("Hello", "World");
        printList(stringList);

        List intList = Arrays.asList(1, 2, 3);
        printList(intList);

In this example, the printList method accepts a list of any type, and it works for both a List and a List without issue.

Example 2: Using Upper-Bounded Wildcard

The following code demonstrates the use of an upper-bounded wildcard. We use it to print all types that are subclasses of Number:

public static void printNumbers(List list) {
            for (Number number : list) {
                System.out.println(number);
            }
        }

        List doubleList = Arrays.asList(1.1, 2.2, 3.3);
        printNumbers(doubleList);

Example 3: Using Lower-Bounded Wildcard

Now, let’s see how the lower-bounded wildcard can be used when we need to add elements to a collection.

public static void addToList(List list) {
            list.add(100);
            list.add(200);
        }

        List numberList = new ArrayList<>();
        addToList(numberList);

Here, addToList accepts a list of any class that is a superclass of Integer, such as Number or Object. This allows us to add Integer values to the list.

Key Benefits of Using Wildcards in Java

  • Flexibility: Wildcards allow you to write methods and classes that can handle a wide range of types, without the need for creating specific methods for every possible type.
  • Type Safety: Wildcards preserve type safety, ensuring that the correct types are used when working with collections.
  • Code Reusability: By using wildcards, you can write code that can be reused with different types, reducing redundancy and improving maintainability.

Conclusion

Wildcards in Java generics are a powerful tool for improving the flexibility and reusability of your code. Whether you’re working with unbounded, upper-bounded, or lower-bounded wildcards, understanding how they work can greatly enhance your ability to write clean, efficient, and type-safe code.

Please follow and like us:

Leave a Comment