Java Generics

Generics in Java

Generics in Java enable types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. They provide a way to create classes, interfaces, and methods that operate on types specified by the user, promoting code reusability, type safety, and readability.

Key Concepts of Generics

Generic Classes

A generic class is a class that can operate on objects of various types while providing compile-time type safety.

  • Example:
Java
  public class Box<T> {
      private T t;

      public void set(T t) {
          this.t = t;
      }

      public T get() {
          return t;
      }

      public static void main(String[] args) {
          Box<Integer> integerBox = new Box<>();
          integerBox.set(10);
          System.out.println("Integer Value: " + integerBox.get());

          Box<String> stringBox = new Box<>();
          stringBox.set("Hello Generics");
          System.out.println("String Value: " + stringBox.get());
      }
  }

Generic Methods

A generic method is a method that can operate on different types. The type parameter is declared before the return type of the method.

  • Example:
Java
  public class GenericMethodExample {
      public static <T> void printArray(T[] array) {
          for (T element : array) {
              System.out.println(element);
          }
      }

      public static void main(String[] args) {
          Integer[] intArray = {1, 2, 3, 4, 5};
          String[] stringArray = {"One", "Two", "Three", "Four", "Five"};

          System.out.println("Integer Array:");
          printArray(intArray);

          System.out.println("String Array:");
          printArray(stringArray);
      }
  }

Bounded Type Parameters

Bounded type parameters restrict the types that can be used as type arguments. They ensure that the type argument implements a particular interface or extends a particular class.

  • Example:
Java
  public class BoundedTypeParameterExample {
      public static <T extends Number> double sum(T a, T b) {
          return a.doubleValue() + b.doubleValue();
      }

      public static void main(String[] args) {
          System.out.println("Sum of 5 and 10: " + sum(5, 10));
          System.out.println("Sum of 5.5 and 10.5: " + sum(5.5, 10.5));
      }
  }

Wildcards

Wildcards are special type arguments that enable flexibility when working with generics. They are represented by the question mark (?).

  • Unbounded Wildcard: ? – Represents any type.
  • Bounded Wildcard: ? extends T – Represents any type that is a subclass of T.
  • Lower Bounded Wildcard: ? super T – Represents any type that is a superclass of T.
  • Example:
Java
  import java.util.List;
  import java.util.ArrayList;

  public class WildcardExample {
      public static void printList(List<?> list) {
          for (Object element : list) {
              System.out.println(element);
          }
      }

      public static void main(String[] args) {
          List<Integer> intList = new ArrayList<>();
          intList.add(1);
          intList.add(2);
          intList.add(3);

          List<String> stringList = new ArrayList<>();
          stringList.add("One");
          stringList.add("Two");
          stringList.add("Three");

          System.out.println("Integer List:");
          printList(intList);

          System.out.println("String List:");
          printList(stringList);
      }
  }

Best Practices for Using Generics in Large Applications

  1. Use Generics to Enforce Type Safety: By using generics, you can catch type-related errors at compile-time, reducing runtime errors.
  2. Prefer Generics Over Raw Types: Avoid using raw types (e.g., List instead of List<T>) as they do not provide type safety.
  3. Use Bounded Type Parameters When Necessary: Use bounded type parameters to restrict the types that can be used as type arguments, ensuring that the types meet certain criteria.
  4. Use Wildcards for Flexibility: Wildcards provide flexibility in method parameters and return types. Use them when you need to handle a variety of types.
  5. Avoid Casting: Generics reduce the need for explicit casting. Use them to eliminate casts and make your code more readable and maintainable.
  6. Document Type Parameters: Clearly document the type parameters in your generic classes and methods to improve code readability and maintainability.
  7. Prefer Interfaces Over Concrete Types: When defining generic types, prefer using interfaces (e.g., List<T>) rather than concrete types (e.g., ArrayList<T>).
  8. Combine Generics with Other OOP Concepts: Use generics in combination with other OOP concepts like inheritance and polymorphism to create more flexible and reusable code.

Practical Examples

Generic Collections

Java Collections Framework provides several generic classes like ArrayList, HashMap, etc., which can be used to store and manipulate collections of objects in a type-safe manner.

  • Example:
Java
  import java.util.ArrayList;
  import java.util.List;

  public class GenericCollectionsExample {
      public static void main(String[] args) {
          List<String> stringList = new ArrayList<>();
          stringList.add("One");
          stringList.add("Two");
          stringList.add("Three");

          for (String s : stringList) {
              System.out.println(s);
          }

          List<Integer> intList = new ArrayList<>();
          intList.add(1);
          intList.add(2);
          intList.add(3);

          for (Integer i : intList) {
              System.out.println(i);
          }
      }
  }

Generic Interfaces

A generic interface can be used to define a contract that can be implemented by different classes in a type-safe manner.

  • Example:
Java
  interface Pair<K, V> {
      K getKey();
      V getValue();
  }

  class OrderedPair<K, V> implements Pair<K, V> {
      private K key;
      private V value;

      public OrderedPair(K key, V value) {
          this.key = key;
          this.value = value;
      }

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

  public class GenericInterfaceExample {
      public static void main(String[] args) {
          Pair<String, Integer> pair = new OrderedPair<>("One", 1);
          System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());
      }
  }

Summary

Generics in Java provide a powerful mechanism to create reusable, type-safe, and maintainable code. By understanding and effectively using generic classes, methods, bounded type parameters, and wildcards, you can write more flexible and robust applications. Following best practices ensures that your use of generics enhances the quality and readability of your code, especially in large-scale applications.

Scroll to Top