Beginner’s Guide to Generics in Java

Dharshi Balasubramaniyam
7 min readApr 5, 2024

--

  • Imagine you’re running a grocery store, and you want to organize your shelves to hold different types of products.
  • Traditionally, you might have separate shelves for fruits, vegetables, dairy products, and so on.
  • Instead of having separate shelves for each type of product, you have a set of flexible shelves that can hold any type of product.
  • Now, let’s think about generics in terms of organizing these shelves. The beauty of generics here is that you don’t need to create separate shelves for each type of product. You can use the same generic shelves for different types of items, making your store more flexible and efficient.
  • In programming, this flexibility is incredibly valuable. You can create generic data structures or functions that work with any type of data, just like our generic shelves can hold any type of product in the grocery store. This makes your code more adaptable and reusable, which is a big win in terms of efficiency and maintainability.
Figure 1: Generics in JAVA

- Generics, refer to a way of writing code that allows types (classes and interfaces) to be parameterized.

- This means that instead of specifying concrete types for variables, methods, or classes, you can use placeholders that are later replaced by actual types when the code is used.

- The primary purpose of generics is to enable the creation of reusable components that can work with any data type.

- They provide a way to write code that is type-safe, meaning that it can catch type errors at compile time rather than at runtime. This helps in identifying and fixing bugs earlier in the development process, which ultimately leads to more robust and reliable software.

- Generic Classes

  • Generic classes in Java allow you to define classes with placeholders for types.
  • These placeholders can then be replaced with actual types when creating instances of the class. This provides flexibility and type safety.
  • To define a generic class, you use angle brackets < > to declare type parameters. These parameters can be used throughout the class definition.
// T is the placeholder representing the type of the item stored in the Box.
public class Box<T> {
private T item;

public void setItem(T item) {
this.item = item;
}

public T getItem() {
return item;
}
}
  • When you create an instance of Box and specify a type, such as Box<Integer>, T is replaced with Integer, and the Box class effectively becomes a container for integers.
public class Main {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.setItem(10);
System.out.println("Item in the integer box: " + integerBox.getItem());

Box<String> stringBox = new Box<>();
stringBox.setItem("Hello, World!");
System.out.println("Item in the string box: " + stringBox.getItem());
}
}

The above Java program demonstrates the usage of a generic class Box with two different types (Integer and String).

- Generic methods

  • Generic methods allow you to create methods that work with different types without specifying the actual type until the method is called.
  • The syntax for defining a generic method involves placing type parameters in angle brackets before the return type.
public class ArrayPrinter {
// Generic method to print an array of any type
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}

public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"apple", "banana", "orange"};

// Print the Integer array
System.out.print("Integer Array: ");
printArray(intArray);

// Print the String array
System.out.print("String Array: ");
printArray(stringArray);
}
}
  • The above code is to demonstrate the use of a generic method printArray that can print elements of arrays of any type.

- Wildcards

  • wildcard types are used to make generic types more flexible, particularly when dealing with collections.
  • They allow you to define generic types that can accept a wider range of subtypes. Wildcard types are represented using the ? symbol.
  • There are two main types of wildcard bounds:
  1. Upper Bounded Wildcards (<? extends T>):
  • These wildcards restrict the unknown type to be a specific type or a subtype of that type. They allow you to specify that a generic type parameter should be an instance of a class or a subclass of that class.
import java.util.*;

class UpperBoundExample {
static double sum(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}

public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println("Sum of integers: " + sum(integers));

List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
System.out.println("Sum of doubles: " + sum(doubles));
}
}
  • In the sum method, the parameter List<? extends Number> accepts a list of any type that extends Number. So, both List<Integer> and List<Double> can be passed to this method.

2. Lower Bounded Wildcards (<? super T>):

  • These wildcards restrict the unknown type to be a specific type or a supertype of that type. They allow you to specify that a generic type parameter should be an instance of a class or a superclass of that class.
import java.util.*;

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

public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
addIntegers(numbers);
System.out.println("Numbers: " + numbers);
}
}
  • In the addIntegers method, the parameter List<? super Integer> accepts a list of any type that is a superclass of Integer. So, List<Number> or List<Object> can be passed to this method.

- Real world examples in JAVA and Spring boot

Here are some real-world examples of how generics are used in Java libraries and frameworks:

1. Java Collections Framework:

  • One of the most prominent uses of generics is in the Java Collections Framework.
  • Collections such as ArrayList, LinkedList, HashMap, etc., are all generic classes. For example:
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
String firstElement = stringList.get(0);
  • Here, List<String> ensures that only String objects can be added to the list, providing type safety at compile time.

2. Repository Interfaces with Spring Data JPA:

  • In Spring Boot applications using Spring Data JPA, repository interfaces are defined with generics to specify the entity type and the type of the entity’s primary key. For example:
public interface UserRepository extends JpaRepository<User, Long> {
// Custom query methods can be declared here
}

Here, User is the entity class representing a user, and Long is the type of the user's primary key.

3. Service Layer:

  • Service classes in Spring Boot often use generics for increased flexibility and type safety. For instance, a generic service interface might look like this:
public interface CrudService<T, ID> {
T findById(ID id);
T save(T entity);
void deleteById(ID id);
}

Implementations of this interface can handle different types of entities by specifying concrete types for T and ID.

4. Controllers and Responses:

  • Generics can be used in controller methods to handle requests and responses for different types of data. For example:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}

@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
}

This provides type safety and allows for different types of data to be returned from controller methods.

5. DTOs (Data Transfer Objects):

  • Generics can also be useful when working with DTOs in Spring Boot applications. For example, you might have a generic ResponseDTO class to handle responses in a uniform way across the application.
public class ResponseDTO<T> {
private boolean success;
private String message;
private T data;

// Constructors, getters, and setters
}
  • You can use this ResponseDTO class to wrap any type of data that you want to return in your API responses. For example, in a controller method:
@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping
public ResponseEntity<ResponseDTO<List<User>>> getAllUsers() {
List<User> users = userService.getAllUsers();
ResponseDTO<List<User>> response = new ResponseDTO<>(true, "Users fetched successfully", users);
return ResponseEntity.ok(response);
}
}

- Conclusion

In conclusion, throughout this beginner’s guide to generics in Java, we’ve covered some fundamental concepts that are essential for any Java programmer to grasp. Here’s a quick recap of the key points we’ve discussed:

  • Generics: Generics in Java allow us to create classes, interfaces, and methods that operate on objects of various types while providing type safety and code reusability.
  • Benefits of Generics: By using generics, we can write more flexible, reusable and maintainable code. Generics also help in avoiding unnecessary type casting.
  • Basic Syntax and Usage: We’ve learned how to define and use generic classes and methods, understanding the syntax and conventions associated with generics in Java.
  • Wildcard Types: We’ve explored wildcard types and their role in allowing for more flexibility when working with generics, including upper and lower bounded wildcards.

As you continue your journey in Java programming, I encourage you to explore generics further. Embracing generics will not only enhance your understanding of Java but also empower you to write cleaner, safer, and more efficient code.

If you found my articles useful, please consider giving it claps and sharing it with your friends and colleagues.

Keep learning, exploring, and creating amazing things with JAVA!

Happy coding!

-Dharshi Balasubramaniyam-

--

--

Dharshi Balasubramaniyam

BSc (hons) in Software Engineering, UG, University of Kelaniya, Sri Lanka. Looking for software engineer/full stack developer internship roles.