Angle Bracket <> in Java with Examples

In Java, angle brackets (<>) are used as part of the syntax for generics. Generics were introduced in Java 5 to provide a way to create classes, interfaces, and methods that operate on a specified type without compromising type safety. The angle brackets are used to declare and define generic types, allowing classes and methods to work with different types in a type-safe manner.

Various uses of Angle Bracket <>

1. Generic Classes

Generic classes allow you to declare a class with one or more type parameters. These type parameters act as placeholders for actual data types that will be specified when creating an instance of the class. This enables the creation of classes that can work with various types without sacrificing type safety.

Filename: Box<T>.java

public class Box<T> {

    private T value;

    public Box(T value) {

        this.value = value;

    }

    public T getValue() {

        return value;

    }

    public void setValue(T value) {

        this.value = value;

    }

    public static void main(String[] args) {

        // Creating a Box for Integer

        Box<Integer> integerBox = new Box<>(42);

        System.out.println("Integer Value: " + integerBox.getValue());

        // Creating a Box for String

        Box<String> stringBox = new Box<>("Hello, Generics!");

        System.out.println("String Value: " + stringBox.getValue());

    }

}

Output:

Integer Value: 42

String Value: Hello, Generics!

Explanation

The Box class is declared with a type parameter T. The value field is of type T, allowing it to hold values of any type. The constructor accepts an initial value of type T. Getter and setter methods (getValue and setValue) operate on the generic type T. In the main method, we create instances of the Box class with specific types (Integer and String).

2. Generic Methods

In Java, generic methods allow you to create methods that work with different types while maintaining type safety. Like generic classes, generic methods use type parameters to specify and enforce the types they work with. This enables you to write methods that can operate on a variety of data types without sacrificing compile-time type checking.

Filename: GenericMethodExample.java

public class GenericMethodExample {

    public static <T extends Comparable<T>> T findMax(T[] array) {

        if (array == null || array.length == 0) {

            return null; // Return null for an empty array

        }

        T max = array[0];

        for (T element : array) {

            if (element.compareTo(max) > 0) {

                max = element;

            }

        }

        return max;

    }

    public static void main(String[] args) {

        // Example with integers

        Integer[] intArray = {3, 1, 4, 1, 5, 9, 2, 6, 5};

        Integer maxInt = findMax(intArray);

        System.out.println("Max Integer: " + maxInt);

        // Example with strings

        String[] stringArray = {"apple", "orange", "banana", "kiwi"};

        String maxString = findMax(stringArray);

        System.out.println("Max String: " + maxString);

    }

}

Output:

Max Integer: 9

Max String: orange

Explanation

The findMax method is declared as a generic method using the <T> syntax before the return type. The type parameter T is constrained to implement the Comparable<T> interface, ensuring that the elements in the array can be compared.

The method uses the type parameter T for the array type and the comparison of elements. In the main method, we demonstrate using the generic method with both an array of integers and an array of strings.

3. Bounded Wildcards

In Java, bounded wildcards are a feature of generics that allow you to specify restrictions on the types that can be used as arguments in generic classes or methods. Bounded wildcards are denoted by the use of the wildcard character (?) along with the extends or super keywords. These wildcards are useful when you want to accept a range of types without losing type safety.

Filename: WildcardExample.java

import java.util.ArrayList;

import java.util.List;

public class WildcardExample {

    // Upper Bounded Wildcard: accepts a list of any type that is a subtype of Number

    public static double sumOfList(List<? extends Number> numbers) {

        double sum = 0.0;

        for (Number number : numbers) {

            sum += number.doubleValue();

        }

        return sum;

    }

    // Lower Bounded Wildcard: adds integers to a list or its supertypes

    public static void addIntegers(List<? super Integer> numbers) {

        numbers.add(1);

        numbers.add(2);

        numbers.add(3);

    }

    public static void main(String[] args) {

        // Upper Bounded Wildcard Example

        List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        List<Double> doubles = new ArrayList<>(List.of(1.1, 2.2, 3.3, 4.4, 5.5));

        double sumOfIntegers = sumOfList(integers);

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

        double sumOfDoubles = sumOfList(doubles);

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

        // Lower Bounded Wildcard Example

        List<Number> numberList = new ArrayList<>(List.of(10, 20, 30));

        addIntegers(numberList);

        System.out.println("List after adding integers: " + numberList);

    }

}

Output:

Sum of Integers: 15.0

Sum of Doubles: 16.5

List after adding integers: [10, 20, 30, 1, 2, 3]

Explanation

sumOfList method takes a list of any type that extends Number (List<? extends Number>) and calculates the sum of its elements. It uses an upper-bounded wildcard to ensure that the list can contain elements of type Number or its subtypes.

addIntegers method takes a list of any type that is a supertype of Integer (List<? super Integer>) and adds integer values to it. It uses a lower-bounded wildcard to allow the list to be of type Integer or any of its supertypes.

4. Creating Parameterized Types

Creating parameterized types allows you to write code that can work with a variety of data types without sacrificing type safety. This is achieved by introducing a type parameter, which acts as a placeholder for the actual type that will be specified when using the class or method. Parameterized types are particularly useful when working with collections or algorithms that should be independent of the specific data types they operate on.

Filename: Box<T>.java

public class Box<T> {

    private T value;

    public Box(T value) {

        this.value = value;

    }

    public T getValue() {

        return value;

    }

    public static <E> void printArray(E[] array) {

        for (E element : array) {

            System.out.print(element + " ");

        }

        System.out.println();

    }

    public static void main(String[] args) {

        // Parameterized Class Example

        Box<Integer> integerBox = new Box<>(42);

        System.out.println("Integer Value: " + integerBox.getValue());

        // Parameterized Method Example

        String[] stringArray = {"Java", "Generics", "Example"};

        printArray(stringArray);

    }

}

Output:

Integer Value: 42

Java Generics Example

Explanation

The Box class is parameterized with a type parameter T. It has a constructor that takes a value of type T and a method getValue that returns the stored value. The printArray method is a parameterized method that can print an array of any type (E). It uses a type parameter to achieve this flexibility.

In the main method, Box<Integer> is instantiated, creating a Box that holds an integer value. The printArray method is called with a String array.

5. Recursive Generics

Recursive generics enable the creation of data structures or classes where the type parameter is used recursively within the class definition. This is common in scenarios where a class or data structure needs to reference instances of the same class with the same type parameter.

Filename: BinaryTree<T>.java

public class BinaryTree<T> {

    private T data;

    private BinaryTree<T> left;

    private BinaryTree<T> right;

    public BinaryTree(T data) {

        this.data = data;

        this.left = null;

        this.right = null;

    }

    public BinaryTree(T data, BinaryTree<T> left, BinaryTree<T> right) {

        this.data = data;

        this.left = left;

        this.right = right;

    }

    public T getData() {

        return data;

    }

    public BinaryTree<T> getLeft() {

        return left;

    }

    public BinaryTree<T> getRight() {

        return right;

    }

    public static void main(String[] args) {

        // Creating a binary tree

        BinaryTree<String> tree = new BinaryTree<>("A",

                new BinaryTree<>("B",

                        new BinaryTree<>("D"),

                        new BinaryTree<>("E")),

                new BinaryTree<>("C",

                        new BinaryTree<>("F"),

                        new BinaryTree<>("G")));

        // Example usage

        System.out.println("Root: " + tree.getData());

        System.out.println("Left Child: " + tree.getLeft().getData());

        System.out.println("Right Child: " + tree.getRight().getData());

    }

}

Output:

Root: A

Left Child: B

Right Child: C

Explanation

The BinaryTree class is defined with a generic type parameter T. The class has three fields: data of type T representing the data of the node, and left and right of type BinaryTree<T> representing the left and right subtrees. Two constructors are provided—one for creating a leaf node and another for creating a node with left and right subtrees.

Getter methods (getData, getLeft, and getRight) allow access to the data and subtrees. In the main method, a binary tree is created with String data. The example usage prints information about the root and its left and right children.

Conclusion

In conclusion, the use of angle brackets in Java generics is fundamental to creating flexible, type-safe, and reusable code. Generics enhance the expressiveness of Java programs and contribute to the development of robust and maintainable software. The combination of generics, wildcards, and other features introduced by angle brackets reflects Java's commitment to providing a strong, statically-typed language that allows developers to write more generic and adaptable code.