Skip to content

Latest commit

 

History

History
232 lines (189 loc) · 7.19 KB

01. Introduction.md

File metadata and controls

232 lines (189 loc) · 7.19 KB

Generics

  • Prior to jdk 5, collection of any type consisted of collection of objects and downcasting was necessary to retrieve the element of correct type
class Main{
    public static void main(String[] args){
        List words = new ArrayList();
        words.add("Hello");
        words.add(" world!");
        String s = ((String)words.get(0)) + ((String)words.get(1));
        System.out.print(s);
    }
}
  • In jdk 5, generic parameters were added to the declaration of collection classes, so the above code can be rewritten as
class Main{
    public static void main(String[] args){
        List<String> words = new ArrayList();
        words.add("Hello");
        words.add(" world!");
        String s = words.get(0) + words.get(1);
        System.out.print(s);
    }
}

Benefits

  • Stronger type checks at compile time.
  • No downcasting
  • Reusable code for different data types

Type Erasure

  • Generics are implemented using Type Erasure approach where the compiler uses the generic type information to compile the code, but erases it afterwards.
  • Generic information is not available at runtime. This enables generic code to be backward compatible with the legacy code that uses raw types.
  • Once the compiler confirms that the generic type is used safely, it converts generic type to raw type.

Limitations

  • Generic Subtyping is not covariant.

    Manager is a subclass of Employee but ArrayList is not subclass of ArrayList

  • Array Subtyping is covariant.

    Manager is a subclass of Employee and Manager[] is subclass of Employee[]

    • Component type of an array is not allowed to be a type variable.
      class Main{
          public static void main(String[] args){
              T[] arr = null; // ok 
              arr = new T[5]; // compiler error
              T[] arr = (T[]) new Object[5]; // No Error
          }
      }
  • Component type of an array is not allowed to be a parametrized type.

          class Pair<T> {
            private T k;
            private T v;
        
            Pair(T k, T v) {
                this.k = k;
                this.v = v;
            }
        
            public T getK() {
                return k;
            }
        
            public T getV() {
                return v;
            }
        
            @Override
            public String toString() {
                return "Pair{" +
                        "k=" + k +
                        ", v=" + v +
                        '}';
            }
        }
      
      class Main{
        public static void main(String[] args){
          List<String>[] lst = new ArrayList<String>[5]; // compiler error
          Pair<Integer>[] a = new Pair<Integer>[10]; // compiler error
    
          List<String>[] lst = new ArrayList[5]; // ok
          Pair<Integer>[] a = new Pair[10]; // ok
          lst[0] = new ArrayList<>(){
                    {
                        add("hello");
                    }
                };
        
                lst[1] = new ArrayList<>(){
                    {
                        add("there");
                        add("hey");
                    }
          };
          
          a[0] = new Pair<Integer>(1,2);
          a[1] = new Pair<Integer>(2,4);
          
          System.out.println(Arrays.toString(lst));
          System.out.println(Arrays.toString(a));
        }
      }

    here, new new ArrayList<String>[5] is not allowed because Java does not allow the creation of arrays with a parameterized component type. however, new ArrayList[5] creates an array of the raw type ArrayList and raw type effectively disables generic type checking. thus this is allowed with unchecked warning.

Reifiable and Non-Reifiable Types

  • Reifiable types are those that have type information fully available at runtime. it includes primitives, non-generic types, raw types. String[] obj=new String[10]; will remain same in the runtime.
  • Non-Reifiable are those whose type informations are removed at compile time by type erasure. List<String> list=new ArrayList<String> becomes List list=new ArrayList() at runtime

Generic Methods

  • Generic methods are the methods that has their own type parameters.
class Main{
    
  public static <T> boolean contains1(List<T> list, T s){
    for(T x:list){
      if(x==null && s==null) return true;
      if(s== null || x==null) continue;
      if(x.equals(s)) return true;
    }
    return false;
  }

  public static void test(){
    List<String> list = Arrays.asList("Bob","Joe","Tom");
    System.out.println(Test.<String>contains1(list, "Tom")); // manually specifying type parameter
    System.out.println(Test.contains1(list, "Tom")); // type parameter inferred
  }


  public static void main(String[] args) {
    test();
  }
  
}

WildCards

  1. ? extends Bounded wildcard
    Since generic subtyping is not covariant, it is very inconvenient. this can be resolved by using bounded wildcards. if Manager is subclass of Employee then List<Manager> is subclass of List<? extends Employee>.

    Limitation: with ? extends, values can be gotten but not inserted.

    class Main{
      public static void main(String[] args){
        List<Integer> ints = new ArrayList<Integer>();
        ints.add(1);
        ints.add(2);
        List<? extends Number> nums = ints;
        nums.add(null); // ok null is ultimate subtype
        nums.add(3); //compiler error
      }      
    }
  2. ? super Bounded wildcard
    when super wildcard is used values can be inserted but cannot be gotten.

        class Main{
          public static void main(String[] args){
            List< ? super Integer> ints = new ArrayList<>();
            ints.add(1);
            ints.add(2);
            System.out.println(ints.get(1)); // OK
            Object value1 = ints.get(1); // ok Object is ultimate supertype
            Integer value = ints.get(1); // compiler error
          }      
        }

    Note: Do not use wildcard if you need to both get and put values.

  3. Unbounded wildcard
    The wildcard ? without the super or extends qualifier, a.k.a unknown type. The important usecase of unbounded wildcard is to capture wildcard. Collection<?> is an abbreviation for Collection<? extends Object>

    class Main{
    
      public static void reverse(List<?> list) {
        List<Object> tmp = new ArrayList<Object>(list);
        for (int i = 0; i < list.size(); i++) {
          list.set(i, tmp.get(list.size()-i-1)); // error
        }
      }
      
      public static void main(String[] args){
          
      }
    }

    Using helper method to capture the wildcard

    class Main{
      public static void reverse(List<?> list) {
        reverseHelper(list);
      }
    
      private static <T> void reverseHelper(List<T> list){
        List<T> tmp = new ArrayList<>(list);
        for (int i = 0; i < list.size(); i++) {
          list.set(i, tmp.get(list.size()-i-1));
        }
      }
    
      public static void main(String[] args) {
      }
    }

    Passing items into the helper method causes the unknown type ? to be “captured” as the type T. In the helper method, getting and setting values is legal because we are not dealing with wildcards in that method.