Introduction
Generics in Java provide a way to create classes, interfaces, and methods that operate on types specified by the client code. Generics add stability to your code by making more of your bugs detectable at compile time. They enable types (classes and methods) to be parameters when defining classes, interfaces, and methods.
Table of Contents
- What are Generics?
- Benefits of Using Generics
- Generic Classes
- Generic Methods
- Bounded Type Parameters
- Generic Interfaces
- Type Inference
- Wildcards in Generics
- Restrictions on Generics
- Example Programs
- Conclusion
1. What are Generics?
Generics allow types (classes and methods) to operate on objects of various types while providing compile-time type safety. They enable you to write more flexible and reusable code without sacrificing type safety.
2. Benefits of Using Generics
- Type Safety: Generics make errors detectable at compile time rather than at runtime.
- Code Reusability: Generics enable you to write a method or class that can operate on objects of various types.
- Elimination of Type Casting: Explicit type casting is not needed when using generics.
3. Generic Classes
A generic class is defined with a type parameter. This type parameter can be used throughout the class for various purposes.
Syntax:
class GenericClass<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
Example:
public class GenericClassExample {
public static void main(String[] args) {
GenericClass<Integer> intObj = new GenericClass<>();
intObj.setValue(100);
System.out.println("Integer Value: " + intObj.getValue());
GenericClass<String> stringObj = new GenericClass<>();
stringObj.setValue("Java Generics");
System.out.println("String Value: " + stringObj.getValue());
}
}
Output:
Integer Value: 100
String Value: Java Generics
4. Generic Methods
A generic method allows the type to be a parameter to methods.
Syntax:
public <T> void genericMethod(T param) {
System.out.println(param.getClass().getName() + " = " + param);
}
Example:
public class GenericMethodExample {
public <T> void print(T param) {
System.out.println(param.getClass().getName() + " = " + param);
}
public static void main(String[] args) {
GenericMethodExample example = new GenericMethodExample();
example.print(123);
example.print("Java Generics");
example.print(45.67);
}
}
Output:
java.lang.Integer = 123
java.lang.String = Java Generics
java.lang.Double = 45.67
5. Bounded Type Parameters
You can restrict the types that can be used as type arguments by using bounded type parameters.
Syntax:
class GenericClass<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
Example:
public class BoundedTypeParameterExample {
public static void main(String[] args) {
GenericClass<Integer> intObj = new GenericClass<>();
intObj.setValue(100);
System.out.println("Integer Value: " + intObj.getValue());
GenericClass<Double> doubleObj = new GenericClass<>();
doubleObj.setValue(45.67);
System.out.println("Double Value: " + doubleObj.getValue());
// The following line will cause a compile-time error
// GenericClass<String> stringObj = new GenericClass<>();
}
}
Output:
Integer Value: 100
Double Value: 45.67
6. Generic Interfaces
An interface can be generic and have type parameters.
Syntax:
interface GenericInterface<T> {
void display(T value);
}
Example:
class GenericInterfaceImpl<T> implements GenericInterface<T> {
@Override
public void display(T value) {
System.out.println(value);
}
}
public class GenericInterfaceExample {
public static void main(String[] args) {
GenericInterface<String> stringImpl = new GenericInterfaceImpl<>();
stringImpl.display("Hello Generics");
GenericInterface<Integer> intImpl = new GenericInterfaceImpl<>();
intImpl.display(123);
}
}
Output:
Hello Generics
123
7. Type Inference
Java 7 introduced the diamond operator (<>
), which allows the compiler to infer the type parameters.
Example:
public class TypeInferenceExample {
public static void main(String[] args) {
GenericClass<Integer> intObj = new GenericClass<>();
intObj.setValue(100);
System.out.println("Integer Value: " + intObj.getValue());
// Using the diamond operator
GenericClass<String> stringObj = new GenericClass<>();
stringObj.setValue("Java Generics");
System.out.println("String Value: " + stringObj.getValue());
}
}
Output:
Integer Value: 100
String Value: Java Generics
8. Wildcards in Generics
Wildcards (?
) allow you to use generics more flexibly. There are three types of wildcards:
- Unbounded Wildcards (
?
): Accepts any type. - Bounded Wildcards (
<? extends Type>
): Accepts a type and its subclasses. - Lower Bounded Wildcards (
<? super Type>
): Accepts a type and its superclasses.
Example:
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("Generics");
printList(intList);
printList(stringList);
}
}
Output:
1
2
3
Hello
Generics
9. Restrictions on Generics
There are several restrictions on generics in Java:
-
Cannot Instantiate Generic Types with Primitive Types:
// This is not allowed GenericClass<int> intObj = new GenericClass<>();
-
Cannot Create Instances of Type Parameters:
class GenericClass<T> { // This is not allowed // T obj = new T(); }
-
Cannot Declare Static Fields Whose Types are Type Parameters:
class GenericClass<T> { // This is not allowed // static T obj; }
-
Cannot Use Casts or instanceof with Parameterized Types:
class GenericClass<T> { // This is not allowed // if (obj instanceof T) { } // T[] array = (T[]) new Object[10]; }
-
Cannot Create Arrays of Parameterized Types:
// This is not allowed GenericClass<String>[] stringArray = new GenericClass<String>[10];
10. Example Programs
Example 1: Generic Class
Example:
class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
public class GenericClassExample {
public static void main(String[] args) {
Box<Integer> intBox = new Box<>();
intBox.set(123);
System.out.println("Integer Value: " + intBox.get());
Box<String> strBox = new Box<>();
strBox.set("Hello Generics");
System.out.println("String Value: " + strBox.get());
}
}
Example 2: Generic Method
Example:
public class GenericMethodExample {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"Hello", "Generics", "in", "Java"};
printArray(intArray);
printArray(strArray);
}
}
Example 3: Bounded Type Parameter
Example:
class NumberBox<T extends Number> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
public double doubleValue() {
return value.doubleValue();
}
}
public class BoundedTypeParameterExample {
public
static void main(String[] args) {
NumberBox<Integer> intBox = new NumberBox<>();
intBox.set(123);
System.out.println("Integer Value: " + intBox.get());
System.out.println("Double Value: " + intBox.doubleValue());
NumberBox<Double> doubleBox = new NumberBox<>();
doubleBox.set(45.67);
System.out.println("Double Value: " + doubleBox.get());
System.out.println("Double Value: " + doubleBox.doubleValue());
}
}
11. Conclusion
Generics in Java provide a powerful and flexible mechanism to write more reusable and type-safe code. By understanding and utilizing generic classes, methods, interfaces, bounded type parameters, and wildcards, you can create robust and maintainable code. Generics help eliminate runtime type errors and reduce the need for explicit type casting, making your code more readable and error-free.
Happy coding!
Comments
Post a Comment
Leave Comment