Monday, September 7, 2015

Java Generics


A Collection can contain any object, for example either a String or Integer. One would feel this is more flexible but this would introduce a type safety problem to your application. Standard Collections can be restricted to store only certain type of object. So Generics was introduced to solve this problem.

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add(1);

  • Above list can have only Strings
  • In the line highlighted in red, you are getting a error at compile time. 
  • Generics stops run-time errors at compile time.
  • Generics help us to have type safety.

Type Safety Violations

public class Container {
      
       Object object = null;
      
       public void add(Object obj) {
              object = obj;
       }
      
       public Object get() {
              return object;
       }
      
       public void print() {
              System.out.println("Container: ".concat((String)object));
       }

       public static void main(String[] args) {
              Container container = new Container();
              container.add("test");
             
              //Throws an error due violation of Java Static Typing.
              //The error is thrown within the print() method.
              container.add(123);
             
              container.print();
       }

}

The Class Cast exceptions is thrown at the run time.

How to Achieve Type Safety

In the below example you can achieve the Type Safety at a cost. That is you need to introduce separate containers for each type for example String and Object.  
 public class StringContainer {
      
       String object = null;
      
       public void add(String obj) {
              object = obj;
       }
      
       public String get() {
              return object;
       }
      
       public void print() {
              System.out.println("Container: ".concat(object));
       }

       public static void main(String[] args) {
              StringContainer container = new StringContainer ();
              container.add("test");
             
              //Identifies the Type Safety violation at compile time
              container.add(123);
             
              container.print();
       }
}

Above code solves the Type Safety problem but you have to do copy and paste operations to create new classes for each type.

To solve the conflict between  Type Safety and Boilerplate, you have to use Generics.
public class Container<T> {
      
       T object = null;
      
       public void add(T obj) {
              object = obj;
       }
      
       public T get() {
              return object;
       }
      
       public void print() {
              System.out.println("Container: " + object);
       }

       public static void main(String[] args) {
              Container<String> container = new Container<String>();
              container.add("test");
             
//Error: The method add(String) in the type Container<String> is not //applicable for the arguments (int)
              container.add(123);
             
              container.print();
       }

}

Generic Collections

Generics List with For Each Loop

import java.util.*;

public class MList {

       //Diamond Operator <>.
       //Diamond operator says don’t add the Generics in explicitly, just
       //infer the context
       List<Person> personList = new ArrayList<>();
      
       public MList() {
              personList.add(new Person("Mahesh"));
              personList.add(new Person("Earth"));
       }
      
       public List<Person> getPerson() {
              return personList;
       }
      
       public static void main(String[] args) {
              for (Person person : new MList().getPerson()) {
                     System.out.println(person.getName());
              }
       }
}

Sets

import java.util.*;

public class MList {

       Set<Person> personList = new HashSet<>();
      
       public MList() {
              Person mahesh = new Person("Mahesh");
              Person earth = new Person("Earth");
             
              personList.add(mahesh);
              personList.add(earth);
              personList.add(mahesh);
       }
      
       public Set<Person> getPerson() {
              return personList;
       }
      
       public static void main(String[] args) {
              for (Person person : new MList().getPerson()) {
                     System.out.println(person.getName());
              }
       }

}

Sets does not allow duplicates only unique elements.

Map

Example with two generic parameters
import java.util.*;

public class MList {

       Map<String, Person> personMap = new HashMap<>();
      
       public MList() {
              Person mahesh = new Person("Mahesh");
              Person earth = new Person("Earth");
             
              personMap.put("Mahesh", mahesh);
              personMap.put("Earch", earth);
              personMap.put("Mahesh", mahesh);
       }
      
       public Map<String, Person> getPersonMap() {
              return personMap;
       }
      
       public static void main(String[] args) {
for (Map.Entry<String, Person> entry : new MList().getPersonMap().entrySet()) {
                     System.out.println(entry);
              }
       }
}

Generic Classes and Interfaces

Implementing a Generic Type

public class AgeComparator implements Comparator<Person> {

       public int compare(Person left, Person right) {
              return Integer.compare(left.getAge(), right.getAge());
       }
      
       public static void main(String[] args) {
              Person mahesh = new Person("Mahesh", 34);
              Person earth = new Person("Earth", 25);
              Person down = new Person("Down", 28);
             
              List<Person> personList = new ArrayList<>();
              personList.add(mahesh);
              personList.add(earth);
              personList.add(down);
             
              for(Person person : personList) {
                     System.out.println(person.getAge());
              }
             
              Collections.sort(personList, new AgeComparator());
              System.out.println("------------");
              for(Person person : personList) {
                     System.out.println(person.getAge());
              }
       }
}

Passing a Parameter to a Generic Type

public class ReverseComparator<T> implements Comparator<T> {

       private final Comparator<T> delegateComparator;
      
       public ReverseComparator(Comparator<T> delegateComparator) {
              this.delegateComparator = delegateComparator;
       }
       public int compare(T left, T right) {
              return -1 * delegateComparator.compare(left, right);
       }
      
       public static void main(String[] args) {
       }
}

public class AgeComparator implements Comparator<Person> {

       public int compare(Person left, Person right) {
              return Integer.compare(left.getAge(), right.getAge());
       }
      
       public static void main(String[] args) {
              Person mahesh = new Person("Mahesh", 34);
              Person earth = new Person("Earth", 25);
              Person down = new Person("Down", 28);
             
              List<Person> personList = new ArrayList<>();
              personList.add(mahesh);
              personList.add(earth);
              personList.add(down);
             
              Collections.sort(personList, new ReverseComparator<Person>(new AgeComparator()));
              System.out.println("------------");
              for(Person person : personList) {
                     System.out.println(person.getAge());
              }
       }
}

Type Bounds

public class SortedPair <T extends Comparable<T>> {

       private final T first;
       private final T second;
      
       public SortedPair(T left, T right) {
              if(left.compareTo(right) > 0) {
                     first = left;
                     second = right;
              } else {
                     first = right;
                     secondleft;
              }
       }
      
       public T getFirst() {
              return first;
       }
      
       public T getSecond() {
              return second;
       }
      
       public static void main(String[] args) {
              SortedPair<Integer> pair1 = new SortedPair<Integer>(21, 32);
              System.out.println(pair1.getFirst());
              System.out.println(pair1.getSecond());
             
              SortedPair<Integer> pair2 = new SortedPair<Integer>(32, 21);
              System.out.println(pair2.getFirst());
              System.out.println(pair2.getSecond());
       }
}

Generics on Methods

public class AgeComparator implements Comparator<Person> {

       public int compare(Person left, Person right) {
              return Integer.compare(left.getAge(), right.getAge());
       }
      
       public static void main(String[] args) {
              Person mahesh = new Person("Mahesh", 34);
              Person earth = new Person("Earth", 25);
              Person down = new Person("Down", 28);
             
              List<Person> personList = new ArrayList<>();
              personList.add(mahesh);
              personList.add(earth);
              personList.add(down);
             
              Person youngestMember =
                           (Person) min(personList, new AgeComparator());
              System.out.println(youngestMember.getAge());
             
              youngestMember = minWithGenerics(personList, new AgeComparator());
              System.out.println(youngestMember.getAge());
             
              List<Integer> numbers = new ArrayList<Integer>();
              numbers.add(1);
              numbers.add(2);
              numbers.add(3);
              //Method Reference [Integer::compare]
              System.out.println(minWithGenerics(numbers, Integer::compare));
             
       }
      
       public static Object min(List values, Comparator comparator) {
              Object lowestElement = values.get(0);
              for(int i = 1; i < values.size(); i++) {
                     final Object element  = values.get(i);
                     if (comparator.compare(element, lowestElement) < 0) {
                           lowestElement = element;
                     }
              }
              return lowestElement;
       }
      
       public static <T> T minWithGenerics(List<T> values, Comparator<T> comparator) {
              T lowestElement = values.get(0);
              for(int i = 1; i < values.size(); i++) {
                     final T element  = values.get(i);
                     if (comparator.compare(element, lowestElement) < 0) {
                           lowestElement = element;
                     }
              }
              return lowestElement;
       }
}

Wildcards

The substitution principle 

arrays are covariant that is arrays are not type safe. list are not covariant.

Upper Bounded Wildcards

public class UpperBoundedWildcards {
    public static void main(String [] a) {
        Person manesh = new Person("Mahesh");
        Person hadoop = new Person("Hadoop");
        List<Person> personsList = new ArrayList<>();
        personsList.add(manesh);
        personsList.add(hadoop);
        printPersonList(personsList);

        Engineer tom = new Engineer("Tom");
        Engineer tim = new Engineer("Tim");
        List<Engineer> engineerList = new ArrayList<>();
        engineerList.add(tom);
        engineerList.add(tim);
        printPersonList_1(engineerList);
    }
    public static void printPersonList(final List<? extends Person> personList) {
        for(Person person : personList) {
            System.out.println(person.getName());
        }
    }
     public static <T extends Person> void printPersonList_1(final List<T> personList) {
        for(Person person : personList) {
            System.out.println(person.getName());
        }
    }
}
class Person {
    private String name;
    public Person(String name ) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
class Engineer extends Person {
    public Engineer(String name) {
        super(name);
    }
}

Lower Bounded Wildcards

public class LowerBoundedWildcards {
    public static void main(String [] a) {
        Person manesh = new Person("Mahesh");
        Person hadoop = new Person("Hadoop");
        List<Person> personsList = new ArrayList<>();
        personsList.add(manesh);
        personsList.add(hadoop);
        AddToList(personsList);

        Engineer tom = new Engineer("Tom");
        Engineer tim = new Engineer("Tim");
        List<Engineer> engineerList = new ArrayList<>();
        engineerList.add(tom);
        engineerList.add(tim);
        AddToList(engineerList);
    }
    public static void AddToList(final List<? super Engineer> list) {
        list.add(new Engineer("Test"));
    }
}

* SUPER for Adding and EXTENDS for iterating (getting)

? extends Vs. ? super

? extends

  1. Declare an upper bound for the type parameter.
  2. To get data out of the parameter 
  3. Covariance 
? super

  1. Declare an lower bound for the type parameter 
  2. To put data into the parameter 
  3. Contravariance  

Unbounded Wildcards 

public class UnboundedWildcards {
    public static void main(String arg[]) throws ClassNotFoundException{
        final Class<?> pluginClass = Class.forName("com.PluginClassName");
    }
}

Raw Types and Compatibility

N/A
Erasure

Reflections

N/A

Advanced Topics

N/A