January 24, 2023

Java Generics - Bounded Type Parameters

When you create a generic class or generic method the type parameters can be replaced by any class type but in some scenario you may want to restrict the types that can be used as type arguments in a parameterized type. That can be done using bounded type parameters in Java generics.

Why bounded type parameter needed in Java Generics

Let’s try to understand it with an example when you may need to use bounded parameters. For example, you have a generic class with a method that operates on numbers might only want to accept instances of Number or its subclasses.

First let’s see what happens if you don’t use bounded type parameters. As an example we’ll have a generic class with a method average() that returns the average of an array of numbers. You have defined a generic class so that you can pass array of any type integer, double, float.

public class BoundedType<T> {
  T[] numbers;
  BoundedType(T[] numbers){
    this.numbers = numbers;
  }

  public double average(){
    double sum = 0.0;
    for(int i = 0; i < numbers.length; i++){
      // Compile time error here
      sum += numbers[i].doubleValue();
    }
    double avg = sum/numbers.length;
    return avg;
  }
}

For calculating average suppose you have written a generic class as given above where you have used doubleValue() method to get number of type double for each number in the array. That should work well with any Number type as doubleValue() method is in Number class and it is the super class for all wrapper classes. But you will get compile time error at this line

sum += numbers[i].doubleValue();

Though your intention is to use this generic class always for numbers but there is no way for compiler to know that. For compiler BoundedType<T> means T can later be replaced by any type so there must be some mechanism for compiler to know that type parameter will be restricted to arguments of type Number. That’s where you use Bounded parameters in Java generics.

How to declare bounded type parameters

To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by a super class (upper bound)

<T extends parentclass>

This specifies that T can only be replaced by parentclass, or any child class of parentclass. So, parentclass acts as an upper bound here.

Bounded type parameter example

If we take the same example as above then you can use Number as the upper bound for the type parameter to get rid of the compile time error. With that compiler knows that the type used for the type parameter is going to be a Number or any of its subclass.

public class BoundedType<T extends Number> {
  T[] numbers;
  BoundedType(T[] numbers){
    this.numbers = numbers;
  }
  
  public double average(){
    double sum = 0.0;
    for(int i = 0; i < numbers.length; i++){
      // Compile time error here
      sum += numbers[i].doubleValue();
    }
    double avg = sum/numbers.length;
    return avg;
  }
  
  public static void main(String[] args) {
    Integer[] numArr = {3,4,5};
    BoundedType<Integer> obj = new BoundedType<Integer>(numArr);
    System.out.println("Average is: " + obj.average());
  }
}

Multiple Bounds in Java generics

Type parameter can have multiple bounds too.

<T extends A1 & A2 & A3>

A type variable with multiple bounds is a subtype of all the types listed in the bound. Note that in case of multiple bounds only one of the bounds can be a class others have to be interfaces. 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> { /* ... */ }

Bounded type parameters with generic methods in Java

In the above example bounded parameter is used at a class level but you can have generic methods with bounded type parameters too. Consider a scenario where you have a method to count the number of elements in an array greater than a specified element and you have written it as given below.

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

You get compiler error at this line-

if (e > element)

because the greater than operator (>) applies only to primitive types such as short, int, double, long, float, byte, and char. You cannot use the > operator to compare objects.

You will have to use a type parameter bounded by the Comparable<T> interface to compile the code.

public static <T extends Comparable<T>> int countElements(T[] numbers, T element) {
  int count = 0;
  for (T e : numbers)
    if (e.compareTo(element) > 0)  // compiler error
      ++count;
  return count;
}

That's all for the topic Java Generics - Bounded Type Parameters. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment