Java Generics is a feature introduced in JDK 5. Generics provide a compile-time type safety detection mechanism, which allows programmers to detect illegal types at compile time.
The essence of generics is parameterized types, meaning the data type being operated on is specified as a parameter.
Suppose we have a requirement: write a sorting method that can sort integer arrays, string arrays, and even arrays of any other type. How should we implement this?
The answer is to use Java Generics.
Using the concept of Java generics, we can write a generic method to sort an object array. Then, call this generic method to sort integer arrays, floating-point arrays, string arrays, etc.
Generic Methods
You can write a generic method, which can accept different types of parameters when called. The compiler handles each method call appropriately based on the parameter types passed to the generic method.
Below are the rules for defining a generic method:
- All generic method declarations have a type parameter declaration section (separated by angle brackets), which is placed before the method return type (as
<E>in the example below). - Each type parameter declaration section contains one or more type parameters, separated by commas. A generic parameter, also known as a type variable, is an identifier used to specify a generic type name.
- Type parameters can be used to declare the return type and serve as placeholders for the actual parameter types received by the generic method.
- The body of a generic method is declared like any other method. Note that type parameters can only represent reference types, not primitive types (like int, double, char, etc.).
Generic markers in Java:
- E - Element (used in collections, as collections store elements)
- T - Type (Java class)
- K - Key
- V - Value
- N - Number (numeric type)
- ? - Represents an unknown Java type
Example
The following example demonstrates how to use a generic method to print elements of different types of arrays:
Example
public class GenericMethodTest {
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", 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};
Character[] charArray = {'H', 'E', 'L', 'L', 'O'};
System.out.println("Integer array elements:");
printArray(intArray);
System.out.println("nDouble array elements:");
printArray(doubleArray);
System.out.println("nCharacter array elements:");
printArray(charArray);
}
}
Compile and run the above code, the output is as follows:
Integer array elements:
1 2 3 4 5
Double array elements:
1.1 2.2 3.3 4.4
Character array elements:
H E L L O
Bounded Type Parameters:
Sometimes, you may want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is the purpose of bounded type parameters.
To declare a bounded type parameter, first list the name of the type parameter, followed by the extends keyword, and finally its upper bound.
Example
The following example demonstrates how "extends" is used in the general sense of "extends" (class) or "implements" (interface). The generic method in this example returns the maximum of three comparable objects.
Example
public class MaximumTest {
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x;
if (y.compareTo(max) > 0) {
max = y;
}
if (z.compareTo(max) > 0) {
max = z;
}
return max;
}
public static void main(String args[]) {
System.out.printf("%d, %d and %d is the largest numbernn", 3, 4, 5, maximum(3, 4, 5));
System.out.printf("%.1f, %.1f and %.1f is the largest numbernn", 6.6, 8.8, 7.7, maximum(6.6, 8.8, 7.7));
System.out.printf("%s, %s and %s is the largest numbern", "pear", "apple", "orange", maximum("pear", "apple", "orange"));
}
}
Compile and run the above code, the output is as follows:
3, 4 and 5 is the largest number
6.6, 8.8 and 7.7 is the largest number
pear, apple and orange is the largest number
Generic Classes
The declaration of a generic class is similar to that of a non-generic class, except that a type parameter declaration section is added after the class name.
Like generic methods, the type parameter declaration section of a generic class also contains one or more type parameters, separated by commas. A generic parameter, also known as a type variable, is an identifier used to specify a generic type name. Because they accept one or more parameters, these classes are called parameterized classes or parameterized types.
Example
The following example demonstrates how we define a generic class:
Example
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String(""));
System.out.printf("Integer value :%dnn", integerBox.get());
System.out.printf("String value :%sn", stringBox.get());
}
}
Compile and run the above code, the output is as follows:
Integer value :10
String value :
Type Wildcards
1. Type wildcards generally use ? to represent an unknown type parameter. For example, List<?> is logically the parent class of all List<concrete type argument> such as List<String>, List<Integer>, etc.
Example
import java.util.*;
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getData(name);
getData(age);
getData(number);
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
}
Output:
data :icon
data :18
data :314
Explanation: Because the parameter of the getData() method is of type List<?>, name, age, and number can all be used as actual arguments for this method. This is the function of wildcards.
2. The upper bound of a type wildcard is defined in the form List<? extends Number>. This definition means the wildcard generic only accepts Number and its subclass types.
Example
import java.util.*;
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getUperNumber(age); //1
getUperNumber(number); //1
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
}
Output:
data :18
data :314
Explanation: An error would occur at //1 because the parameter in the getUperNumber() method has already been limited to an upper bound of Number. Therefore, a generic of String is not within this range, so it will cause an error.
3. The lower bound of a type wildcard is defined in the form List<? super Number>, indicating that the type can only accept Number and its parent class types, such as instances of the Object type.
YouTip