Showing posts with label Java generics. Show all posts
Showing posts with label Java generics. Show all posts

Monday, March 4, 2024

Generic Class, Interface And Generic Method in Java

In the post Generics in Java basics of Java generics are already covered. In this post we’ll see how to create generic class, generic method and generic interface in Java.

Generic class in Java

A generic class is defined with the following format:

class name<T1, T2, ..., Tn> { 
  /* ... */ 
} 

The type parameter section, delimited by angle brackets (<>), follows the class name. It specifies the type parameters (also called type variables) T1, T2, ..., and Tn.

Java Generic class example

Let us create a generic class with two type parameters and see it in use with different data types.

class GenClass<K, V>{
  private K key;
  private V value;
  public GenClass(K key, V value) {
    this.key = key;
    this.value = value;
  }
  public K getKey(){
    return key;
  }
  public V getValue(){
    return value;
  }
}

public class GClassDemo {
  public static void main(String[] args) {
    GenClass<String, String> g1 = new GenClass<>("A", "Value A");
    System.out.println("Key- " + g1.getKey());
    System.out.println("Value- " + g1.getValue());
    
    GenClass<Integer, String> g2 = new GenClass<>(1, "Value 1");
    System.out.println("Key- " + g2.getKey());
    System.out.println("Value- " + g2.getValue());
  }
}

Output

Key- A
Value- Value A
Key- 1
Value- Value 1

Here you can see that first time String is passed as the type for both generic types K and V, where as it is passed as Integer and String second time.

Generic Interface in Java

You can create a generic interface much the same way as a generic class but there are some conditions while implementing the generic interface.

Java Generic interface example

public interface GInterface<E> {
  void setValue(E e);
  E getValue();
}

Class implementing Generic interface

import org.netjs.examples.interfaces.GInterface;

public class GClassImpl<E> implements GInterface<E> {
  E e;
  @Override
  public void setValue(E e) {
    this.e = e;   
  }

  @Override
  public E getValue() {
    return e;
  }
}

Points to note here

  1. Here note that a class that implements a generic interface has to be a generic class.
    public class GClassImpl<E> implements Ginterface<E>
    

    If implementing class of the generic interface is not a generic class that will result in compile time error because the type parameter E is not known in that case.

    public class GClassImpl implements Ginterface<E>
    
    This will result in compile-time error.
  2. Of course providing a proper data type with the interface while implementing it is OK, in that case normal class can be used.
    public class GClassImpl implements GInterface<String> {
      String str;
      @Override
      public void setValue(String e) {
        this.str = e;  
      }
    
      @Override
      public String getValue() {
        return str;
      }
    }
    
    Here you have used String as a type parameter with the interface so it is OK to use a normal class but type will become String in the class then.
  3. A generic class implementing a generic interface can have other parameters too. This is perfectly ok-
    public class GClassImpl<E, K> implements Ginterface<E>
    

Generic method in Java

Any method in the generic class can use the type parameter of the class so that way methods in a generic class are generic. Apart from that keep in mind the following points.

  • Generic methods can add more parameters of their own.
  • There can be generic methods even in a non-generic class.

When you are writing a generic method after the access modifier you need to declare the type parameters then the return type. For example if you are writing a public method that uses one type parameter and doesn’t return anything then it will be written as-

 
public <T> void MethodName(T obj1){

} 

Java Generics - generic method example

If you want to write a generic method that can be used to display elements of an array of any type.

 
public class GenericMethodDemo {  
  public static void main(String[] args) {
    GenericMethodDemo gm = new GenericMethodDemo();
    Integer[] intArray = {1, 2, 3, 4, 5, 6, 7};
    Double[] doubleArray = {3.4, 5.6, 7.8, 1.2, 4.5};
    // integer array
    gm.printElements(intArray);
    // double array
    gm.printElements(doubleArray);
  }
    
  public <T> void printElements(T[] arr){
    // Displaying elements
    for(int i = 0; i < arr.length; i++){
      System.out.print(" " + arr[i]);
    }
    System.out.println();
  }
}

Output

 
1 2 3 4 5 6 7
3.4 5.6 7.8 1.2 4.5

When you are calling a generic method there is no need to specify type (though you can do it if you want). Type will be inferred automatically based on the type of the method arguments. So calling your method using this form gm.<Integer>printElements(intArray); for Integer argument is also OK but it is not needed at all.

That's all for this topic Generic Class, Interface And Generic Method in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Bounded Type Parameter in Java Generics
  2. Wildcard in Java Generics
  3. Type Erasure in Java Generics
  4. AutoBoxing And UnBoxing in Java
  5. Varargs (Variable-length Arguments) in Java

You may also like-

  1. Interface Default Methods in Java
  2. Java join() Method - Joining Strings
  3. Spliterator in Java
  4. Difference Between HashMap And ConcurrentHashMap in Java
  5. Lock Striping in Java Concurrency
  6. finalize() Method in Java
  7. static Import in Java With Examples
  8. Try-With-Resources in Java With Examples

Thursday, November 17, 2022

Type Erasure in Java Generics

When generics was introduced in Java there was a requirement for it to be compatible with the existing code, written in previous versions, which of course was non-generic. That is why you can still add raw types, as example- List alist = new ArrayList();
This will give you warning for raw type but you can still have a non-generic list like this. Another thing that happens internally for compatibility is to replace all type parameters which is known as type erasure in Java generics


How type erasure works in Java

As mentioned above, during the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.

The Java compiler applies type erasure in generics to-

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. Which means, if you have a unbounded type parameter that will be replaced by Object while compiling the code. If there is a bounded parameter that will be replaced by the provided bound.
    The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.

As example if you have the following generic class

 
public class GenType<T> {
  T obj;
  public T getObj() {
    return obj;
  }

  public void setObj(T obj) {
    this.obj = obj;
  }  
}

here type parameter T is unbounded, thus the Java compiler replaces it with Object, so your compiled class won’t have any type parameter

public class GenType {
  Object obj;
  public Object getObj() {
    return obj;
  }

  public void setObj(Object obj) {
    this.obj = obj;
  }
}

If your class were using a bounded parameter as below-

public class GenType<T extends Number> {
  T obj;
  public T getObj() {
    return obj;
  }

  public void setObj(T obj) {
    this.obj = obj;
  }   
}

Here it will be replaced by the bound i.e. Number

public class GenType {
  Number obj;
  public Number getObj() {
    return obj;
  }

  public void setObj(Number obj) {
    this.obj = obj;
  }  
}

Bridge methods in Java

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process. In order to get an idea what bridge methods are let’s see an example.

If we have the following two classes-

GenType

public class GenType<T> {
  T obj;
  public GenType(T obj) { 
    this.obj = obj; 
  }

  public void setObj(T obj) {
    this.obj = obj;
  }  
}

MyGen

 
public class MyGen extends GenType<Integer> {
  public MyGen(Integer num) { 
    super(num); 
  } 
  public void setObj(Integer data) {
    System.out.println(" In MyGen.setData");
    super.setObj(data);
  } 
}

Here note that MyGen class extends the GenType class which has the Integer parameter. After compilation and type erasure, type parameters will be removed from both the classes and these classes will look as follows-

 
public class GenType {
  Object obj;
  public GenType(Object obj) { 
    this.obj = obj; 
  }

  public void setObj(Object obj) {
    this.obj = obj;
  }
}
 
public class MyGen extends GenType {
  public MyGen(Integer num) { 
    super(num); 
  } 
  public void setObj(Integer num) {
    System.out.println(" In MyGen.setData");
    super.setObj(num);
  }
}

If you have noticed, after type erasure setObj() method signatures don’t match in GenType and MyGen classes. In GenType class it becomes setObj(Object obj) where as in MyGen it becomes setObj(Integer num).

Therefore, the MyGen setObj method does not override the GenType setObj method.

To solve this problem and preserve the polymorphism of generic types after type erasure, a Java compiler generates a bridge method to ensure that subtyping works as expected. For the MyGen class, the compiler generates the following bridge method for setObj:

 
public class MyGen extends GenType {
  public MyGen(Integer num) { 
    super(num); 
  } 

  // Bridge method generated by the compiler
  public void setObj(Object num) {
    setObj((Integer) num);
  }
  public void setObj(Integer num) {
    System.out.println(" In MyGen.setData");
    super.setObj(num);
  }
}

Here you can see a bridge method which has the same signature as the setObj method of the GenType class is inserted and it delegates to the actual setObj method.

Generics Ambiguity errors because of type erasure

Let’s say you have the following class where you are trying to have two overloaded methods (set) with different type parameters K and V respectively.

 
class GenClass<K, V>{
  private K key;
  private V value;
  public GenClass(K key, V value) {
    this.key = key;
    this.value = value;
  }

  public void set(K key){
    this.key = key;
  }

  public void set(V value){
    this.value = value;
  }

  public K getKey(){
    return key;
  }
  public V getValue(){
    return value;
  }
}

This class will give compilation error
Erasure of method set(K) is the same as another method in type GenClass<K,V>
Erasure of method set(V) is the same as another method in type GenClass<K,V>

Though you may think since K and V are two different parameters so its ok to write overloaded methods using these parameters as arguments. But there is no such compulsion that these two parameters will be different, at the time of creating objects you can provide same type for both the type parameters.

Also type erasure will replace the type parameter with Object in this case so both the methods will become-

 
public void set(Object key){
 this.key = key;
}
 
public void set(Object value){
 this.value = value;
}

In a case like this it is better to give different name for the methods and avoid any ambiguity.

That's all for this topic Type Erasure in Java Generics. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Generics in Java
  2. Bounded Type Parameter in Java Generics
  3. Wildcard in Java Generics
  4. Enum Type in Java
  5. Spliterator in Java

You may also like-

  1. Varargs (Variable-length Arguments) in Java
  2. Java Stream API Tutorial
  3. Effectively Final in Java 8
  4. Java ThreadLocal Class With Examples
  5. Is String Thread Safe in Java
  6. Java ReentrantLock With Examples
  7. Java Collections Interview Questions And Answers
  8. Dependency Injection in Spring Framework

Tuesday, October 11, 2022

Generics in Java

There have been many new feature additions in Java over the year, with introduction of lambda expressions in Java 8 it has even moved toward functional programming. But long before the lambda expressions and stream API there was one change which had a very big impact on the way you program and that was generics which was added in Java 5.

Generics in Java not only added a new way to declare classes, methods or interface but it also changed many of the classes in the API, Java collections API is an example. Initially it took some time getting used to the parameters like T, K or V with the classes or methods but now its quite a familiar thing and very much a part of your daily programming.

Java Generics

Generics in Java enable you to specify type parameters when defining classes, interfaces and methods. Here type parameter is the type of data (class or interface) which these classes, interfaces and methods will operate upon.

As example, when you say List<T> that means a list which will store elements of type T. Since you have specified a type parameter so this List class is generic and T is the type parameter which specifies the type of element stored in the List.

Generic class format in Java

A generic class is defined using the following format:

class name<T1, T2, ..., Tn> { 
  /* ... */ 
}

The type parameter section, delimited by angle brackets (<>), follows the class name. It specifies the type parameters (also called type variables) T1, T2, ..., and Tn.

Java Generics - Type Parameter Naming Conventions

By convention, generic type parameter names are single, upper case letters. That helps n distinguishing between type parameters and normal variables.

The most commonly used type parameter names are:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

Benefits of Generics in Java

You might be thinking there is already an Object class which provides almost the same thing, as Object is super class of all the other classes so Object class can be used to make a class generic. Though that can be done but it may cause some unseen problems because there is no type safety.

Let’s say you have a List where you intend to store strings and you have not made it generic that means all its elements will be stored as objects of type Object-

List strList = new ArrayList();
strList.add("a");
strList.add("b");
strList.add(new Integer(4));
Iterator itr = strList.iterator();
while(itr.hasNext()){
 String str = (String)itr.next();
 System.out.println("" + str);
}

Since elements are stored as class Object type so there won’t be any compile time error if you store an integer too (as done in this line strList.add(new Integer(4));), even if you intended to store Strings.

Since the elements are stored as class Object type so you need to cast those elements to String while retrieving. At that time you will get a run time exception when the code will try to cast Integer to String.

So the code snippet shown above will throw run time exception-
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

That brings to the fore couple of benefits of using generics in Java-

  • Stronger type checks at compile time- In the above code if you use generics and specify the parameterized type of List as String you will get compile time error if an attempt is made to add element of any other type to the list. A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.

    As example-

    List<String> strList = new ArrayList<String>();
    strList.add("a");
    strList.add("b");
    strList.add(new Integer(4));
    

    With generics this code will throw following compile-time error if you try to add Integer to the list
    The method add(String) in the type List<String> is not applicable for the arguments (Integer)

  • Casting is not required- Another benefit you get with generics in Java is that cast is not required. Since you already specify the type with generics so explicit cast is not required.

    As example-

    List<String> strList = new ArrayList<String>();
    strList.add("a");
    strList.add("b");
    strList.add("c");
    
    Iterator<String> itr = strList.iterator();
    while(itr.hasNext()){
      String str = itr.next();
      System.out.println("" + str);
    }
    

    Here you see casting is not required when retrieving elements from the list.

  • Enabling programmers to implement generic algorithms– In many algorithms, logic remains same, irrespective of the data they are used with. On the other hand, generics make it possible to write classes, methods and interface that work with different kind of data that too in a type safe manner. Now combine these two facts and the result you can write generic algorithms that work with any type of data.

    Let’s take a simple example to see how to create a generic class with parameterized type. While creating an object of the class you can specify the type, which in generics terminology is called generic type invocation, which replaces T with some concrete value.

    Generic class

    public class GenType<T> {
      T obj;
    
      public T getObj() {
        return obj;
      }
    
      public void setObj(T obj) {
        this.obj = obj;
      } 
    }
    

    Using Generic class

    public class GenericDemo {
      public static void main(String[] args) {
        // With Integer type
        GenType<Integer> genInt = new GenType<Integer>();
        genInt.setObj(21);
        int value = genInt.getObj();
        System.out.println("integer value " + value);
        
        // With String type
        GenType<String> genStr = new GenType<String>();
        genStr.setObj("Generic class test");
        String str = genStr.getObj();
        System.out.println("String value " + str);
        
        // With Double type
        GenType<Double> genDouble = new GenType<Double>();
        genDouble.setObj(34.56);
        double dblValue = genDouble.getObj();
        System.out.println("Double value " + dblValue);
      }
    }
    

    Output

    integer value 21
    String value Generic class test
    Double value 34.56
    

    Here you see that the same generic class is first invoked with Integer type, then String and then Double. And you get the added benefit of type safety and no chance of run time exception (which is a probability if you had used an Object class to make the class generic).

Points to remember

  1. Generics in Java provide type safety as there are stronger type checks at compile-time.
  2. Generics don’t work with primitive types, type arguments passed to the type parameters must be references.
  3. You can have generic classes, generic methods and generic interfaces.
  4. Type parameters are erased when generic classes are compiled.
  5. A generic class with type parameters can’t have static members using the type parameter. As example -
    public class GenType<T> {
      static T obj;
      .....
      .....
    }
    
    This code will have compile-time error - Cannot make a static reference to the non-static type T.
  6. In Java 7 and later you can replace the type arguments required to invoke the constructor of a generic class with an empty set of type arguments (<>) as long as the compiler can determine, or infer, the type arguments from the context. This pair of angle brackets, <>, is informally called the diamond.
    As example- List<Integer> ls = new ArrayList<>();

    You don’t need to write <Integer> again, empty set of type argument (<>) will do, type will be inferred automatically.

That's all for this topic Generics in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Generic Class, Interface And Generic Method in Java
  2. Bounded Type Parameter in Java Generics
  3. Wildcard in Java Generics
  4. Type Erasure in Java Generics
  5. Covariant Return Type in Java

You may also like-

  1. Interface Default Methods in Java
  2. Effectively Final in Java 8
  3. Method Reference in Java
  4. Java ThreadLocal Class With Examples
  5. Is String Thread Safe in Java
  6. Dependency Injection using factory-method in Spring
  7. strictfp in Java
  8. Polymorphism in Java

Monday, May 3, 2021

Bounded Type Parameter in Java Generics

In the post generics in Java you would have already seen examples where type parameters are replaced by any class type. But there are times when you want to restrict the types that can be used as type arguments in a parameterized type. That can be done using bounded type parameter in Java Generics.

As example if you have a generic class with a method that operates on numbers, you would want to restrict it to accept instances of Number or its subclasses only.

Let’s first see an example where you don’t bind type parameters to analyse what happens in that case-

You have a generic class Test with a method average which returns the average of the numbers in the array passed to it. Since the class is generic so you intend to pass array of any type integer, double, float. Here return type of method average is double as you want an accurate value. Since Number class (Super class of all numeric classes) has doubleValue() method so you can always get the double value out of any type of number.

public class Test<T> {
  T[] numArr;
  Test(T[] numArr){
    this.numArr = numArr;
  }
  public double getAvg(){
    double sum = 0.0;
    for(int i = 0; i < numArr.length; i++){
      sum += numArr[i].doubleValue();
    }
    double avg = sum/numArr.length;
    return avg;
  }
}

This code will give you compile-time error-

The method doubleValue() is undefined for the type T

You get this error as there is no way for compiler to know type T will always be used for numeric classes. You need to let the compiler know that type T will always be Number and doubleValue() method can be called safely. That’s when you need bounded type to restrict the types that can be used for parameterized type. In the above case that restriction is; the type should be Number.

Bounded type in Java generics

In order to create a bounded type you need to provide an upper bound which acts as a restriction for types. As this upper bound is a superclass, the type that can be used has to be a subclass of that upper bound.

General form of bounded type parameter

To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound.

T extends superclass

In the example used above that upper bound has to be the Number class as Number class is the super class of all the numeric classes. Thus in that case your bounded type will be - T extends Number

Java Generics - Bounded type parameter example

public class Test<T extends Number> {
  T[] numArr;
  Test(T[] numArr){
    this.numArr = numArr;
  }
  public double getAvg(){
    double sum = 0.0;
    for(int i = 0; i < numArr.length; i++){
      sum += numArr[i].doubleValue();
    }
    double avg = sum/numArr.length;
    return avg;
  }
}

Now you won’t get compile-time error as you have provided the Number class as upper bound for your generic type T. Which means any type passed for the generic type T has to be the sub class of class Number. Since doubleValue() method is in Number class it will be part of any sub class of Number through inheritance. So no compile-time error!

Multiple Bounds in Java generics

A type parameter can have multiple bounds:

<T extends B1 & B2 & B3>

A type variable with multiple bounds is a subtype of all the types listed in the bound. If one of the bounds is a class, it must be specified first. For example:

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }

Not specifying bounds in this order will result in compile-time error.

Generic Methods and Bounded Type Parameters

You can also use bounded types with generic methods. Let’s see an example where it becomes necessary to use bounded types. Consider a method where you want to count the number of elements in an array greater than a specified element elem.

public static <T> int countGreaterThan(T[] anArray, T elem) {
  int count = 0;
  for (T e : anArray)
    if (e > elem)  // compiler error
      ++count;
  return count;
}

This method will result in compile-time error as greater than (>) operator can be used only with primitive types such as short, int, double, long, float, byte, and char. It can’t be used to compare objects, you have to use types that implement Comparable interface in order to compare objects. Thus, Comparable interface becomes the upper bound in this case.

Code with upper bound

public class Test{
  public <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray){
      if (e.compareTo(elem) > 0) {
        ++count;
      }
    }
    return count;
  }
}

You can use the following code to run it-

Test test = new Test();
Integer[] numArr = {5, 6, 7, 1, 2};
int count = test.countGreaterThan(numArr, 5);
System.out.println("count - " + count);

Output

count – 2

Reference- https://docs.oracle.com/javase/tutorial/java/generics/boundedTypeParams.html

That's all for this topic Bounded Type Parameter in Java Generics. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Generic Class, Interface And Generic Method in Java
  2. Type Erasure in Java Generics
  3. Covariant Return Type in Java
  4. Lambda Expressions in Java 8
  5. Spliterator in Java

You may also like-

  1. How HashMap Works Internally in Java
  2. How HashSet Works Internally in Java
  3. Java ReentrantReadWriteLock With Examples
  4. Java ArrayBlockingQueue With Examples
  5. Executor And ExecutorService in Java With Examples
  6. Association, Aggregation and Composition in Java
  7. Marker Interface in Java
  8. Best Practices For Exception Handling in Java

Sunday, May 2, 2021

Wildcards in Java Generics

In Java generics there is also an option of giving question mark (?) as type which is called wildcard in Java generics. This question mark (?) represents an unknown type.

The wildcard can be used in a variety of situations-

  • As the type of a parameter, field, or local variable;
  • Sometimes as a return type (though it is better programming practice to be more specific).

Types of wildcards in Java

Wildcards in Java generics can be classified into three types-

  1. Upper bounded wildcards
  2. Lower bounded wildcards
  3. Unbounded wildcards

Upper bounded wildcards in Java generics

The way we can have bounded parameters in generics same way we can have bounded wildcards too. So let’s see with an example when we should have upper bounded wildcards in Java generics.

Suppose you want to write a method that can take list of any type of Number which means a method that works on lists of Number and the subtypes of Number, such as Integer, Double, and Float i.e. List<Integer>, List<Double>, and List<Number>.

Here note one thing, though Integer is a subtype of Number, List<Integer> is not a subtype of List<Number> and, in fact, these two types are not related. Same applies for Double and Float too. So you can’t have your method as follows-

public static  double sumOfElements(List<Number> list){
  double s = 0.0;
  for (Number n : list)
    s += n.doubleValue();
  return s;
}

If you try to write your method as above you can’t call it with a List with integers because of the reason stated above.

List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfElements(li));

Trying to call the sumOfElements method with argument List<Integer> will result in compile-time error

The method sumOfElements(List<Number>) in the type WildCardDemo is not applicable for the arguments (List<Integer>)

In the case like this where you want your method to be generic enough to be able to work with a list of type Number or any of its subclasses. you can achieve this by using an upper bounded wildcard.

General form of upper bounded wildcard

To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound.

<? extends UpperBoundType>

Upper bounded wildcard Java example

To write the above mentioned method sumOfElements so that it works with Number or any of its subtype you need to provide a wild card and Number as the upper bound i.e. <? extends Number>. This will match Number or any of its subtype.

import java.util.Arrays;
import java.util.List;

public class WildCardDemo {
  public static void main(String[] args) {
    // With List<Integer>
    List<Integer> li = Arrays.asList(5, 6, 7);
    System.out.println("sum = " + sumOfElements(li));
    // With List<Double>
    List<Double> ld = Arrays.asList(1.2, 3.8, 8.2);
    System.out.println("sum = " + sumOfElements(ld));
  }
    
  public static double sumOfElements(List<? extends Number> list){
    double s = 0.0;
    for (Number n : list)
      s += n.doubleValue();
    return s;
  }
}

Output

sum = 18.0
sum = 13.2

Lower Bounded Wildcards in Java Generics

The way Upper bounded wildcard restricts the unknown type to be a specific type or a subtype of that type same way lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.

General form of the lower bounded wildcard

A lower bounded wildcard is expressed using the wildcard character ('?'), following by the super keyword, followed by its lower bound:

<? super bounded_type>

Lower Bounded Wildcard Java Example

Suppose you want to write a method that put elements of type Integer into a List. If you want to make sure that method works for List<Integer>, List<Number>, and List<Object> — anything that can hold Integer values. Here note that Number and Object are super type of Integer.

That generic method can be written as follows by using the lower bounded wildcard where lower bound is Integer.

public static void addNumbers(List<? super Integer> list) {
  for (int i = 1; i <= 10; i++) {
    list.add(i);
  }
}

You can invoke this method using the following code-

// Using object
List<Object> lo = new ArrayList<Object>();
addNumbers(lo);
// Using Integer
List<Integer> li = new ArrayList<Integer>();
addNumbers(li);

Note- You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both.

Unbounded wildcards in Java generics

Unbounded wildcards are, as the name suggests, wild cards with out any upper or lower bound. Unbounded wildcard type is specified using the wildcard character (?), for example, List<?>.This is called a list of unknown type.

There are two scenarios where an unbounded wildcard is a useful approach-

  • If you are writing a method that can be implemented using functionality provided in the Object class.
  • When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear.

Java unbounded wildcard example

Suppose you want to write a method that can print elements of a List of any type. If you write that method using Object as type for the List, you will get an error when you pass it a List of integers.

public static void printElements(List<Object> list){
  for (Object elem : list){
    System.out.println(elem + " ");
  }
  System.out.println();
}

If you try to invoke this method using the following lines-

List<Integer> li = Arrays.asList(5, 6, 7);
printElements(li);

You will get compile-time error-

The method printElements(List<Object>) in the type WildCardDemo is not applicable for the arguments (List<Integer>)

You get this error because the above method prints only a list of Object instances; it cannot print List<Integer>, List<String>, List<Double>, and so on, because they are not subtypes of List<Object>. To write a generic printElements method, use List<?>:

import java.util.Arrays;
import java.util.List;

public class WildCardDemo {
  public static void main(String[] args) {
    // With List<Integer>
    List<Integer> li = Arrays.asList(5, 6, 7);
    printElements(li);
    // With List<Double>
    List<Double> ld = Arrays.asList(1.2, 3.8, 8.2);
    printElements(ld);
  }
    
  public static void printElements(List<?> list){
    for (Object elem : list){
      System.out.print(elem + " ");
    }
    System.out.println();
  }
}

Output

5 6 7 
1.2 3.8 8.2

Reference: https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html

That's all for this topic Wildcard in Java Generics. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Generic Class, Interface And Generic Method in Java
  2. Bounded Type Parameter in Java Generics
  3. Type Erasure in Java Generics
  4. @FunctionalInterface Annotation in Java
  5. Core Java Basics Interview Questions And Answers

You may also like-

  1. Parallel Stream in Java Stream API
  2. Reduction Operations in Java Stream API
  3. Java Multithreading Interview Questions And Answers
  4. Marker Interface in Java
  5. Difference Between Encapsulation and Abstraction in Java
  6. Difference Between Checked And Unchecked Exceptions in Java
  7. Java Exception Handling And Method Overriding
  8. Configuring DataSource in Spring Framework