Site Search:

Generic Classes, Interfaces and Methods

<Back

Generic Classes

generic class Cup
generic class Cup

The syntax for generic class is to declare a formal type parameter in angle brackets after the name of the class, When you instantiate the class, you need to tell the compiler what type should be for that particular instance.

OCPJP>cat Box.java 
public class Box<T> {
  private T contents;
  public Box(T t) {
    contents = t;
  } 
  public T getContents() {
    return contents;
  }
  public void setContents(T t) {
    contents = t;
  }
  public static void main(String args[]) {
    Box<String> box = new Box<>("gift");
    System.out.println(box.getContents());

    Box<Integer> box2 = new Box<>(5);  //autoboxing
    System.out.println(box2.getContents() instanceof Integer);
  }
}
OCPJP>javac Box.java 
OCPJP>java Box
gift
true

In the above example, Box is a generic class which declares a type parameter T. When you create instance box, you tell compiler String is the type parameter T. When you create instance box2, you tell compiler Integer is the type parameter for that particular instance.

Generic classes can have multiple generic parameters:

OCPJP>cat Node.java 
public class Node<K, V>{
  private V value;
  private K key;
  private Node<K, V> next;
  public Node(K key, V value, Node<K, V> next) {
    this.key = key;
    this.value = value;
    this.next = next;
  }  
  public final K getKey() {return key;}
  public final V getValue() { return value;}
  public final Node<K, V> getNext() {return next;}
  public static void main(String[] args) {
    Node<String, String> first = new Node<>("a", "A", null);
    Node<String, String> node = new Node<>("b", "B", first);
    System.out.println(node.getValue()+" "+node.getNext().getValue());
    Node<Integer, Integer> a = new Node<>(1, 10, null);
    Node<Integer, Integer> b = new Node<>(2, 20, a);
    System.out.println(b.getValue()+" " + b.getNext().getNext());
  } 

}
OCPJP>javac Node.java 
OCPJP>java Node
B A
20 null

In the above example, Node have two generic parameters K stands for key and V stands for value. When we create instances first and node, we tell compiler the K is String and V is String. When we create instances a and b, We tell compiler the type parameters K and V are Integer and Integer.

Generic Interfaces

Similar to generic classes, generic interface can declare type parameter in angular brackets after the interface name.

When A class implementing the generic interface, it can specify the generic type in the class, or create a generic class or use raw type.

OCPJP>cat Birds.java 
/* java.lang.Comparable
public interface Comparable<T> {
  public int compareTo(T o);
}
*/
import java.util.*;
//specify the generic type in the class
class Chicken implements Comparable<Chicken> {
  private int weight;
  public Chicken(int weight) {this.weight = weight;}
  public int getWeight() {return weight;}
  @Override
  public int compareTo(Chicken c) {
    return this.weight > c.getWeight() ? 1 : (this.weight == c.getWeight() ? 0: -1);
  }
  @Override
  public String toString() {return weight + "";}
}
//create a generic class
//due to type erasure, t have to be cast to ComprableBird before getWeight() available in compareTo, 
//which is no better than raw type 
class ComparableBird<T> implements Comparable<T> {
  private int weight;
  public ComparableBird(int weight) {this.weight = weight;}
  public int getWeight() {return weight;}
  @Override
  public int compareTo(T t) {
    return this.weight > ((ComparableBird)t).getWeight() ? 1 : (this.weight == ((ComparableBird)t).getWeight() ? 0:-1);
  }
  @Override
  public String toString() {return weight + "";}
}
//create raw class
class RawBird implements Comparable {
  private int weight;
  public RawBird(int weight) {this.weight = weight;}
  public int getWeight() {return weight;}
  @Override
  public int compareTo(Object t) {
    return this.weight > ((RawBird)t).getWeight() ? 1 : (this.weight == ((RawBird)t).getWeight() ? 0: -1);
  }
  @Override
  public String toString() {return weight + "";}
}

public class Birds{
  public static void main(String args[]) {
    List<Chicken> list = new ArrayList<>();
    list.add(new Chicken(10));list.add(new Chicken(5));list.add(new Chicken(12));
    Collections.sort(list);
    System.out.println(list);

    List<ComparableBird<ComparableBird>> l = new ArrayList<>();
    l.add(new ComparableBird(15));l.add(new ComparableBird(14));l.add(new ComparableBird(6));
    Collections.sort(l);
    System.out.println(l); 
    
    List l2 = new ArrayList();
    l2.add(new RawBird(7)); l2.add(new RawBird(1)); l2.add(new RawBird(5));
    Collections.sort(l2);
    System.out.println(l2);
  }
}
OCPJP>javac Birds.java 
Note: Birds.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
OCPJP>java Birds
[5, 10, 12]
[6, 14, 15]
[1, 5, 7]
OCPJP>

In the above example, we used 3 ways to implement java.lang.Comparable interface, the first one is the recommended way, the 2nd and 3rd way are used here just to cover the legal syntax OCPJP will test.

Generic Methods

OCPJP often tests the syntax of generics on static methods because they just looks weird:

OCPJP>cat ToolBox.java
class Tool<T> {}
public class ToolBox<U> {
  public static <T> Tool<T> solve(T t) {
    System.out.println("try to solve problem " + t);
    return new Tool<T>();
  }
  public static <T> T desc(T t) {
    System.out.println("met a " + t);
    return t; 
  } 
  //public static U idle(U u) {}  //non-static type variable U cannot be referenced from a static context
  public static <T> void idle(T t){}

  public static void main(String...args) {
    System.out.println(ToolBox.desc("hammer problem"));
    System.out.println(ToolBox.solve("drill problem"));
    System.out.println(ToolBox.solve(10));
    System.out.println(ToolBox.<Integer>solve(10));
    ToolBox.<String>idle("xxx");
  }
}
OCPJP>javac ToolBox.java 
OCPJP>java ToolBox
met a hammer problem
hammer problem
try to solve problem drill problem
Tool@70dea4e
try to solve problem 10
Tool@5c647e05
try to solve problem 10
Tool@33909752
OCPJP>


In the above example, test points are:

  • For static generic methods, the formal parameter <T> have to be specified immediately before the return type of the method, even though the return type is void. This is because static methods aren't part of an instance that can declare the type.
  • You can call a generic method without specifying a type, like ToolBox.solve(10),  let the compiler figure it out from function parameter's type, or you can optionally specify the type ToolBox.<Integer>solve(10).  If you choose to do so, the type have to be immediately before the method name.