How to Work with Java Generics & The Method Class Interface

Avatar

By squashlabs, Last Updated: July 31, 2023

How to Work with Java Generics & The Method Class Interface

Table of Contents

Introduction to Generics

Generics in Java provide a way to create reusable code that can work with different types. It allows us to define classes, interfaces, and methods that can operate on a variety of data types without sacrificing type safety. By using generics, we can write code that is more flexible, efficient, and less error-prone.

Related Article: How To Parse JSON In Java

Code Snippet 1: Generic Method

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

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

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

        System.out.print("Double Array: ");
        printArray(doubleArray);

        System.out.print("Character Array: ");
        printArray(charArray);
    }
}

Output:

Integer Array: 1 2 3 4 5 
Double Array: 1.1 2.2 3.3 4.4 5.5 
Character Array: H E L L O 

Code Snippet 2: Generic Class

public class GenericClassExample<T> {
    private T value;

    public GenericClassExample(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        GenericClassExample<Integer> intValue = new GenericClassExample<>(10);
        GenericClassExample<String> stringValue = new GenericClassExample<>("Hello");

        System.out.println("Integer Value: " + intValue.getValue());
        System.out.println("String Value: " + stringValue.getValue());
    }
}

Output:

Integer Value: 10
String Value: Hello

Theoretical Background of Generics

Generics were introduced in Java 5 to provide compile-time type safety and eliminate the need for casting. The concept of generics is based on parameterized types. It allows us to define classes, interfaces, and methods that can work with different types, known as type parameters.

Related Article: How To Convert Array To List In Java

Code Snippet 3: Generic Interface

public interface GenericInterfaceExample<T> {
    T performOperation(T operand1, T operand2);
}

Code Snippet 4: Wildcard Usage

public class WildcardExample {
    public static double sum(List<? extends Number> numbers) {
        double total = 0.0;
        for (Number number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);

        double sumOfIntegers = sum(integers);
        double sumOfDoubles = sum(doubles);

        System.out.println("Sum of Integers: " + sumOfIntegers);
        System.out.println("Sum of Doubles: " + sumOfDoubles);
    }
}

Output:

Sum of Integers: 15.0
Sum of Doubles: 16.5

Detailed Analysis of Generics

Generics provide compile-time type checking and help prevent type-related errors at runtime. The Java compiler ensures that the code using generics is type-safe by performing type inference and enforcing type constraints. This improves code reliability and reduces the likelihood of bugs.

Related Article: How To Iterate Over Entries In A Java Map

Code Snippet 5: Generic Exception Handling

public class GenericExceptionHandlingExample {
    public static <T extends Exception> void handleException(T exception) {
        System.out.println("Exception: " + exception.getMessage());
    }

    public static void main(String[] args) {
        NullPointerException nullPointerException = new NullPointerException("Null Value Detected");
        ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException = new ArrayIndexOutOfBoundsException("Array Index Out of Bounds");

        handleException(nullPointerException);
        handleException(arrayIndexOutOfBoundsException);
    }
}

Output:

Exception: Null Value Detected
Exception: Array Index Out of Bounds

Code Snippet 6: Type Erasure

import java.util.ArrayList;
import java.util.List;

public class TypeErasureExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

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

        System.out.println(stringList.getClass() == integerList.getClass());
    }
}

Output:

true

Generics and their Syntax

The syntax for using generics involves declaring type parameters inside angle brackets (““) after the class, interface, or method name. The type parameters can be any valid identifier and are used to represent the actual types that will be used when the code is instantiated or invoked.

Related Article: How To Split A String In Java

Code Snippet 7: Generic Method with Bounded Type Parameters

public class BoundedTypeParameterExample {
    public static <T extends Number> double sum(List<T> numbers) {
        double total = 0.0;
        for (T number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);

        double sumOfIntegers = sum(integers);
        double sumOfDoubles = sum(doubles);

        System.out.println("Sum of Integers: " + sumOfIntegers);
        System.out.println("Sum of Doubles: " + sumOfDoubles);
    }
}

Output:

Sum of Integers: 15.0
Sum of Doubles: 16.5

Code Snippet 8: Generic Class with Multiple Type Parameters

public class MultipleTypeParameterExample<T, U> {
    private T value1;
    private U value2;

    public MultipleTypeParameterExample(T value1, U value2) {
        this.value1 = value1;
        this.value2 = value2;
    }

    public T getValue1() {
        return value1;
    }

    public U getValue2() {
        return value2;
    }

    public static void main(String[] args) {
        MultipleTypeParameterExample<Integer, String> example = new MultipleTypeParameterExample<>(10, "Hello");

        System.out.println("Value 1: " + example.getValue1());
        System.out.println("Value 2: " + example.getValue2());
    }
}

Output:

Value 1: 10
Value 2: Hello

Use Case 1: Implementing Generics in Method

Generics can be used in methods to create reusable code that can work with different types. By using generics in methods, we can avoid code duplication and improve code maintainability.

Related Article: How To Convert Java Objects To JSON With Jackson

Code Snippet 9: Generic Method with Multiple Type Parameters

public class MultipleTypeParameterMethodExample {
    public static <T, U> void printValues(T value1, U value2) {
        System.out.println("Value 1: " + value1);
        System.out.println("Value 2: " + value2);
    }

    public static void main(String[] args) {
        printValues(10, "Hello");
        printValues(3.14, true);
    }
}

Output:

Value 1: 10
Value 2: Hello
Value 1: 3.14
Value 2: true

Code Snippet 10: Generic Method with Upper Bounded Type Parameter

public class UpperBoundedTypeParameterExample {
    public static <T extends Number> double sum(List<T> numbers) {
        double total = 0.0;
        for (T number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);

        double sumOfIntegers = sum(integers);
        double sumOfDoubles = sum(doubles);

        System.out.println("Sum of Integers: " + sumOfIntegers);
        System.out.println("Sum of Doubles: " + sumOfDoubles);
    }
}

Output:

Sum of Integers: 15.0
Sum of Doubles: 16.5

Use Case 2: Generics in Class Definition

Generics can also be used in class definitions to create generic classes that can work with different types. By using generics in classes, we can create reusable data structures and algorithms that are type-safe and flexible.

Related Article: Storing Contact Information in Java Data Structures

Code Snippet 11: Generic Class with Type Parameter

public class GenericClassExample<T> {
    private T value;

    public GenericClassExample(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        GenericClassExample<Integer> intValue = new GenericClassExample<>(10);
        GenericClassExample<String> stringValue = new GenericClassExample<>("Hello");

        System.out.println("Integer Value: " + intValue.getValue());
        System.out.println("String Value: " + stringValue.getValue());
    }
}

Output:

Integer Value: 10
String Value: Hello

Code Snippet 12: Generic Class with Multiple Type Parameters

public class MultipleTypeParameterClassExample<T, U> {
    private T value1;
    private U value2;

    public MultipleTypeParameterClassExample(T value1, U value2) {
        this.value1 = value1;
        this.value2 = value2;
    }

    public T getValue1() {
        return value1;
    }

    public U getValue2() {
        return value2;
    }

    public static void main(String[] args) {
        MultipleTypeParameterClassExample<Integer, String> example = new MultipleTypeParameterClassExample<>(10, "Hello");

        System.out.println("Value 1: " + example.getValue1());
        System.out.println("Value 2: " + example.getValue2());
    }
}

Output:

Value 1: 10
Value 2: Hello

Use Case 3: Interface and Generics

Generics can also be used in interfaces to create generic interfaces that can be implemented by different classes. By using generics in interfaces, we can define common behaviors and contracts that can work with different types.

Related Article: How to Convert JSON String to Java Object

Code Snippet 13: Generic Interface

public interface GenericInterfaceExample<T> {
    T performOperation(T operand1, T operand2);
}

public class AdditionOperation implements GenericInterfaceExample<Integer> {
    @Override
    public Integer performOperation(Integer operand1, Integer operand2) {
        return operand1 + operand2;
    }
}

public class ConcatenationOperation implements GenericInterfaceExample<String> {
    @Override
    public String performOperation(String operand1, String operand2) {
        return operand1 + operand2;
    }
}

public class Main {
    public static void main(String[] args) {
        GenericInterfaceExample<Integer> addition = new AdditionOperation();
        GenericInterfaceExample<String> concatenation = new ConcatenationOperation();

        System.out.println("Addition: " + addition.performOperation(5, 10));
        System.out.println("Concatenation: " + concatenation.performOperation("Hello", "World"));
    }
}

Output:

Addition: 15
Concatenation: HelloWorld

Best Practice 1: Correct use of Wildcards

When working with generics, it is important to understand the correct use of wildcards. Wildcards allow us to express “some unknown type” when working with generic types. There are two types of wildcards: upper bounded and lower bounded.

Code Snippet 14: Upper Bounded Wildcard

public class UpperBoundedWildcardExample {
    public static double sum(List<? extends Number> numbers) {
        double total = 0.0;
        for (Number number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);

        double sumOfIntegers = sum(integers);
        double sumOfDoubles = sum(doubles);

        System.out.println("Sum of Integers: " + sumOfIntegers);
        System.out.println("Sum of Doubles: " + sumOfDoubles);
    }
}

Output:

Sum of Integers: 15.0
Sum of Doubles: 16.5

Related Article: How to Retrieve Current Date and Time in Java

Code Snippet 15: Lower Bounded Wildcard

public class LowerBoundedWildcardExample {
    public static void addIntegers(List<? super Integer> numbers) {
        for (int i = 1; i <= 5; i++) {
            numbers.add(i);
        }
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        numbers.add(0.0);

        addIntegers(numbers);

        System.out.println("Numbers: " + numbers);
    }
}

Output:

Numbers: [0.0, 1, 2, 3, 4, 5]

Best Practice 2: Avoiding Raw Types

When using generics, it is recommended to avoid using raw types. Raw types are generic types without type parameters specified. They exist for backward compatibility with pre-generic code but can lead to type safety issues and potential runtime errors.

Code Snippet 16: Raw Type Example

public class RawTypeExample {
    public static void main(String[] args) {
        List rawList = new ArrayList();
        rawList.add(10);
        rawList.add("Hello");

        for (Object element : rawList) {
            System.out.println(element);
        }
    }
}

Output:

10
Hello

Related Article: How to Reverse a String in Java

Code Snippet 17: Generic Type Example

public class GenericTypeExample {
    public static void main(String[] args) {
        List<Object> genericList = new ArrayList<>();
        genericList.add(10);
        genericList.add("Hello");

        for (Object element : genericList) {
            System.out.println(element);
        }
    }
}

Output:

10
Hello

Real World Example 1: Generic Data Structures

Generics are widely used in the implementation of data structures such as lists, queues, and maps. By using generics in data structures, we can create highly reusable and type-safe containers that can store and manipulate data of different types.

Code Snippet 18: Generic List

public class GenericListExample<T> {
    private List<T> list;

    public GenericListExample() {
        this.list = new ArrayList<>();
    }

    public void add(T element) {
        list.add(element);
    }

    public void remove(T element) {
        list.remove(element);
    }

    public void print() {
        for (T element : list) {
            System.out.println(element);
        }
    }

    public static void main(String[] args) {
        GenericListExample<String> stringList = new GenericListExample<>();
        stringList.add("Hello");
        stringList.add("World");

        GenericListExample<Integer> integerList = new GenericListExample<>();
        integerList.add(1);
        integerList.add(2);

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

        System.out.println("Integer List:");
        integerList.print();
    }
}

Output:

String List:
Hello
World
Integer List:
1
2

Related Article: How to Generate Random Integers in a Range in Java

Code Snippet 19: Generic Queue

import java.util.LinkedList;
import java.util.Queue;

public class GenericQueueExample<T> {
    private Queue<T> queue;

    public GenericQueueExample() {
        this.queue = new LinkedList<>();
    }

    public void enqueue(T element) {
        queue.add(element);
    }

    public T dequeue() {
        return queue.poll();
    }

    public void print() {
        for (T element : queue) {
            System.out.println(element);
        }
    }

    public static void main(String[] args) {
        GenericQueueExample<String> stringQueue = new GenericQueueExample<>();
        stringQueue.enqueue("Hello");
        stringQueue.enqueue("World");

        GenericQueueExample<Integer> integerQueue = new GenericQueueExample<>();
        integerQueue.enqueue(1);
        integerQueue.enqueue(2);

        System.out.println("String Queue:");
        stringQueue.print();

        System.out.println("Integer Queue:");
        integerQueue.print();
    }
}

Output:

String Queue:
Hello
World
Integer Queue:
1
2

Real World Example 2: Generic Algorithms

Generics are also used in the implementation of generic algorithms that can work with different data types. By using generics in algorithms, we can create reusable and type-safe methods that can perform operations on different types of data.

Code Snippet 20: Generic Sorting Algorithm

import java.util.Arrays;

public class GenericSortingExample {
    public static <T extends Comparable<T>> void sort(T[] array) {
        Arrays.sort(array);
    }

    public static void main(String[] args) {
        Integer[] integers = {5, 3, 1, 4, 2};
        String[] strings = {"D", "B", "E", "A", "C"};

        System.out.println("Before Sorting - Integers: " + Arrays.toString(integers));
        System.out.println("Before Sorting - Strings: " + Arrays.toString(strings));

        sort(integers);
        sort(strings);

        System.out.println("After Sorting - Integers: " + Arrays.toString(integers));
        System.out.println("After Sorting - Strings: " + Arrays.toString(strings));
    }
}

Output:

Before Sorting - Integers: [5, 3, 1, 4, 2]
Before Sorting - Strings: [D, B, E, A, C]
After Sorting - Integers: [1, 2, 3, 4, 5]
After Sorting - Strings: [A, B, C, D, E]

Related Article: Java Equals Hashcode Tutorial

Code Snippet 21: Generic Searching Algorithm

import java.util.Arrays;

public class GenericSearchingExample {
    public static <T extends Comparable<T>> int binarySearch(T[] array, T key) {
        int low = 0;
        int high = array.length - 1;

        while (low <= high) {
            int mid = (low + high) / 2;
            int comparison = array[mid].compareTo(key);

            if (comparison == 0) {
                return mid;
            } else if (comparison < 0) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }

        return -1;
    }

    public static void main(String[] args) {
        Integer[] integers = {1, 2, 3, 4, 5};
        String[] strings = {"A", "B", "C", "D", "E"};

        int index1 = binarySearch(integers, 3);
        int index2 = binarySearch(strings, "D");

        System.out.println("Index of 3 in Integers: " + index1);
        System.out.println("Index of D in Strings: " + index2);
    }
}

Output:

Index of 3 in Integers: 2
Index of D in Strings: 3

Real World Example 3: Type Safety with Generics

Generics provide type safety by ensuring that the code operates on the correct data types. By using generics, we can catch type-related errors at compile-time, reducing the likelihood of runtime errors and improving code reliability.

Code Snippet 22: Type Safety with Generics

public class TypeSafetyExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

        // stringList.add(10); // Compilation Error: Type mismatch

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

Output:

Hello
World

Related Article: How To Convert String To Int In Java

Code Snippet 23: Type Inference with Generics

public class TypeInferenceExample {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

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

        System.out.print("Double Array: ");
        printArray(doubleArray);

        System.out.print("Character Array: ");
        printArray(charArray);
    }
}

Output:

Integer Array: 1 2 3 4 5 
Double Array: 1.1 2.2 3.3 4.4 5.5 
Character Array: H E L L O 

Performance Consideration 1: Space Efficiency

Generics do not have a significant impact on space efficiency because type parameter information is erased at runtime. The JVM uses type erasure to replace type parameters with their upper bound or Object if no upper bound is specified.

Code Snippet 24: Type Erasure

import java.util.ArrayList;
import java.util.List;

public class TypeErasureExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

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

        System.out.println(stringList.getClass() == integerList.getClass());
    }
}

Output:

true

Related Article: Java Composition Tutorial

Code Snippet 25: Type Erasure with Generic Methods

public class TypeErasureMethodExample {
    public static void printList(List<String> stringList) {
        for (String element : stringList) {
            System.out.println(element);
        }
    }

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

        printList(stringList);
    }
}

Output:

Hello
World

Performance Consideration 2: Speed Efficiency

Generics have a minor impact on speed efficiency due to the additional overhead of type checking and type inference at compile-time. However, this impact is negligible and generally outweighed by the benefits of type safety and code reuse provided by generics.

Code Snippet 26: Generic Method Performance

public class GenericMethodPerformanceExample {
    public static <T> void performOperation(T element) {
        // Perform some operation
    }

    public static void main(String[] args) {
        long startTime = System.nanoTime();

        for (int i = 0; i < 1000000; i++) {
            performOperation(i);
        }

        long endTime = System.nanoTime();
        long duration = endTime - startTime;

        System.out.println("Execution Time: " + duration + " nanoseconds");
    }
}

Output:

Execution Time: ...

Related Article: Java Hashmap Tutorial

Code Snippet 27: Generic Class Performance

public class GenericClassPerformanceExample<T> {
    private T value;

    public GenericClassPerformanceExample(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        long startTime = System.nanoTime();

        for (int i = 0; i < 1000000; i++) {
            GenericClassPerformanceExample<Integer> example = new GenericClassPerformanceExample<>(i);
            int value = example.getValue();
        }

        long endTime = System.nanoTime();
        long duration = endTime - startTime;

        System.out.println("Execution Time: " + duration + " nanoseconds");
    }
}

Output:

Execution Time: ...

Advanced Technique 1: Bounded Type Parameters

Bounded type parameters allow us to restrict the types that can be used as type arguments in generics. We can specify upper bounds, lower bounds, or both to limit the type arguments to a specific range of types.

Code Snippet 28: Upper Bounded Type Parameter

public class UpperBoundedTypeParameterExample {
    public static <T extends Number> double sum(List<T> numbers) {
        double total = 0.0;
        for (T number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);

        double sumOfIntegers = sum(integers);
        double sumOfDoubles = sum(doubles);

        System.out.println("Sum of Integers: " + sumOfIntegers);
        System.out.println("Sum of Doubles: " + sumOfDoubles);
    }
}

Output:

Sum of Integers: 15.0
Sum of Doubles: 16.5

Related Article: Popular Data Structures Asked in Java Interviews

Code Snippet 29: Lower Bounded Type Parameter

public class LowerBoundedTypeParameterExample {
    public static void addIntegers(List<? super Integer> numbers) {
        for (int i = 1; i <= 5; i++) {
            numbers.add(i);
        }
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        numbers.add(0.0);

        addIntegers(numbers);

        System.out.println("Numbers: " + numbers);
    }
}

Output:

Numbers: [0.0, 1, 2, 3, 4, 5]

Advanced Technique 2: Generic Type Inference

Type inference allows the Java compiler to automatically determine the type arguments of generic methods or constructors based on the context in which they are used. This eliminates the need to explicitly specify the type arguments, making the code more concise.

Code Snippet 30: Type Inference with Generic Method

public class TypeInferenceMethodExample {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

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

        System.out.print("Double Array: ");
        printArray(doubleArray);

        System.out.print("Character Array: ");
        printArray(charArray);
    }
}

Output:

Integer Array: 1 2 3 4 5 
Double Array: 1.1 2.2 3.3 4.4 5.5 
Character Array: H E L L O 

Related Article: Java List Tutorial

Code Snippet 31: Type Inference with Generic Class

public class TypeInferenceClassExample<T> {
    private T value;

    public TypeInferenceClassExample(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        TypeInferenceClassExample<Integer> intValue = new TypeInferenceClassExample<>(10);
        TypeInferenceClassExample<String> stringValue = new TypeInferenceClassExample<>("Hello");

        System.out.println("Integer Value: " + intValue.getValue());
        System.out.println("String Value: " + stringValue.getValue());
    }
}

Output:

Integer Value: 10
String Value: Hello

Code Snippet 1: Generic Method

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

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

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

        System.out.print("Double Array: ");
        printArray(doubleArray);

        System.out.print("Character Array: ");
        printArray(charArray);
    }
}

Output:

Integer Array: 1 2 3 4 5 
Double Array: 1.1 2.2 3.3 4.4 5.5 
Character Array: H E L L O 

Code Snippet 2: Generic Class

public class GenericClassExample<T> {
    private T value;

    public GenericClassExample(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        GenericClassExample<Integer> intValue = new GenericClassExample<>(10);
        GenericClassExample<String> stringValue = new GenericClassExample<>("Hello");

        System.out.println("Integer Value: " + intValue.getValue());
        System.out.println("String Value: " + stringValue.getValue());
    }
}

Output:

Integer Value: 10
String Value: Hello

Related Article: Java Set Tutorial

Code Snippet 3: Generic Interface

public interface GenericInterfaceExample<T> {
    T performOperation(T operand1, T operand2);
}

Code Snippet 4: Wildcard Usage

public class WildcardExample {
    public static double sum(List<? extends Number> numbers) {
        double total = 0.0;
        for (Number number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);

        double sumOfIntegers = sum(integers);
        double sumOfDoubles = sum(doubles);

        System.out.println("Sum of Integers: " + sumOfIntegers);
        System.out.println("Sum of Doubles: " + sumOfDoubles);
    }
}

Output:

Sum of Integers: 15.0
Sum of Doubles: 16.5

Code Snippet 5: Generic Exception Handling

public class GenericExceptionHandlingExample {
    public static <T extends Exception> void handleException(T exception) {
        System.out.println("Exception: " + exception.getMessage());
    }

    public static void main(String[] args) {
        NullPointerException nullPointerException = new NullPointerException("Null Value Detected");
        ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException = new ArrayIndexOutOfBoundsException("Array Index Out of Bounds");

        handleException(nullPointerException);
        handleException(arrayIndexOutOfBoundsException);
    }
}

Output:

Exception: Null Value Detected
Exception: Array Index Out of Bounds

Related Article: Java Classloader: How to Load Classes in Java

Error Handling in Generics

When working with generics, it is important to handle errors and exceptions appropriately. This ensures that the code behaves correctly and provides meaningful feedback to the users. Proper error handling can help identify and resolve issues early in the development process.

Java Do-While Loop Tutorial

Learn how to use the do-while loop in Java with this tutorial. This article provides an introduction to the do-while loop, explains its syntax, and guides you through... read more

Tutorial: Best Practices for Java Singleton Design Pattern

This guide provides examples of best practices for the Java Singleton Design Pattern. Learn about lazy initialization, thread safety, serialization-safe singletons, and... read more

How to Use Public Static Void Main String Args in Java

Java's public static void main string args method is a fundamental concept every Java developer should understand. This article provides a comprehensive guide on how to... read more

Java Printf Method Tutorial

Learn how to use the Java Printf method for formatted output. This tutorial covers the syntax, arguments, flags, and best practices of the Printf method. It also... read more

Tutorial: Java Write To File Operations

Java Write To File Operations is a tutorial that provides a guide on performing file operations in Java, with a particular focus on writing to a file. The article covers... read more

How to Use Regular Expressions with Java Regex

Regular expressions are a powerful tool for pattern matching and text manipulation in Java. This tutorial provides practical examples, from basic to advanced techniques,... read more