Generic Type - COMPLETE DEEP DIVE π―
< > Ke Andar KYA Hota Hai?
ResponseEntity<User> // User class
ResponseEntity<List<User>> // List of Users
ResponseEntity<String> // String
ResponseEntity<Integer> // Integer
ResponseEntity<Map<String, Object>> // Map
ResponseEntity<Void> // Nothing (empty)
ResponseEntity<?> // Anything (wildcard)
1οΈβ£ GENERIC TYPE - What is ?
Definition:
// Generic class definition
public class ResponseEntity<T> {
private T body; // T is placeholder for ANY type
public ResponseEntity(T body) {
this.body = body;
}
public T getBody() {
return this.body;
}
}
/*
<T> means:
- T is a TYPE PARAMETER
- T can be replaced with ANY class
- T is decided at USAGE time, not definition time
*/
2οΈβ£ WHERE is Defined?
In Class Declaration:
// Spring's actual ResponseEntity source code
package org.springframework.http;
public class ResponseEntity<T> extends HttpEntity<T> {
// β
// Generic Type Parameter
private final HttpStatusCode status;
// Constructor
public ResponseEntity(T body, HttpStatusCode status) {
super(body);
this.status = status;
}
// Methods return same type T
public static <T> ResponseEntity<T> ok(T body) {
return new ResponseEntity<>(body, HttpStatus.OK);
}
}
Breakdown:
public class ResponseEntity<T>
β β
Class name Generic type parameter
- T is defined at CLASS level
- T can be any type (User, String, List, etc.)
- T is like a variable for types
3οΈβ£ HOW Does Work?
Example 1: Simple Generic Class
// Define a generic Box class
public class Box<T> {
private T item;
public void put(T item) {
this.item = item;
}
public T get() {
return this.item;
}
}
// USAGE:
// Box of String
Box<String> stringBox = new Box<>();
stringBox.put("Hello");
String value = stringBox.get(); // Returns String
// Box of Integer
Box<Integer> intBox = new Box<>();
intBox.put(123);
Integer number = intBox.get(); // Returns Integer
// Box of User
Box<User> userBox = new Box<>();
userBox.put(new User("Raj"));
User user = userBox.get(); // Returns User
Behind the Scenes:
// When you write: Box<String>
// Compiler generates (conceptually):
public class Box_String {
private String item;
public void put(String item) {
this.item = item;
}
public String get() {
return this.item;
}
}
// When you write: Box<User>
// Compiler generates (conceptually):
public class Box_User {
private User item;
public void put(User item) {
this.item = item;
}
public User get() {
return this.item;
}
}
4οΈβ£ ResponseEntity - Real Example
When You Write:
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
What Happens:
// Step 1: You declare return type
ResponseEntity<User>
// β
// T = User
// Step 2: Compiler knows
// - body type is User
// - ok() method should accept User
// - getBody() will return User
// Step 3: At runtime
ResponseEntity<User> response = ResponseEntity.ok(user);
User retrievedUser = response.getBody(); // Type-safe!
5οΈβ£ WHERE Can Be Used?
1. Class Level:
public class Container<T> {
private T data;
}
2. Method Level:
public class Utils {
// Generic method
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
}
// Usage:
List<String> names = Arrays.asList("Raj", "Amit");
String first = Utils.getFirst(names); // Returns String
List<Integer> numbers = Arrays.asList(1, 2, 3);
Integer firstNum = Utils.getFirst(numbers); // Returns Integer
3. Interface Level:
public interface Repository<T> {
T save(T entity);
T findById(Long id);
List<T> findAll();
}
// Implementation:
public class UserRepository implements Repository<User> {
@Override
public User save(User entity) { ... }
@Override
public User findById(Long id) { ... }
@Override
public List<User> findAll() { ... }
}
6οΈβ£ Common Types in Spring Boot
Built-in Java Types:
ResponseEntity<String> // String
ResponseEntity<Integer> // Integer
ResponseEntity<Long> // Long
ResponseEntity<Boolean> // Boolean
ResponseEntity<Double> // Double
Collections:
ResponseEntity<List<User>> // List
ResponseEntity<Set<String>> // Set
ResponseEntity<Map<String, Object>> // Map
ResponseEntity<Collection<Product>> // Collection
Custom Classes:
ResponseEntity<User> // Your User class
ResponseEntity<Product> // Your Product class
ResponseEntity<Order> // Your Order class
ResponseEntity<ApiResponse<User>> // Wrapper class
Spring Data Types:
ResponseEntity<Page<User>> // Paginated data
ResponseEntity<Slice<User>> // Slice of data
Special Types:
ResponseEntity<Void> // No body (empty response)
ResponseEntity<?> // Wildcard (any type)
ResponseEntity<byte[]> // Binary data
ResponseEntity<Resource> // File/Resource
7οΈβ£ Multiple Generic Types
Example: Map with Two Types
// Map has TWO type parameters
Map<K, V>
// Usage:
Map<String, Integer> // Key=String, Value=Integer
Map<Long, User> // Key=Long, Value=User
Map<String, List<Product>> // Key=String, Value=List<Product>
// In ResponseEntity:
ResponseEntity<Map<String, User>>
Custom Class with Multiple Generics:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// Usage:
Pair<String, Integer> pair = new Pair<>("age", 25);
String key = pair.getKey(); // Returns String
Integer value = pair.getValue(); // Returns Integer
// In ResponseEntity:
ResponseEntity<Pair<String, User>> response =
ResponseEntity.ok(new Pair<>("user", user));
8οΈβ£ Nested Generics
// List inside ResponseEntity
ResponseEntity<List<User>>
// Map with List value
ResponseEntity<Map<String, List<Product>>>
// Page with custom wrapper
ResponseEntity<ApiResponse<Page<User>>>
// Complex nesting
ResponseEntity<Map<String, List<ApiResponse<User>>>>
Example:
@GetMapping("/data")
public ResponseEntity<ApiResponse<List<User>>> getData() {
List<User> users = userService.findAll();
ApiResponse<List<User>> response = ApiResponse.success(users);
return ResponseEntity.ok(response);
}
// Response structure:
{
"success": true,
"data": [
{"id": 1, "name": "Raj"},
{"id": 2, "name": "Amit"}
]
}
9οΈβ£ Wildcard Types
1. Unbounded Wildcard: <?>
// Accept any type
public void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
// Can accept:
List<String> strings = Arrays.asList("a", "b");
List<Integer> numbers = Arrays.asList(1, 2, 3);
printList(strings);
printList(numbers);
// In ResponseEntity:
ResponseEntity<?> response = ResponseEntity.ok(anyObject);
2. Upper Bounded Wildcard: <? extends Type>
// Accept Type or its subclasses
public void processAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.makeSound();
}
}
// Can accept:
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
processAnimals(dogs); // OK
processAnimals(cats); // OK
// In ResponseEntity:
ResponseEntity<? extends User> response; // User or subclass
3. Lower Bounded Wildcard: <? super Type>
// Accept Type or its superclasses
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
// Can accept:
List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addNumbers(integers); // OK
addNumbers(numbers); // OK
addNumbers(objects); // OK
π Type Erasure - What Happens at Runtime
At Compile Time:
List<String> strings = new ArrayList<>();
strings.add("Hello");
String value = strings.get(0); // Type-safe
At Runtime (After Type Erasure):
// Generic types removed
List strings = new ArrayList();
strings.add("Hello");
Object value = strings.get(0); // No type info
String str = (String) value; // Manual cast
Why Type Erasure?
1. Backward compatibility with old Java code
2. Generics = compile-time feature only
3. At runtime, List<String> = List<Integer> = List
4. JVM doesn't know about <T>
Impact:
// β CANNOT do this
new T() // Cannot instantiate
T[] array = new T[10] // Cannot create array
if (obj instanceof T) // Cannot check type
// β
CAN do this
Class<T> clazz = ...
T instance = clazz.newInstance() // Using reflection
1οΈβ£1οΈβ£ Real-World Examples
Example 1: Generic Service
public interface CrudService<T, ID> {
T save(T entity);
T findById(ID id);
List<T> findAll();
void delete(ID id);
}
public class UserService implements CrudService<User, Long> {
// β β
// T=User ID=Long
@Override
public User save(User entity) { ... }
@Override
public User findById(Long id) { ... }
@Override
public List<User> findAll() { ... }
@Override
public void delete(Long id) { ... }
}
Example 2: Generic Repository
public interface JpaRepository<T, ID> extends Repository<T, ID> {
T save(T entity);
Optional<T> findById(ID id);
List<T> findAll();
void deleteById(ID id);
}
// Usage:
public interface UserRepository extends JpaRepository<User, Long> {
// β β
// T=User ID=Long
// Inherited methods will use User and Long
User save(User user);
Optional<User> findById(Long id);
List<User> findAll();
}
Example 3: Generic Controller
public abstract class BaseController<T, ID> {
protected abstract CrudService<T, ID> getService();
@GetMapping("/{id}")
public ResponseEntity<T> findById(@PathVariable ID id) {
T entity = getService().findById(id);
return ResponseEntity.ok(entity);
}
@PostMapping
public ResponseEntity<T> create(@RequestBody T entity) {
T created = getService().save(entity);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}
// Implementation:
@RestController
@RequestMapping("/api/users")
public class UserController extends BaseController<User, Long> {
// β β
// T=User ID=Long
@Autowired
private UserService userService;
@Override
protected CrudService<User, Long> getService() {
return userService;
}
}
1οΈβ£2οΈβ£ Generic Response Wrappers
ApiResponse with Generic:
public class ApiResponse<T> {
// β
// Generic data type
private boolean success;
private String message;
private T data; // Can be any type
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.data = data;
return response;
}
}
// Usage in Controller:
@GetMapping("/user/{id}")
public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(ApiResponse.success(user));
}
// Response: { "success": true, "data": { "id": 1, ... } }
@GetMapping("/users")
public ResponseEntity<ApiResponse<List<User>>> getUsers() {
List<User> users = userService.findAll();
return ResponseEntity.ok(ApiResponse.success(users));
}
// Response: { "success": true, "data": [...] }
PageResponse with Generic:
public class PageResponse<T> {
private List<T> content;
private int pageNumber;
private int totalPages;
private long totalElements;
// Constructor, getters, setters
}
// Usage:
@GetMapping("/users")
public ResponseEntity<PageResponse<UserDTO>> getUsers(Pageable pageable) {
Page<User> page = userService.findAll(pageable);
List<UserDTO> dtos = page.getContent()
.stream()
.map(userMapper::toDTO)
.collect(Collectors.toList());
PageResponse<UserDTO> response = new PageResponse<>();
response.setContent(dtos);
response.setPageNumber(page.getNumber());
response.setTotalPages(page.getTotalPages());
return ResponseEntity.ok(response);
}
1οΈβ£3οΈβ£ Where is NOT Allowed?
// β CANNOT use in static context without declaring
public class MyClass<T> {
private static T data; // β ERROR
public static T getData() { // β ERROR
return data;
}
}
// β
FIX: Make method generic
public class MyClass<T> {
public static <E> E getData(E data) { // β
OK
return data;
}
}
// β CANNOT instantiate directly
T obj = new T(); // β ERROR
// β CANNOT create generic array
T[] array = new T[10]; // β ERROR
// β
FIX: Use List
List<T> list = new ArrayList<>();
π Summary Table
| Symbol | Meaning | Example |
|---|---|---|
<T> |
Type parameter | class Box<T> |
<E> |
Element (collections) | List<E> |
<K> |
Key (maps) | Map<K, V> |
<V> |
Value (maps) | Map<K, V> |
<?> |
Unknown type | List<?> |
<? extends T> |
T or subclass | List<? extends Number> |
<? super T> |
T or superclass | List<? super Integer> |
π― Key Takeaways
// 1. <T> is defined at CLASS/METHOD level
public class ResponseEntity<T> { ... }
β
Defined here
// 2. <T> is replaced at USAGE time
ResponseEntity<User> response;
β
T becomes User
// 3. <T> provides TYPE SAFETY
List<String> list = new ArrayList<>();
list.add("Hello"); // β
OK
list.add(123); // β Compile error
// 4. <T> works at COMPILE time only
// At runtime, all generics are erased
// 5. Common patterns:
ResponseEntity<User> // Single object
ResponseEntity<List<User>> // List
ResponseEntity<ApiResponse<User>> // Wrapper
ResponseEntity<Void> // Empty
Generics = Type Safety + Code Reusability! π
Top comments (0)