- What are Generics?
- How Generics Improve Type Safety – What if we use Object
- Bounded Types
- Using Wildcard Arguments
- Generic Methods
- Generic Constructors
- Generic Interfaces
- Raw Types and Legacy Code
- Generic Class Hierarchies
- Erasure
- Generics Restrictions
What are Generics?
- parameterized types. (generic class or generic method)
- creating classes, interfaces, and methods that will work in a type-safe manner with various kinds of data.
- all type-casts are automatic and implicit.
- Many algorithms are same irrespective of data type, stack is same for Integer, String, Object, or Thread.
- collection classes can now be used with complete type safety.
- Java compiler creates only one Gen and substitutes necessary casts, to behave as specific version of Gen were created.
- process of removing generic type information is called erasure.
class Gen<T> {
T ob;
Gen(T o) {ob = o;}
T getob() {return ob;}
void showType() {
sysout(ob.getClass().getName());
}
}
Gen<Integer> iOb = new Gen<Integer>(88);
iOb = new Gen<Double>(88.0); // Error!
Generics Work Only with Reference Types
- cannot use a primitive type, int or char.
- iOb and strOb of type Gen<T>, but not type compatible with each other.
How Generics Improve Type Safety – What if we use Object
- First, explicit casts must be employed to retrieve the stored data.
- Second, many kinds of type mismatch errors cannot be found until run time.
int v = (Integer) iOb.getob();
iOb = strOb; // compiles, conceptually wrong!
v = (Integer) iOb.getob(); // run-time error!
class_name<type_arg_list>
var_name = new class_name<type_arg_list>(constructor_arg_list);
class TwoGen<T,V>{
T ob1;
V ob2;
TwoGen(T o1, V o2) {
ob1 = o1;
ob2 = o2;
}
}
TwoGen<Integer,String> tgObj = new TwoGen<Integer,String>(88, "Generics");
TwoGen<String,String> x = new TwoGen<String,String>("A", "B");
Bounded Types
class Stats<T extends Number> { // Number or its subclass
T[] nums; // array of Number or subclass
Stats(T[] o) { nums = o; }
double average() { // Return type double in all cases.
double sum = 0.0;
for (int i = 0; i < nums.length; i++)
sum += nums[i].doubleValue();
return sum / nums.length;
}
}
- limit or bound types that can be passed to type parameter.
- For example, to calculate average of an array of numbers, any type of number (integers, float, double).
- <T extends superclass> sets upper limit
Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Stats<Double> dob = new Stats<Double>(dnums);
System.out.println("dob average() - " + dob.average());
Integer inums[] = { 1, 2, 3, 4, 5 };
Stats<Integer> iob = new Stats<Integer>(inums);
System.out.println("iob average() - " + iob.average());
Multiple Bounds
<T extends superClassName & Interface>
class Bound<T extends A & B> {
private T objRef;
public Bound(T obj){ this.objRef = obj; }
public void doRunTest(){ this.objRef.displayClass(); }
}
public class BoundedClass {
public static void main(String a[]) {
Bound<A> bea = new Bound<A>(new A());
bea.doRunTest();
}
}
- Multiple Bounds allowed.
- A and B – both interface
- A is class then B, C should be interfaces.
interface B {
public void displayClass();
}
class A implements B {
public void displayClass() {
sysout("Inside super class A");
}
}
Using Wildcard Arguments
- example, Stats class has a method sameAvg() to find if two Stats have arrays with same average, any numeric type. boolean sameAvg(Stats<?> ob)
boolean sameAvg(Stats<T> ob) { // This won't work!
if(average() == ob.average()) // this object (type T), ob is also type T. Will not work for int/long/double combination
return true;
return false;
}
// Not feasible with current method definition as both iob and dob needs to be of same types
System.out.println("iob sameAvg(dob) - " + iob.sameAvg(dob));
- Problem with code here, work only with other Stats of same type as the invoking object.
- To make a generic sameAvg() method, use the wildcard argument, ?, representing unknown type.
- wildcard does not affect what type of Stats objects can be created; this is governed by the extends clause.
boolean sameAvg(Stats<? extends Number> ob) {
if(average() == ob.average()) // Will work for int/long/double combination
return true;
return false;
}
Bounded Wildcards
- Wildcard arguments can be bounded.
- upper bound - extends (<? extends superclass>)
- lower bound - super (<? super subclass>)
- call to showXZY( ) on **Coords
** reference will give compile-time error, thus ensuring type safety.
class TwoD { int x, y; }
class ThreeD extends TwoD { int z; }
class FourD extends ThreeD { int t; }
class Coords<T extends TwoD> {
T[] coords;
Coords(T[] o) { coords = o; }
}
static void showXY(Coords<?> c) {...} // Valid for all 2D,3D,4D
static void showXYZ(Coords<? extends ThreeD> c) {...} // Valid for 3D,4D
Generic Methods
- can have generic method that uses one or more type parameters of its own.
- can create a generic method enclosed within a non-generic class, type parameters are declared before the return type.
- Generic methods can be either static or non-static.
class GenMethDemo { // Non-Generic Class with Generic method
// Determine if an object is in an array.
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;
}
public static void main(String args[]) {
Integer nums[] = { 1, 2, 3, 4, 5 };
if (isIn(2, nums))
System.out.println("2 is in nums");
String strs[] = { "one", "two", "three" };
if (isIn("two", strs))
System.out.println("two is in strs");
// if(isIn("two", nums)) // Oops! Won't compile! Types must be compatible.
}
}
- types of the arguments are automatically discerned, but can also be explicitly specified.
GenMethDemo.<Integer, Integer>isIn(2, nums)
Generic Constructors
- It is possible to have generic constructors, even if their class is not.
class GenCons {
private double val;
<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 test1 = new GenCons(100) test1.showval(); // 100.0
GenCons test2 = new GenCons(123.5F); test2.showval();
}
}
Generic Interfaces
- Generic interfaces are specified just like generic classes.
interface MinMax<T extends Comparable<T>> {
T min(); T max();
}
class MyClass<T extends Comparable<T>> implements MinMax<T> { // No need to define upper bound twice
T[] vals;
MyClass(T[] o) {
vals = o;
}
public T min() { // return type T
T min = vals[0];
for (int i = 1; i<vals.length; i++)
if (vals[i].compareTo(min) < 0)
min = vals[i];
return min;
}
}
- Once type parameter defined, it is passed to interface without modification.
class MyClass<T extends Comparable<T>> implements MinMax<T> {
class MyClass<T extends Comparable<T>> implements MinMax<T extends Comparable<T>> {// wrong!
- If class implements generic interface, then class must also be generic. It should takes a type parameter to pass to interface.
class MyClass implements MinMax<T> { // Wrong! MyClass doesn’t have a type parameter, cannot pass any to interface
class MyClass implements MinMax<Integer> { // OK but of no use as it’s no more generic
- Generic interface offers two benefits:
- First, it can be implemented for different types of data.
- Second, it allows you to put constraints (that is, bounds) on the types of data.
Raw Types and Legacy Code
- Pre-generics code must be compatible with generics.
- For transition to generics, Java allows a generic class to be used without any type arguments.
- This creates a raw type for the class compatible with legacy code, having no knowledge of generics.
- The main drawback to using the raw type is that the type safety of generics is lost.
- Due to potential errors, javac gives unchecked warnings on use of raw type as it might jeopardize type safety.
class Gen<T> {
T ob;
Gen(T o) { ob = o; }
T getob() { return ob; }
}
class RawDemo {
public static void main(String args[]) {
Gen<Integer> iOb = new Gen<Integer>(88);
Gen<String> strOb = new Gen<String>("Generics Test");
Gen raw = new Gen(new Double(98.6));
double d = (Double) raw.getob(); // Cast here is necessary because type is unknown.
// int i = (Integer) raw.getob(); // run-time error
strOb = raw; // OK, but potentially wrong
// String str = strOb.getob(); // run-time error
raw = iOb; // OK, but potentially wrong
// d = (Double) raw.getob(); // run-time error
}
}
- No type arguments, this creates a Gen object whose type T is replaced by Object.
Generic Class Hierarchies
Generic Superclass
class Gen<T> {
T ob;
Gen(T o) { ob = o; }
T getob() { return ob; }
}
class Gen2<T> extends Gen<T> { // defines type T
Gen2(T o) { super(o); }
}
class Gen2<T, V> extends Gen<T> { // defines type T and V both.
V ob2;
Gen2(T o, V o2) {
super(o); // Type arguments needed by generic superclass
ob2 = o2;
}
V getob2() { return ob2; }
}
- Gen2 does not use the type parameter T except to support the Gen superclass.
- A generic class can act as a superclass or be a subclass.
- Type arguments needed by a generic superclass must be passed up the hierarchy by all subclasses.
- Constructor arguments must be passed up a hierarchy.
Generic Subclass
- Non-generic class can have generic subclass.
class NonGen {
int num;
NonGen(int i) { num = i; }
int getnum() { return num; }
}
class Gen<T> extends NonGen {
T ob; // declare an object of type T
Gen(T o, int i) {
super(i); // super constructor as 1st statement
ob = o;
}
T getob() { return ob; }
}
// main() method
Gen<String> w = new Gen<String>("Hello", 47);
System.out.print(w.getob() + " ");
System.out.println(w.getnum());
Run-Time Type Comparisons Within a Generic Hierarchy
instanceof - returns true if an object is of the specified type or can be cast to the specified type.
class Gen<T> {
T ob;
Gen(T o) { ob = o; }
T getob() { return ob; }
}
class Gen2<T> extends Gen<T> {
Gen2(T o){super(o);}
}
class HierDemo3 {
public static void main(String args[]) {.
Gen<Integer> iOb = new Gen<Integer>(88);
Gen2<Integer> iOb2 = new Gen2<Integer>(99);
Gen2<String> strOb2 = new Gen2<String>("Generics Test");
if(iOb2 instanceof Gen2<?>) {sysout("iOb2 is instance of Gen2");} // true
if(iOb2 instanceof Gen<?>) {sysout ("iOb2 is instance of Gen");} // true
if(strOb2 instanceof Gen2<?>) {sysout ("strOb2 is instance of Gen2");} // true
if(strOb2 instanceof Gen<?>) {sysout ("strOb2 is instance of Gen");} // true
if(iOb instanceof Gen2<?>) {sysout ("iOb is instance of Gen2");} // false
if(iOb instanceof Gen<?>) {sysout ("iOb is instance of Gen");} // true
// if(iOb2 instanceof Gen2<Integer>) sysout ("iOb2 is instance of Gen2<Integer>");
// can't be compiled, generic type info does not exist at run time.
}
- wildcard enables instanceof to determine if iOb2 is an object of any type of Gen2.
Casting
- can cast one instance of a generic class into another only if the two are compatible and their type arguments are the same.
(Gen<Integer>) iOb2 // legal - because iOb2 includes an instance of Gen<Integer>.
(Gen<Long>) iOb2 // illegal - is not legal because iOb2 is not an instance of Gen<Long>.
Overriding Methods in a Generic Class
class Gen<T> {
T ob;
Gen(T o) { ob = o; }
T getob() {
sysout("Gen's getob():");
return ob;
}
}
class Gen2<T> extends Gen<T> {
Gen2(T o) { super(o); }
T getob() {
sysout("Gen2's getob(): ");
return ob;
}
}
Type Inference with Generics
- <>, diamond operator, to tell the compiler to infer the type arguments needed by the constructor in the new expression.
MyClass<Integer,String> mcOb = new MyClass<Integer,String>(98, "A String");
MyClass<Integer,String> mcOb = new MyClass<>(98, "A String");
boolean isSame(MyClass<T,V> o) {
return (ob1 == o.ob1 && ob2 == o.ob2) ? true : false;
}
if (mcOb.isSame(new MyClass<>(1, "test")))
System.out.println("Same");
Erasure
- compatibility with preexisting, non-generic code, is achieved through erasure.
How erasure works?
- On compilation, all generic type information is removed (erased).
- replaces type parameters with their bound type, Object by default, and apply appropriate casts.
- No type parameters exist at run time. They are simply a source-code mechanism.
Bridge Methods
- Needed for situation where type erasure in superclass and subclass does not produce same erasure method.
- A method is generated that uses type erasure of superclass, and calls the method with subclass type erasure.
- bridge methods only occur at the bytecode level, are not seen by you, and are not available for your use.
class Gen<T> { // situation creating a bridge method.
T ob;
Gen(T o) { ob = o; }
T getob() { return ob; }
}
class Gen2 extends Gen<String> {
Gen2(String o) { super(o); }
String getob() { return ob; }
}
class BridgeDemo {
public static void main(String args[]) {
Gen2 strOb2 = new Gen2("Generics Test");
System.out.println(strOb2.getob());
}
}
- Due to type erasure, the expected form of getob( ) will be Object getob() { // …
- compiler generates a bridge method that calls the String version.
- examine class file Gen2 by javap, you see below methods:
class Gen2 extends Gen<java.lang.String> {
Gen2(java.lang.String);
java.lang.String getob();
java.lang.Object getob(); // bridge method
}
Normally, same signature would cause an error, but because this does not occur in your source code, it does not cause a problem and is handled correctly by the JVM.
Ambiguity Errors
- Due to erasure, two distinct generic declarations resolve to same erased type, causing a conflict.
class MyGenClass<T, V> {
T ob1; V ob2;
// ambiguous, will not compile.
void set(T o) { ob1 = o; }
void set(V o) { ob2 = o; }
}
- There are two ambiguity problems here:
- T and V can be of same types.
- MyGenClass<String, String> obj = new MyGenClass<>()
- both versions of set( ) identical, an error.
- type erasure of set( ) reduces both versions to the following:
- void set(Object o) { // … both set() are inherently ambiguous.
- T and V can be of same types.
class MyGenClass<T, V extends Number> { // almost OK!
MyGenClass<String, Number> x = new MyGenClass<String, Number>();
MyGenClass<Number, Number> y = new MyGenClass<Number, Number>();
- Better to use two separate method names, rather than trying to overload set().
- Often, ambiguity can be resolved by restructuring the code as it generally means a conceptual error in your design.
Generics Restrictions
Type Parameters Can’t Be Instantiated
- T is a placeholder, not well-defined type.
class Gen<T> {
T ob;
Gen() { ob = new T(); } // Illegal!!!
}
Restrictions on Static Members
- No static member can use a type parameter declared by the enclosing class, static are class level but T is at instance level.
class Wrong<T> {
static T ob; // Wrong, no static variables of type T.
static T getob() { return ob; } // Wrong, no static method can use T.
// generic static method with its own type param is OK
static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y){...}
}
Generic Array Restriction
- cannot instantiate an array of type parameter.
- cannot create an array of type-specific generic references.
class Gen<T extends Number> {
T ob;
T[] vals;
Gen(T o, T[] nums) {
ob = o;
vals = nums;
// vals = new T[10]; // can't create an array of T
}
}
public static void main(String args[]) {
Integer n[] = { 1, 2, 3, 4, 5 };
Gen<Integer> iOb = new Gen<Integer>(50, n);
// Gen<Integer> gens[] = new Gen<Integer>[10]; // Wrong!
// Gen<Long> gensLong[] = new Gen<Long>[10]; // Wrong!
// gens[0] = gensLong[0]; // Wrong! this is why generic array of type is not allowed
Gen<?> gens[] = new Gen<?>[10]; // OK
}
- No direct type casting of arrays on type-erasure.
(int[]) Object[]
- You can create an array of references to a generic type if you use a wildcard.
Generic Exception Restriction
- A generic class cannot extend Throwable.
- you cannot create generic exception classes.