Generics in Java allow for type-safe code by enabling classes, methods, and interfaces to operate on objects of various types while providing compile-time type checking. They offer a powerful way to write reusable and flexible code. This article will cover the usage of generic classes, methods, and interfaces in advanced Java with examples.
A generic class allows a class to operate on objects of any type. The type parameter is defined using angle brackets
<T>
where T
can be any valid type.
Example: Generic class that stores a single object
// Generic class definition class Box<T> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; } } public class GenericClassExample { public static void main(String[] args) { // Using the generic class with Integer type Box<Integer> intBox = new Box<>(); intBox.setValue(10); System.out.println("Integer value: " + intBox.getValue()); // Using the generic class with String type Box<String> strBox = new Box<>(); strBox.setValue("Hello, Generics!"); System.out.println("String value: " + strBox.getValue()); } }
A generic method is a method that can accept parameters of various types. The type parameter is specified in the method declaration before the return type.
Example: Generic method that swaps two elements
// Generic method definition public class GenericMethodExample { public static <T> void swap(T[] array, int i, int j) { T temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4}; System.out.println("Before swap: " + intArray[0] + ", " + intArray[1]); swap(intArray, 0, 1); System.out.println("After swap: " + intArray[0] + ", " + intArray[1]); String[] strArray = {"A", "B", "C"}; System.out.println("Before swap: " + strArray[0] + ", " + strArray[1]); swap(strArray, 0, 1); System.out.println("After swap: " + strArray[0] + ", " + strArray[1]); } }
A generic interface is an interface that uses generics, allowing the methods within the interface to be type-safe and flexible. You can define a generic interface and then implement it with various types.
Example: Generic interface that defines a method to print a value
// Generic interface definition interface Printer<T> { void print(T value); } // Implementing the generic interface with Integer type class IntegerPrinter implements Printer<Integer> { public void print(Integer value) { System.out.println("Integer value: " + value); } } // Implementing the generic interface with String type class StringPrinter implements Printer<String> { public void print(String value) { System.out.println("String value: " + value); } } public class GenericInterfaceExample { public static void main(String[] args) { Printer<Integer> intPrinter = new IntegerPrinter(); intPrinter.print(100); Printer<String> strPrinter = new StringPrinter(); strPrinter.print("Generics are powerful!"); } }
Java allows you to restrict the types that can be used as type parameters. You can use extends
to specify an upper bound
for the type parameter. This is useful when you need to ensure that the type passed to the generic class or method has certain
properties or methods.
Example: Bounded type parameter for a generic method
// Bounded type parameter example public class BoundedTypeExample { // Only objects of type Number or its subclasses can be used public static <T extends Number> void printNumber(T number) { System.out.println("Number: " + number); } public static void main(String[] args) { printNumber(10); // Valid (Integer) printNumber(15.5); // Valid (Double) // The following will cause a compile-time error: // printNumber("Hello"); // Invalid (String is not a subclass of Number) } }
The wildcard in generics allows you to specify a method or class that can work with any type of data. It is represented by
a question mark (?
) and can be used to represent an unknown type.
Example: Using wildcards with generic methods
// Wildcard example public class WildcardExample { // This method accepts a list of any type of objects public static void printList(java.util.List<? extends Number> list) { for (Number number : list) { System.out.println(number); } } 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); printList(intList); printList(doubleList); } }
Generics are commonly used in the Java Collections Framework. By using generics, you can ensure type safety when working with collections like List, Set, and Map.
Example: Using generic collections
import java.util.*; public class GenericCollectionExample { public static void main(String[] args) { // Create a generic list of strings List<String> stringList = new ArrayList<>(); stringList.add("Java"); stringList.add("Generics"); // Print list elements for (String str : stringList) { System.out.println(str); } // Create a generic map with key-value pairs Map<Integer, String> map = new HashMap<>(); map.put(1, "One"); map.put(2, "Two"); // Print map entries for (Map.Entry<Integer, String> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } } }
Generics in Java are a powerful feature that improves code readability, reusability, and type safety. By using generic classes, methods, and interfaces, you can write more flexible and type-safe code. Understanding the various aspects of generics, such as bounded types, wildcards, and generic collections, is essential for mastering advanced Java programming.