Week 105 — What are generics and how can they be used?

Question of the Week #105
What are generics and how can they be used?
8 Replies
0x150
0x1505d ago
Generics allow specifying placeholders for types in classes and methods. If a class specifies a generic/type parameter using the <T> syntax, it can use the type T for variables, parameters, return types and superclasses/interfaces.
public class SomeGenericClass<T> {
private T someField;
private List<T> someList;

public SomeGenericClass(T element) {
someField = element;
someList = new ArrayList<>();
someList.add(element);
}

public T getElement(){
return someField;
}

public static void someStaticMethod(){
//static methods cannot make use of generics specified at a class level
}
}
public class SomeGenericClass<T> {
private T someField;
private List<T> someList;

public SomeGenericClass(T element) {
someField = element;
someList = new ArrayList<>();
someList.add(element);
}

public T getElement(){
return someField;
}

public static void someStaticMethod(){
//static methods cannot make use of generics specified at a class level
}
}
When an object of that class is created, all generics should be assigned to concrete types. Generics cannot be assigned to primitives (as of JDK 23 which is the current Java version at the time of writing this).
SomeGenericClass<String> objectOfGenericClassWithTypeParameterSetToString = new SomeGenericClass<>("Hello World");
String element = objectOfGenericClassWithTypeParameterSetToString.getElement();
SomeGenericClass<LocalDateTime> objectOfGenericClassWithTypeParameterSetToLocalDateTime = new SomeGenericClass<>(LocalDateTime.now());

SomeGenericClass.someStaticMethod();
SomeGenericClass<String> objectOfGenericClassWithTypeParameterSetToString = new SomeGenericClass<>("Hello World");
String element = objectOfGenericClassWithTypeParameterSetToString.getElement();
SomeGenericClass<LocalDateTime> objectOfGenericClassWithTypeParameterSetToLocalDateTime = new SomeGenericClass<>(LocalDateTime.now());

SomeGenericClass.someStaticMethod();
Methods can also declare generic types by specifying them before the return type:
public <T> T someMethod(T element){
return element;
}
public <T> T someMethod(T element){
return element;
}
String stringPassedToMethod = someMethod("abc");
LocalDateTime localDateTimePassedToMethod = someMethod(LocalDateTime.now());
String stringPassedToMethod = someMethod("abc");
LocalDateTime localDateTimePassedToMethod = someMethod(LocalDateTime.now());
0x150
0x1505d ago
Wildcards can be specified using the?-symbol meaning "any fixed type" whereas ? extends Temporal refers to "any fixed subtype of Temporal". It is also possible to use the super keyword to specify bounds requiring a supertype.
List<?> anyList = new ArrayList<String>();
anyList = new ArrayList<LocalDate>();

List<? extends Temporal> temporalList = new ArrayList<LocalDate>();
temporalList = new ArrayList<>(Temporal);
temporalList = new ArrayList<>(LocalDateTime);
//temporalList = new ArrayList<>(String);//compiler error

List<? super String> listAllowingStringInsertion = new ArrayList<String>();//the type of this list could be String or supertypes of it
listAllowingStringInsertion.add("Hello World");
listAllowingStringInsertion = new ArrayList<Object>();//any List<Object> is a List<? super String>
List<?> anyList = new ArrayList<String>();
anyList = new ArrayList<LocalDate>();

List<? extends Temporal> temporalList = new ArrayList<LocalDate>();
temporalList = new ArrayList<>(Temporal);
temporalList = new ArrayList<>(LocalDateTime);
//temporalList = new ArrayList<>(String);//compiler error

List<? super String> listAllowingStringInsertion = new ArrayList<String>();//the type of this list could be String or supertypes of it
listAllowingStringInsertion.add("Hello World");
listAllowingStringInsertion = new ArrayList<Object>();//any List<Object> is a List<? super String>
Generics can have bounds specifying that the types must be within these bounds.
public class SomeClass<T extends Temporal> {
private T element;
}
public class SomeClass<T extends Temporal> {
private T element;
}
SomeClass<LocalDate> thisIsValid = new SomeClass<>();
//SomeClass<String> thisIsInvalid = new SomeClass<>();//violates the <T extends Temporal> bound
SomeClass<LocalDate> thisIsValid = new SomeClass<>();
//SomeClass<String> thisIsInvalid = new SomeClass<>();//violates the <T extends Temporal> bound
These bounds can also make use of wildcards. For example, it is possible to require a generic type to extend Collection of some Temporal
public class SomeClassWithCollection<C extends Collection<? extends Temporal>> {
private C theCollection;
public SomeClassWithCollection(C collection) {
theCollection = collection;
}
public C getCollection(){
return theCollection;
}
public Optional<Temporal> findSomeElement(){
Iterator<? extends Temporal> it = theCollection.iterator();
if(it.hasNext()){
return Optional.of(it.next());
}
return Optional.empty();
}
}
public class SomeClassWithCollection<C extends Collection<? extends Temporal>> {
private C theCollection;
public SomeClassWithCollection(C collection) {
theCollection = collection;
}
public C getCollection(){
return theCollection;
}
public Optional<Temporal> findSomeElement(){
Iterator<? extends Temporal> it = theCollection.iterator();
if(it.hasNext()){
return Optional.of(it.next());
}
return Optional.empty();
}
}
📖 Sample answer from dan1st
0x150
0x1505d ago
Generics enables developers to define functions, classes, and interfaces that can work with multiple data types, without the need for explicit type casting or separate implementations for each type. Example
public class Container<T> {
private T value;

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

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}
public class Container<T> {
private T value;

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

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}
Submission from ig.imanish
0x150
0x1505d ago
Generics is used for type templating where the return type of a method can be different types or class uses different types of members. My favourite example of a good usage of generics is with Transformers or a Provider or List. You're welcome ( ̄︶ ̄)↗
Submission from klone_king
0x150
0x1505d ago
Generics are a language feature that was added to Java 5. They allow code to work with objects of any type (or a constrained type), rather than a single specific type. The classic use case for generics are collections of objects. As an example, think about the java.util.List interface. Without generics, there would be only 2 possibilities for the API: 1. Methods like add and get would operate on Object, so that any possible object could be passed/returned 2. Methods like add and get would need to be overridden for every possible class Obviously, number 2 is not practical (and may not even be possible). So, before generics, the List interface operated on Object. This works, but is not type-safe. There is nothing preventing someone from inserting a String into a list that is expected to contain Integers. This would actually work on the add call. But down the road, when objects are read from the list, the client would have no way of knowing whether the objects it retrieves are Integers or something else, without checking every time. However, generics allow types and methods to define one or more generic parameters. The compiler uses these to constrain the the types of parameters or return values that are allowed at a call site or within a method. For instance, the aforementioned java.util.List interface defines a single generic parameter, <T>. Most of the methods in the interface make use of this generic parameter for their own arguments and/or return type.
0x150
0x1505d ago
This small example shows how a generified type can be used:
List<String> strings = new ArrayList<>(); // strings can only hold... strings
strings.add("string one");
strings.add("string two");
strings.add(LocalDate.now()); // Will not compile

String one = strings.get(0); // No check or cast needed
LocalDate three = strings.get(2); // Will not compile
List<String> strings = new ArrayList<>(); // strings can only hold... strings
strings.add("string one");
strings.add("string two");
strings.add(LocalDate.now()); // Will not compile

String one = strings.get(0); // No check or cast needed
LocalDate three = strings.get(2); // Will not compile
⭐ Submission from dangerously_casual
0x150
0x1505d ago
Generic types are placeholders for reference types that can be used to make a piece of code work for multiple types, without having to duplicate the code. They're like C++'s templates, although less advanced. Whereas C++'s templates are literal templates that just insert the type (or value) used in the generic type parameter, Java figures out the types at compile time. The resulting bytecode then uses the first parent type applicable to all possible types of the generic type, and can safely cast that parent type to the used generic type based on usage.
<ElementType, ListType extends List<ElementType>> ListType addAnElementTo(ListType theList, ElementType theElement) {
theList.add(theElement);
return theList;
}
<ElementType, ListType extends List<ElementType>> ListType addAnElementTo(ListType theList, ElementType theElement) {
theList.add(theElement);
return theList;
}
this will be compiled to:
List addAnElementTo(List theList, Object theElement) {
theList.add(theElement);
return theList;
}
List addAnElementTo(List theList, Object theElement) {
theList.add(theElement);
return theList;
}
but due to compile time constraints, javac can safely transform usages of the code like
List<String> strings = addAnElementTo(new ArrayList<>(), "hello");
List<String> strings = addAnElementTo(new ArrayList<>(), "hello");
to
List<String> strings = (ArrayList<String>) addAnElementTo(new ArrayList<String>(), "hello");
List<String> strings = (ArrayList<String>) addAnElementTo(new ArrayList<String>(), "hello");
. Doing it this way means that basic generic types are supported, but this also means that type information about a generic type can't be accessed at runtime, since the generic type doesn't really exist at runtime anymore. A hack is using an automatically filled vararg parameter with the generic type you want to test, such as:
<T> void printTheTypeGiven(T... typeProbe) {
System.out.println(typeProbe.getClass().getComponentType());
}
<T> void printTheTypeGiven(T... typeProbe) {
System.out.println(typeProbe.getClass().getComponentType());
}
because this method can be called like this.<String>printTheGivenType(), without passing the vararg parameter explicity, javac will generate an empty array of T, and pass it to the method, which looks like this at runtime: printTheGivenType(new String[0]). With that information, we can figure out which type the array has, and can thus figure the type out. This isn't exactly foolproof, since you can technically pass an array to the method manually and use some other type extending T, but it's the best method we have right now.
0x150
0x1505d ago
Reified generics solve this problem, but they're not supported in java yet.
Submission from 0x150

Did you find this page helpful?