Generics in Java


Introduction

Generics are a powerful feature in Java that allow you to write more flexible and reusable code while maintaining type safety. Generics enable classes, interfaces, and methods to operate on objects of various types while ensuring that errors are caught during compile time rather than runtime.

In this tutorial, we will explore the basics of generics in Java, including generic classes, methods, and bounded types, and show how they can help make your code more robust and flexible.

Step 1: Generic Classes

A generic class allows you to define a class that can operate on any type of object. You define a class with a type parameter, and the type is specified when you create an instance of the class.

          // Define a generic class
          public class Box<T> {
              private T value;
      
              public void setValue(T value) {
                  this.value = value;
              }
      
              public T getValue() {
                  return value;
              }
          }
      
          public class Main {
              public static void main(String[] args) {
                  // Create a Box for storing Integer
                  Box<Integer> integerBox = new Box<>();
                  integerBox.setValue(10);
                  System.out.println("Integer Value: " + integerBox.getValue());
      
                  // Create a Box for storing String
                  Box<String> stringBox = new Box<>();
                  stringBox.setValue("Hello Generics");
                  System.out.println("String Value: " + stringBox.getValue());
              }
          }
        

In this example:

  • The class Box<T> is a generic class with a type parameter T.
  • The method setValue accepts an argument of type T, and the method getValue returns a value of type T.
  • In the main method, we create instances of the Box class for different types (Integer and String) and use them accordingly.

Step 2: Generic Methods

You can also create generic methods that work with different types. The type parameter is specified before the return type of the method, allowing you to apply the method to any type.

          public class Main {
              // Generic method that returns the length of any type of array
              public static <T> int getArrayLength(T[] array) {
                  return array.length;
              }
      
              public static void main(String[] args) {
                  Integer[] intArray = {1, 2, 3, 4};
                  String[] strArray = {"Hello", "Generics"};
      
                  // Call the generic method with Integer array
                  System.out.println("Integer Array Length: " + getArrayLength(intArray));
      
                  // Call the generic method with String array
                  System.out.println("String Array Length: " + getArrayLength(strArray));
              }
          }
        

In this example:

  • The method getArrayLength is a generic method that works with any array type T[].
  • The type T is specified when calling the method, and the method returns the length of the array.

Step 3: Bounded Type Parameters

Java generics allow you to specify bounds for the type parameters. This means you can restrict the types that are allowed to be passed as arguments to a generic class or method.

          // Define a generic method with bounded type
          public class Main {
              public static <T extends Number> void printNumber(T number) {
                  System.out.println("Number: " + number);
              }
      
              public static void main(String[] args) {
                  // Call the generic method with different types of Number
                  printNumber(10);  // Integer
                  printNumber(10.5); // Double
              }
          }
        

In this example:

  • The method printNumber has a bounded type parameter T extends Number, meaning that T can only be a subtype of Number.
  • This ensures that only types like Integer, Double, and other subclasses of Number can be passed to the method.

Step 4: Wildcards in Generics

Wildcards are used to represent an unknown type in a generic class or method. There are three types of wildcards in Java generics: ?, ? extends T, and ? super T.

The wildcard ? represents any type, ? extends T is used for an upper-bounded wildcard (restricting to subtypes of T), and ? super T is used for a lower-bounded wildcard (restricting to superclasses of T).

          public class Main {
              // Method using wildcard
              public static void printList(java.util.List<? extends Number> list) {
                  for (Number num : list) {
                      System.out.println(num);
                  }
              }
      
              public static void main(String[] args) {
                  java.util.List<Integer> intList = java.util.Arrays.asList(1, 2, 3);
                  java.util.List<Double> doubleList = java.util.Arrays.asList(1.1, 2.2, 3.3);
      
                  // Call the method with different types of lists
                  printList(intList);
                  printList(doubleList);
              }
          }
        

In this example:

  • The method printList uses ? extends Number as the wildcard, allowing it to accept any list of type Number or its subclasses.
  • We call the method with lists of type Integer and Double as arguments.

Conclusion

In this tutorial, we learned the following about generics in Java:

  • We defined and used generic classes that can work with any type.
  • We created generic methods that accept and return different types.
  • We explored bounded type parameters to restrict the types that can be used with generics.
  • We learned how to use wildcards to represent unknown types in generic classes and methods.
Generics provide type safety, reduce redundancy, and improve code maintainability. They are a powerful tool in Java to create flexible and reusable code.





Advertisement