MODULE 2 AJAVA
MODULE 2 AJAVA
Module-2
What Are Generics?
Generics in Java allow you to create classes, interfaces, and methods that operate on various
types while providing compile-time type safety. They enable you to define a class, interface,
or method with type parameters, which can then be used with different types in a type-safe
manner.
Key Concepts
1. Parameterized Types: Generics allow you to specify types as parameters. This means
you can create a class or method that works with any data type, but still maintains
type safety.
2. Type Safety: Before generics, you could create generalized classes or methods using
Object references, but this approach lacked type safety. Generics provide compile-
time type checks and eliminate the need for explicit type casting.
3. Code Reusability: Generics help in writing reusable and maintainable code. You can
write a class or method that works with different types of objects without duplicating
code for each type
A Simple Generics Example
Generics in Java allow you to define classes, interfaces, and methods with type parameters,
enabling them to work with different types while maintaining type safety. Here's a simple
example of a generic class and how it's used.
Example Code
Generic Class (Gen<T>)
// A simple generic class.
// Here, T is a type parameter that
// will be replaced by a real type
// when an object of type Gen is created.
class Gen<T> {
T ob; // declare an object of type T
1
Advanced Java Programming 21CS642
// Return ob.
T getob() {
return ob;
}
// Show type of T.
void showType() {
System.out.println("Type of T is " + ob.getClass().getName());
}
}
Demo Class (GenDemo)
// Demonstrate the generic class.
class GenDemo {
public static void main(String args[]) {
// Create a Gen reference for Integers.
Gen<Integer> iOb;
2
Advanced Java Programming 21CS642
System.out.println();
3
Advanced Java Programming 21CS642
o String str = strOb.getob();: Retrieves the value of ob, which is "Generics Test",
and assigns it to str.
Output
The program produces the following output:
Type of T is java.lang.Integer
value: 88
Type of T is java.lang.String
value: Generics Test
Key Points
Type Parameter (T): Acts as a placeholder for the actual type that will be used when
an instance of the generic class is created.
Type Safety: Generics provide compile-time type safety. You can't assign an object of
one type to a generic class of another type without a compile-time error.
Autoboxing and Unboxing: Java automatically converts between primitive types and
their corresponding wrapper classes (e.g., int to Integer and vice versa).
Generics Work Only with Reference Types
Java generics only work with reference types, not primitive types. This is because generics
are implemented using type erasure, which involves replacing generic types with their bounds
or Object if no bound is specified. Primitive types like int or char are not reference types and
thus cannot be used as type arguments in generics.
Example of Illegal Use:
Gen<int> intOb = new Gen<int>(53); // Error, can't use primitive type
To handle primitives, Java provides wrapper classes like Integer, Character, etc. These classes
are reference types and can be used with generics.
Example of Legal Use:
Gen<Integer> intOb = new Gen<Integer>(53); // Correct, using Integer wrapper class
Generic Types Differ Based on Their Type Arguments
Generic types are differentiated based on their type arguments. This means that even if two
generic types are structurally similar, they are considered different if their type parameters
differ.
Example of Type Safety:
Gen<Integer> iOb = new Gen<Integer>(88);
Gen<String> strOb = new Gen<String>("Hello");
4
Advanced Java Programming 21CS642
void showType() {
System.out.println("Type of ob is " + ob.getClass().getName());
}
}
class NonGenDemo {
public static void main(String args[]) {
NonGen iOb = new NonGen(88); // Stores an Integer
iOb.showType(); // Shows Integer type
5
Advanced Java Programming 21CS642
T getob() {
return ob;
}
void showType() {
System.out.println("Type of T is " + ob.getClass().getName());
}
}
6
Advanced Java Programming 21CS642
class GenDemo {
public static void main(String args[]) {
Gen<Integer> iOb = new Gen<Integer>(88);
iOb.showType(); // Shows Integer type
7
Advanced Java Programming 21CS642
V getob2() {
return ob2;
}
}
Using the Generic Class with Two Type Parameters
To use TwoGen, you need to specify both type parameters when creating an instance. Here’s
an example demonstrating how to instantiate and use the TwoGen class:
class SimpGen {
public static void main(String args[]) {
// Create an instance of TwoGen with Integer and String type parameters
TwoGen<Integer, String> tgObj = new TwoGen<Integer, String>(88, "Generics");
8
Advanced Java Programming 21CS642
9
Advanced Java Programming 21CS642
Creating an Instance
To create an instance of a generic class, you specify the type arguments in angle brackets and
provide constructor arguments as needed:
ClassName<TypeArgList> varName = new ClassName<TypeArgList>(constructorArgs);
TypeArgList: The type arguments for the type parameters.
varName: The variable name for the instance.
constructorArgs: Arguments passed to the constructor.
Bounded Types
Sometimes you want to restrict the types that can be used as type parameters. Bounded types
help achieve this by specifying constraints on the type parameters.
Upper Bound
You can specify an upper bound for a type parameter to restrict it to a certain class or its
subclasses. This is done using the extends keyword:
class ClassName<T extends Superclass> {
// Class body
}
Here, T can only be a subclass of Superclass or Superclass itself. This ensures that the type
parameter T will have all the methods and properties of Superclass.
Example with Numeric Types
Consider the Stats class, which calculates the average of an array of numbers. The original
version of the class fails because T is not constrained, so the compiler does not recognize
doubleValue() method from Number:
class Stats<T> {
T[] nums;
Stats(T[] o) {
nums = o;
}
double average() {
double sum = 0.0;
for(int i = 0; i < nums.length; i++)
10
Advanced Java Programming 21CS642
Stats(T[] o) {
nums = o;
}
double average() {
double sum = 0.0;
for(int i = 0; i < nums.length; i++)
sum += nums[i].doubleValue(); // Now it works!
return sum / nums.length;
}
}
Example Usage
Here’s how you can use the bounded Stats class:
class BoundsDemo {
public static void main(String args[]) {
Integer[] inums = { 1, 2, 3, 4, 5 };
Stats<Integer> iob = new Stats<Integer>(inums);
double v = iob.average();
System.out.println("iob average is " + v);
11
Advanced Java Programming 21CS642
double w = dob.average();
System.out.println("dob average is " + w);
12
Advanced Java Programming 21CS642
Problem Statement
You have a Stats class that calculates the average of an array of numbers. You want to add a
method sameAvg( ) that compares the average of the current Stats object with another Stats
object, regardless of the specific numeric type they use (e.g., Integer, Double, Float).
The challenge is that the sameAvg( ) method must work with any Stats object, irrespective of
the specific numeric type it holds.
Initial Attempt and Issues
A straightforward approach might look like this:
boolean sameAvg(Stats<T> ob) {
if (average() == ob.average())
return true;
return false;
}
However, this approach only works if ob is of the same type as the invoking Stats object. If
the invoking object is Stats<Integer>, then ob must also be Stats<Integer>. This is too
restrictive and does not accommodate comparisons between different numeric types.
Solution: Using Wildcards
To create a more flexible sameAvg( ) method, you can use a wildcard argument. The wildcard
? represents an unknown type and allows for greater flexibility in handling different types:
boolean sameAvg(Stats<?> ob) {
if (average() == ob.average())
return true;
return false;
}
Here’s what’s happening:
Stats<?>: This represents a Stats object with an unknown type. It can be any type that
extends Number.
average(): This method computes the average of the numbers in the array and is
compatible with the Stats<?> parameter because it uses the wildcard.
Example Code
Here’s a full example that demonstrates the use of wildcards with the Stats class:
13
Advanced Java Programming 21CS642
14
Advanced Java Programming 21CS642
15
Advanced Java Programming 21CS642
// Three-dimensional coordinates.
class ThreeD extends TwoD {
int z;
ThreeD(int a, int b, int c) {
super(a, b);
z = c;
}
16
Advanced Java Programming 21CS642
// Four-dimensional coordinates.
class FourD extends ThreeD {
int t;
FourD(int a, int b, int c, int d) {
super(a, b, c);
t = d;
}
}
// This class holds an array of coordinate objects.
class Coords<T extends TwoD> {
T[] coords;
Coords(T[] o) { coords = o; }
}
// Demonstrate a bounded wildcard.
class BoundedWildcard {
static void showXY(Coords<?> c) {
System.out.println("X Y Coordinates:");
for(int i = 0; i < c.coords.length; i++)
System.out.println(c.coords[i].x + " " + c.coords[i].y);
System.out.println();
}
static void showXYZ(Coords<? extends ThreeD> c) {
System.out.println("X Y Z Coordinates:");
for(int i = 0; i < c.coords.length; i++)
System.out.println(c.coords[i].x + " " + c.coords[i].y + " " + c.coords[i].z);
System.out.println();
}
static void showAll(Coords<? extends FourD> c) {
17
Advanced Java Programming 21CS642
System.out.println("X Y Z T Coordinates:");
for(int i = 0; i < c.coords.length; i++)
System.out.println(c.coords[i].x + " " + c.coords[i].y + " " + c.coords[i].z + " " +
c.coords[i].t);
System.out.println();
}
public static void main(String args[]) {
TwoD td[] = {
new TwoD(0, 0),
new TwoD(7, 9),
new TwoD(18, 4),
new TwoD(-1, -23)
};
Coords<TwoD> tdlocs = new Coords<>(td);
System.out.println("Contents of tdlocs.");
showXY(tdlocs); // OK, is a TwoD
// The following lines will result in compile-time errors:
// showXYZ(tdlocs); // Error, not a ThreeD
// showAll(tdlocs); // Error, not a FourD
FourD fd[] = {
new FourD(1, 2, 3, 4),
new FourD(6, 8, 14, 8),
new FourD(22, 9, 4, 9),
new FourD(3, -2, -23, 17)
};
Coords<FourD> fdlocs = new Coords<>(fd);
System.out.println("Contents of fdlocs.");
showXY(fdlocs); // OK, is a FourD
showXYZ(fdlocs); // OK, is a FourD
showAll(fdlocs); // OK, is a FourD
}
18
Advanced Java Programming 21CS642
}
Upper Bound Wildcard (<? extends T>): Useful for reading values of a specific type or its
subtypes.
Lower Bound Wildcard (<? super T>): Useful for writing values to a collection, ensuring
that you can add instances of a specific type or its subtypes.
Generic Method Definition:
static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y) {
for(int i = 0; i < y.length; i++) {
if(x.equals(y[i])) return true;
}
return false;
}
o <T extends Comparable<T>, V extends T>: This specifies two type
parameters:
T extends Comparable<T>, meaning T must be a type that can be
compared.
V extends T, meaning V must be T or a subclass of T.
o T x: The object to search for in the array.
o V[] y: The array to search through.
2. Method Usage in main():
public static void main(String args[]) {
Integer nums[] = { 1, 2, 3, 4, 5 };
if(isIn(2, nums))
System.out.println("2 is in nums");
if(!isIn(7, nums))
System.out.println("7 is not in nums");
19
Advanced Java Programming 21CS642
}
o Calls isIn() with Integer and String arrays.
o Demonstrates type safety: trying to call isIn() with incompatible types (like
String and Integer) will result in a compile-time error.
Generic Constructor Example
In the provided code, the class GenCons has a generic constructor, which allows it to handle
various numeric types. Here’s a detailed explanation:
Code
java
Copy code
// Use a generic constructor.
class GenCons {
private double val;
// Generic constructor
<T extends Number> GenCons(T arg) {
val = arg.doubleValue();
}
void showval() {
System.out.println("val: " + val);
}
}
class GenConsDemo {
public static void main(String args[]) {
GenCons test = new GenCons(100); // Integer argument
GenCons test2 = new GenCons(123.5F); // Float argument
test.showval();
test2.showval();
20
Advanced Java Programming 21CS642
}
}
Generic Constructor:
<T extends Number> GenCons(T arg) {
val = arg.doubleValue();
}
o Type Parameter: <T extends Number> indicates that the constructor is
generic and can accept any type that extends Number.
o Constructor Logic: val = arg.doubleValue(); converts the given argument to a
double and assigns it to the instance variable val.
2. Class GenCons:
o Field: private double val; stores the converted value.
o Method showval(): Prints the value of val.
3. Class GenConsDemo:
o Object Creation:
GenCons test = new GenCons(100); // Integer argument
GenCons test2 = new GenCons(123.5F); // Float argument
test is created with an Integer value 100.
test2 is created with a Float value 123.5F.
Method Calls:
test.showval();
test2.showval();
These calls print the values stored in val for each GenCons object.
Output
val: 100.0
val: 123.5
This output shows that the constructor correctly handles different numeric types and converts
them to double.
Generic Interface Declaration:
interface MinMax<T extends Comparable<T>> {
T min();
21
Advanced Java Programming 21CS642
T max();
}
o T extends Comparable<T>: This means that the type parameter T must
implement the Comparable<T> interface, which is used for comparing objects
of type T.
o Methods: The MinMax interface declares two methods, min() and max(),
which are expected to return the minimum and maximum values of the
collection.
2. Implementation of the Generic Interface:
class MyClass<T extends Comparable<T>> implements MinMax<T> {
T[] vals;
MyClass(T[] o) {
vals = o;
}
public T min() {
T v = vals[0];
for (int i = 1; i < vals.length; i++)
if (vals[i].compareTo(v) < 0) v = vals[i];
return v;
}
public T max() {
T v = vals[0];
for (int i = 1; i < vals.length; i++)
if (vals[i].compareTo(v) > 0) v = vals[i];
return v;
}
}
o MyClass<T extends Comparable<T>>: This class implements the
MinMax<T> interface and provides concrete implementations for the min()
and max() methods.
22
Advanced Java Programming 21CS642
o Constructor: MyClass(T[] o) initializes the vals array with the given array of
type T.
o min() and max() Methods: These methods iterate through the array to find
and return the minimum and maximum values, respectively.
3. Using the Generic Interface and Implementation:
class GenIFDemo {
public static void main(String args[]) {
Integer inums[] = {3, 6, 2, 8, 6};
Character chs[] = {'b', 'r', 'p', 'w'};
23
Advanced Java Programming 21CS642
24
Advanced Java Programming 21CS642
25
Advanced Java Programming 21CS642
26
Advanced Java Programming 21CS642
// A subclass of Gen.
class Gen2<T> extends Gen<T> {
Gen2(T o) {
super(o);
}
}
Explanation:
o Gen<T> is a generic class with a type parameter T.
o Gen2<T> extends Gen<T> and must also use the type parameter T to match
the superclass.
When you create an instance of Gen2, the type parameter T specified in Gen2 is passed to
Gen, ensuring type consistency.
Gen2<Integer> num = new Gen2<>(100);
In this case, Integer is passed as the type parameter T, so ob in Gen will be of type Integer.
Example: Generic Subclass Adding More Type Parameters
// A subclass can add its own type parameters.
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getob() {
return ob;
}
}
27
Advanced Java Programming 21CS642
super(o);
ob2 = o2;
}
V getob2() {
return ob2;
}
}
28
Advanced Java Programming 21CS642
}
}
// A generic subclass.
class Gen<T> extends NonGen {
T ob;
Gen(T o, int i) {
super(i);
ob = o;
}
T getob() {
return ob;
}
}
29
Advanced Java Programming 21CS642
class HierDemo3 {
public static void main(String args[]) {
// Create a Gen object for Integers.
Gen<Integer> iOb = new Gen<Integer>(88);
// Create a Gen2 object for Integers.
Gen2<Integer> iOb2 = new Gen2<Integer>(99);
// Create a Gen2 object for Strings.
Gen2<String> strOb2 = new Gen2<String>("Generics Test");
30
Advanced Java Programming 21CS642
31
Advanced Java Programming 21CS642
// Illegal
Gen<Long> g2 = (Gen<Long>) iOb2;
The cast (Gen<Integer>) iOb2 is legal because iOb2 is indeed an instance of
Gen<Integer>.
The cast (Gen<Long>) iOb2 is illegal because iOb2 is not an instance of Gen<Long>;
it's specifically a Gen<Integer>.
Summary
The instanceof operator can be used with generics, but you can only check for the
existence of generic types with wildcards (?), not specific generic types.
Generic type information is erased at runtime, so checking against specific generic
parameters is not possible.
Casting between generic types requires that the cast be consistent with the actual type
of the object.
Type Inference with Generics
Since JDK 7, Java has supported a shorthand syntax for creating instances of generic types,
known as the diamond operator (<>). This allows the compiler to infer the type arguments
based on the context, reducing verbosity and making code easier to read.
Example: Diamond Operator
Consider the generic class:
class MyClass<T, V> {
T ob1;
V ob2;
32
Advanced Java Programming 21CS642
// Usage
if (mcOb.isSame(new MyClass<>(1, "test"))) {
System.out.println("Same");
33
Advanced Java Programming 21CS642
}
In this example, new MyClass<>(1, "test") uses the diamond operator, and the type
arguments for MyClass are inferred from the method parameter.
Erasure
Java generics use type erasure to maintain compatibility with older versions of Java. This
means that generic type information is removed during compilation. Instead of using the
generic type parameters, the compiler replaces them with their bound types (e.g., Object if no
specific bound is set) and inserts appropriate casts.
How Erasure Works
1. Type Parameters Replaced: Generic type parameters are replaced by their bounds or
Object if no bounds are specified.
2. Casting: The compiler inserts casts to ensure type safety.
3. Compatibility: This process ensures that generic code is compatible with older, non-
generic code.
Because of type erasure, generic type parameters do not exist at runtime, which affects
operations like type checks.
Bridge Methods
To handle situations where type erasure results in a mismatch between overridden methods in
subclasses, the Java compiler generates bridge methods. These are synthetic methods that
bridge the gap between the type-erased method signatures of the superclass and the specific
signatures in the subclass.
Example: Bridge Method
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getob() {
return ob;
}
}
34
Advanced Java Programming 21CS642
@Override
String getob() {
System.out.print("You called String getob(): ");
return ob;
}
}
class BridgeDemo {
public static void main(String[] args) {
Gen2 strOb2 = new Gen2("Generics Test");
System.out.println(strOb2.getob());
}
}
In this example, Gen2 extends Gen<String> and overrides getob() to return a String. Due to
type erasure, the JVM expects getob() to return Object. To ensure compatibility, the compiler
generates a bridge method:
java.lang.String getob(); (specific to Gen2)
java.lang.Object getob(); (bridge method to handle type erasure)
This bridge method allows polymorphic calls to work correctly with the type-erased
superclass methods.
Ambiguity Errors in Generics
When working with generics in Java, ambiguity errors can arise due to type erasure. These
errors occur when different generic declarations resolve to the same erased type, leading to
conflicts in method resolution. Here's a deeper look into this issue:
Example of Ambiguity with Method Overloading
Consider the following class with generic type parameters:
class MyGenClass<T, V> {
35
Advanced Java Programming 21CS642
T ob1;
V ob2;
// Overloaded methods
void set(T o) {
ob1 = o;
}
void set(V o) {
ob2 = o;
}
}
In this example, MyGenClass has two overloaded set() methods, one taking a parameter of
type T and the other of type V. However, ambiguity issues can arise due to type erasure:
1. Type Erasure Conflict:
o Java's type erasure process converts all generic type parameters to their bound
type or Object if no bound is specified. For both set(T o) and set(V o), the
erased signature is void set(Object o). This makes it impossible to distinguish
between the two methods at runtime, causing ambiguity.
2. Instance Creation Example:
MyGenClass<String, String> obj = new MyGenClass<String, String>();
In this case, both T and V are String, so both set() methods effectively become void set(String
o), further exacerbating the ambiguity.
3. Attempt to Restrict Type Parameters:
class MyGenClass<T, V extends Number> {
//...
}
Even with a restriction like V extends Number, ambiguity can still occur if both type
parameters end up being the same bound type.
MyGenClass<Number, Number> x = new MyGenClass<Number, Number>();
// Ambiguous method call
36
Advanced Java Programming 21CS642
A practical solution is to use different method names rather than overloading them.
Overloading with generics often requires careful consideration and can sometimes indicate a
design issue.
Generic Restrictions
There are several important restrictions when using generics in Java:
1. Cannot Instantiate Type Parameters: You cannot create an instance of a type
parameter directly:
class Gen<T> {
T ob;
Gen() {
ob = new T(); // Illegal
}
}
Since T is a placeholder, the compiler cannot know what specific object to create.
2. Static Members Restrictions: You cannot declare static members using type
parameters of the enclosing class:
class Wrong<T> {
static T ob; // Illegal
static T getob() { // Illegal
return ob;
}
}
However, static generic methods with their own type parameters are allowed.
3. Generic Arrays:
You cannot create an array of a generic type parameter:
class Gen<T extends Number> {
T vals[];
Gen(T o, T[] nums) {
ob = o;
// vals = new T[10]; // Illegal
vals = nums; // Legal
}
37
Advanced Java Programming 21CS642
}
You can declare an array of references to a wildcard generic type:
Gen<?> gens[] = new Gen<?>[10]; // Legal
You cannot create an array of a specific generic type:
// Gen<Integer> gens[] = new Gen<Integer>[10]; // Illegal
4. Generic Exception Restriction:
You cannot create generic exceptions:
class MyException<T> extends Exception { // Illegal
}
This restriction is because exceptions are handled differently in Java and cannot be
parameterized.
38