Background information to understand generic classes

  • The importance of data type information in programming:

    • The data type of a variable is of utmost importance to the Java compiler when it translates the program statements

    • Some uses:

      • The data type is used to determine if an operation is permitted:

         int a;
         double b;
        
         a = b;   // Not permitted without casting
        

      • The data type can determine the meaning of an operation:

         int a, b;          a + b    means: addition
        
         String a, b;       a + b    means: concatenation
        

Intro to generic classes

  • Consider the following class that stores a String object:

    public class StringStore 
    {
       private String data;     // variable of String type
    
       public StingStore(String data) 
       {
          this.data = data;
       }
    
       public String getData()  // return String type
       {
          return this.data;
       }
    } 

  • We can store Strings in a StringStore object:

     StringStore a = new StringStore("Hello");
    
     System.out.println(a.getData());               --> Hello
     System.out.println(a.getData() + a.getData()); --> HelloHello

DEMO: 06-generics/01-intro/Demo.java + StringStore.java

Intro to generic classes

  • Ths StringStore class can only store String objects:

    public class StringStore 
    {
       private String data;     // variable of String type
    
       public StingStore(String data) 
       {
          this.data = data;
       }
    
       public String getData()  // return String type
       {
          return this.data;
       }
    } 

  • If we try to store an Integer, we get a compile error:

     StringStore a = new StringStore(12345);
    
    

DEMO: 06-generics/01-intro/Demo1.java + StringStore.java

Intro to generic classes

  • Consider the another class that stores a Integer object (using a wrapper class):

    public class IntegerStore 
    {
       private Integer data;    // variable of Integer type
    
       public IntegerStore(Integer data) 
       {
          this.data = data;
       }
    
       public Integer getData() // return Integer type 
       {
          return this.data;
       }
    } 

  • We can use IntegerStore class to store Integer objects:

     IntegerStore a = new IntegerStore(12345);
    
     System.out.println(a.getData());               --> 12345
     System.out.println(a.getData() + a.getData()); --> 24690

DEMO: 06-generics/01-intro/Demo2.java + IntegerStore.java

  Can we write 1 class to store every possible object type ???  

  • Observations:

    • The classes StringStore and IntegerStore store object (reference) types

    • The code look very very very similar...

  • Question:

    • Can we write 1 class to handle storage of every possible object type without losing the data type information ???

      (Afterall, every object type is a reference type)

Review: the Object class

  • The designers of the Java programming language has created the following inheritance hierachy:

     

    • Every class in Java is descended from one special class called the Object class

      I.e.: the Object class is the parent class of every class in any Java program

    • Therefore:

      • We can upcast any class to Object !!!

Intro to generic classes

  • Idea:   use the Object class since every class is a subclass of Object:

    public class ObjectStore 
    {
       private Object data;    // variable of Object type
    
       public ObjectStore(Object data) // Can receive any ref type
       {
          this.data = data;
       }
    
       public Object getData()  // return Object type 
       {
          return this.data;
       }
    } 

  • The output of this program is:

     ObjectStore a = new ObjectStore("Hello");
     ObjectStore b = new ObjectStore(12345);
    
     System.out.println(a.getData());  --> Hello // Work !
     System.out.println(b.getData());  --> 12345

DEMO: 06-generics/01-intro/Demo3.java + ObjectStore.java
Looks good ????   try Demo4.java !!

Intro to generic classes

  • Idea:   use the Object class since every class is a subclass of Object:

    public class ObjectStore 
    {
       private Object data;    // variable of Object type
    
       public ObjectStore(Object data) 
       {
          this.data = data;
       }
    
       public Object getData()  // return Object type 
       {
          return this.data;
       }
    } 

  • However: this program generate a bad operand types for + operator error:

     ObjectStore a = new ObjectStore("Hello");
     ObjectStore b = new ObjectStore(12345);
    
     System.out.println(a.getData() + a.getData()); // Error
     System.out.println(b.getData() + b.getData()); // Error

DEMO: 06-generics/01-intro/Demo4.java + ObjectStore.java

Why won't Demo4.java compile ???

  • Because the method getData() returns an Object type:

    public class ObjectStore 
    {
       ... (abbreviate to save space)
    
       public Object getData()  // return Object type
       {
          return this.data;
       }
    } 

  • Demo4.java contains the expressions:

     a.getData() + a.getData() // getData() returns an Object type
     b.getData() + b.getData() // getData() returns an Object type

    Compile error because the + operation is not defined on the Object type !!

  • I.e.: we have lost the data type information (String a and Integer b) !!

How can you fix Demo4.java ???

  • Original content of Demo4.java:

    public class Demo4
    {
       public static void main(String[] args)
       {
          ObjectStore a = new ObjectStore("Hello"); // a String
          ObjectStore b = new ObjectStore( 12345 ); // an Integer
    
          System.out.println(             a.getData() 
                              +           a.getData()   ); 
          System.out.println(             b.getData() 
                              +           b.getData()   ); 
       }
    }
    

  • How can we fix Demo4.java ???

    I.e.: how can we add the data type information back to the code ???

    Hint: use casting !!

How can you fix Demo4.java ???

  • Solution:

    public class Demo4
    {
       public static void main(String[] args)
       {
          ObjectStore a = new ObjectStore("Hello"); // a String
          ObjectStore b = new ObjectStore( 12345 ); // an Integer
    
          System.out.println(   (String)  a.getData() 
                              + (String)  a.getData()   ); 
          System.out.println(   (Integer) b.getData() 
                              + (Integer) b.getData()   ); 
       }
    }
    

DEMO: demo/06-generics/01-intro/Demo5.java + ObjectStore.java

But can you fix Demo4.java automatically ???

  • Solution:

    public class Demo4
    {
       public static void main(String[] args)
       {
          ObjectStore a = new ObjectStore("Hello"); // a String
          ObjectStore b = new ObjectStore( 12345 ); // an Integer
    
          System.out.println(   (String)  a.getData() 
                              + (String)  a.getData()   ); 
          System.out.println(   (Integer) b.getData() 
                              + (Integer) b.getData()   ); 
       }
    }
    

  • $64,000 question:

    • Can we automate the casting operation ???

    Yes !!! with generic classes (or parameterized classes)

 

Generic class: parameterized classes

  • Generic class:

    • A generic class is a parameterized class where the parameters (plural) are always object types

  • Review:   how to define a non-generic class:

      public class ClassName
      {
          ....
      }

  • Syntax to define a generic (= parameterized) class:

      // T1, T2, .. are object type parameters
      public class ClassName<T1,T2,..> 
      {
        .... // We can use T1, T2, .. as type specifier here
      }

Example of a generic (parameterized) class

  • Example of a generic class:

    public class GenericStore<T>  // T is the type parameter
    {
       private T data;    // variable of T type
    
       public GenericStore(T data) 
       {
          this.data = data;
       }
    
       public T getData() // returns T type variable
       {
          return this.data;
       }
    }

  • Important facts:

    • The Java compiler will remember the places where the generic type parameter T were used

    • The generic type parameter T tells the Java compiler to place the correct cast operation at some result before using it.

What happens when the Java compiler processes a generic class

  • When the parameter type is <T>:

    public class GenericStore<T>  // T is the type parameter
    {
       private T data;    // variable of T type
    
       public GenericStore(T data) 
       {
          this.data = data;
       }
    
       public T getData() // returns T type variable
       {
          return this.data;
       }
    }

    The Java compiler will:

    • Replace every occurence of <T> by:   Object

What happens when the Java compiler processes a generic class

  • The resulting program class is as follows:

    public class GenericStore  // No parameter T !!!
    {
       private Object data;  // variable of Object type
    
       public GenericStore(Object data) 
       {
          this.data = data;
       }
    
       public Object getData() // returns Object type variable
       {
          return this.data;
       }
    }

    The resulting class will:

    • Store an object of any reference data type !!!

      (But we have lost the data type information !)

  • Remember:

    • The java compiler has also remembered where the type parameter <T> were !!!

How the Java compiler use a generic class

  • When you define an variable of a generic class, you specify the object type parameter along with the class name as follows:

      GenericsStore<String>  a = new GenericsStore<String>();
      GenericsStore<Integer> b = new GenericsStore<Integer>();
    

  • The result will be:

    • The Java compiler will remember the parameter type of each variable            and

    • Insert the proper casting operation before using the value returned by their methods !

    Example:

     Expression:         a.getData() + a.getData() 
    
     Because getData() returns type <T> and <T> = <String>
     Java compiler will insert (String):
    
             --> (String)a.getData() + (String)a.getData()

DEMO: demo/06-generics/01-intro/Demo6.java + GenericStore.java

Postscript

  1. The parameters of a generic class must be object (reference) types

    Therefore:

    • You cannot define generic class variables using primitive types:

       GenericStore<int> a = new GenericStore<int>(); // Illegal !

    • Use a wrapper class if you need to use a primitive type

  2. You can use this short hand notation to define a generic class variable:

       GenericStore<Integer> a = new GenericStore<>();

    The Java compiler can infer the second parameter 😆

  3. Commonly parameter names used are:   T (Type), E (Element), K (Key) and V (Value)

Post-postscript

  • Interfaces behaves like classes

  • You can also define generic (parameterized) interfaces

  • Example: the non-generic ComparableTing interface was:

    public interface ComparableThing
    {
        public int compareTo( ComparableThing x );
    }

  • When we parameterize the ComparableThing<T> with type T, we can use the type T to declare the parameter variable:

    public interface ComparableThing<T>
    {
        public int compareTo( T x );
    }

  • Implementing a parameterized interface is very tricky and beyond the scope of CS 171... --- see Chapter 19.5 in Liang's book
    (E.g.: selectionSort in demo/06-generics/02-comparable - not part of CS171)