Bounded Wildcards

Java-Bounded Wildcards

Bounded Wildcards

A bounded wildcard is a wildcard that uses the extends keyword just as a type variable would to limit the range of assignable types. For example :

Bounded Wildcards

Our ‘abc’ variable is limited to holding instantiations of Gen on parameter types of A and its subclasses(B and C). So, we can assign it a Gen<A> or a Gen<B> or a Gen<C>. The unbounded wildcard instantiation is a kind of supertype of all of these concrete instantiations :

Bounded Wildcards

In the same way that the unbounded wildcard serves as a superclass for all instantiations of a generic type, bounded wildcards create more limited supertypes covering a narrower range of instantiations. In this case, our wildcard instantiation, Gen<? extends A>, is the supertype of all instantiations of Gen on A types. As with type parameter bounds, the bound A is called the upper bound of the type.

Bounded Wildcards

Wildcard arguments can be bounded in much the same way that a type parameter can be bounded. A bounded wildcard is especially important when you are creating a generic type that will operate on a class hierarchy. To understand why, let’s work through an example. Consider the hierarchy of classes : A,B,C. At the top of the hierarchy is A, which encapsulates a data ‘a’ A is inherited by B, which creating an ‘ab’ data. B is inherited by C, which creating an ‘abc’ data. Shown next is a generic class called Gen, which stores an array of A :

Bounded Wildcards

Notice that Gen specifies a type parameter bounded by A. This means that any array stored in a Gen object will contain objects of type A or one of its subclasses. Now, assume that you want to write a method that displays the value of ‘a’ for each element in the ‘t’ array of a Gen object. Because all types of Gen objects have at least one value (‘a’), this is easy to do using a wildcard :

Bounded Wildcards

Because Gen is a bounded generic type that specifies A as an upper bound, all objects that can be used to create a Gen object will be arrays of type A, or of classes derived from A(B,C). Thus, show_All_a( ) can display the contents of any Gen object. However, what if you want to create a method that displays the ‘a’ and ‘b’ values of a B or C object? The trouble is that not all Gen objects will have two values, because a Gen<A> object will only have ‘a’. Therefore, how do you write a method that displays the ‘a’ and ‘b’ values for Gen<B> and Gen<C> objects, while preventing that method from being used with Gen<A> objects? The answer is the bounded wildcard argument.

A bounded wildcard enables you to restrict the types of objects upon which a method will operate. The most common bounded wildcard is the upper bound, which is created using an extends clause in much the same way it is used to create a bounded type. Using a bounded wildcard, it is easy to create a method that displays the ‘a’  and ‘b’ values of a Gen object, if that object actually has those two values. For example, the following show_BC_ab( ) method shows the ‘a’ and ‘b’ values of the elements stored in a Gen object, if those elements are actually of type B or its subclasses of B(C) :

Bounded Wildcards

The extends clause has been added to the wildcard in the declaration of parameter ‘ge’. It states that the ? can match any type as long as it is B, or a class derived from B. Thus, the extends clause establishes an upper bound that the ? can match. Because of this bound, show_BC_ab( ) can be called with references to objects of type Gen<B> or Gen<C>, but not with a reference of type Gen<A>. Attempting to call show_BC_ab( )  with a Gen<A>  reference results in a compile-time error, thus ensuring type safety.

Program

Bounded Wildcards Bounded Wildcards

Program Source

class A {

    int a;

    A(int aa) {
        a = aa;
    }
}

class B extends A {

    int b;

    B(int aa, int bb) {
        super(aa);
        b = bb;
    }
}

class C extends B {

    int c;

    C(int aa, int bb, int cc) {
        super(aa, bb);
        c = cc;
    }
}

class Gen<T extends A> {

    T t[];

    Gen(T tt[]) {
        t = tt;
    }
}

public class Javaapp {

    static void show_All_a(Gen<?> ge) {

        System.out.println(".........show_All_a.........");

        for (int i = 0; i < ge.t.length; i++) {             
            System.out.println("a-> : " + ge.t[i].a);
        }
    }

    static void show_BC_ab(Gen<? extends B> ge) {

        System.out.println(".........show_BC_ab.........");

        for (int i = 0; i < ge.t.length; i++) {             
            System.out.println("a-> : " + ge.t[i].a + " b-> : " + ge.t[i].b);
        }
    }

    static void show_C_abc(Gen<? extends C> ge) {

        System.out.println(".........show_C_abc.........");

        for (int i = 0; i < ge.t.length; i++) {             
            System.out.println("a-> : " + ge.t[i].a + " b-> : " + ge.t[i].b + " c-> : " + ge.t[i].c);
        }
    }

    public static void main(String[] args) {

        Gen<A> a = new Gen<A>(new A[]{new A(30), new A(60), new A(90)});
        Gen<B> b = new Gen<B>(new B[]{new B(15, 30), new B(45, 60), new B(75, 90)});
        Gen<C> c = new Gen<C>(new C[]{new C(10, 20, 30), new C(40, 50, 60), new C(70, 80, 90)});

        show_All_a(a);
        show_All_a(b);
        show_All_a(c);

        show_BC_ab(b);
        show_BC_ab(c);

        show_C_abc(c);
    }
}

Leave a Comment