Generics in Java are a way to provide type safety at compile-time and enable code reuse by allowing classes and methods to operate on a range of types rather than just one. Generics were introduced in Java 5 and are represented by angle brackets <>.
When you create a generic class or method, you define a type parameter using a placeholder name inside the angle brackets. For example, to create a generic class that can hold any type of object, you can define it like this:
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
In this example, T is the type parameter. You can create an instance of this class by specifying the actual type to use for T:
Box<Integer> integerBox = new Box<>();
Box<String> stringBox = new Box<>();
Upper bounds and lower bounds are used to restrict the range of types that can be used with a type parameter. An upper bound restricts the type parameter to a specific type or any of its subtypes, while a lower bound restricts the type parameter to a specific type or any of its supertypes.
To specify an upper bound, you can use the extends keyword followed by the upper bound type
public class Box<T extends Number> {
// …
}
In this example, T can be any type that extends Number (such as Integer, Double, Float, etc.).
To specify a lower bound, you can use the super keyword followed by the lower bound type:
public void addAll(Collection<? super T> c) {
// …
}
In this example, the addAll method can accept any collection that is a supertype of T (such as Collection<Object> or Collection<Number>).
The choice between using an upper bound or a lower bound depends on the requirements of your code. If you need to restrict the range of types that can be used as a type parameter to a specific type or its subtypes, use an upper bound. If you need to restrict the range of types to a specific type or its supertypes, use a lower bound.
What is the difference between List<? extends T> and List <? super T>?
List<? extends T> denotes a list of elements of a type that extends T. This means that the list can contain elements of type T or any subtype of T. For example, if T is Number, List<? extends Number> can refer to a List<Integer>, List<Double>, List<Long>, or any other list whose element type is a subtype of Number. You can use this kind of wildcard when you want to read from a list but don’t need to write to it.
List<? super T> denotes a list of elements of a type that is a supertype of T. This means that the list can contain elements of type T or any superclass of T. For example, if T is Integer, List<? super Integer> can refer to a List<Object>, List<Number>, or List<Integer>. You can use this kind of wildcard when you want to write to a list but don’t need to read from it.
In general, you can use List<? extends T> when you want to consume elements from a list, and List<? super T> when you want to produce elements to a list.
Can you pass List<String> to a method that accepts List<Object>?
No, you cannot pass a List<String> to a method that accepts a List<Object> directly in Java. This is because of type safety and type erasure in Java’s generics.
Consider the following example:
List<String> strings = new ArrayList<>();
strings.add(“hello”);
List<Object> objects = strings; // This line is not allowed.
objects.add(1);
String s = strings.get(1); // Throws ClassCastException at runtime
Here, we create a List<String> and add a string to it. Then, we try to assign it to a variable of type List<Object>, which is not allowed because of type safety. If it were allowed, we could add an integer to the list, as shown on the third line, which would cause a ClassCastException when we try to retrieve the elements as strings.
To avoid this issue, you can use a wildcard type to represent an unknown type parameter. For example, you can use List<? extends Object> or List<?> to accept any type of list.