Monday 29 June 2015

Generics in collections


Array List

Java's List interface (java.util.List) can be generified. In other words, instances of List can be given a type, so only instances of that type can be inserted and read from that List. Here is an example:
List<String> list = new ArrayList<String>;
This list is now targeted at only String instances, meaning only String instances can be put into this list. If you try to put something else into this List, the compiler will complain.
The generic type checks only exists at compile time. At runtime it is possible to tweak your code so that a String List has other objects that String's inserted. This is a bad idea, though.

Accessing a Generic List

You can get and insert the elements of a generic List like this:
List<String> list = new ArrayList<String>;

String string1 = "a string";
list.add(string1);

String string2 = list.get(0);
Notice how it is not necessary to cast the object obtained from the List.get() method call, as is normally necessary. The compiler knows that this List can only contain String instances, so casts are not necessary.

Iterating a Generic List

You can iterate a generic List using an iterator, like this:
List<String> list = new ArrayList<String>;

Iterator<String> iterator = list.iterator();

while(iterator.hasNext()){
  String aString = iterator.next();
}
Notice how it is not necessary to cast the object returned from the iterator.next() next call. Because theList is generified (has a type), the compiler knows that it contains String instances. Therefore it is not necessary to cast the objects obtained from it, even if it comes from its Iterator.
You can also use the new for-loop, like this:
List<String> list = new ArrayList<String>;

for(String aString : list) {
    System.out.println(aString);
}
Notice how a String variable is declared inside the parantheses of the for-loop. For each iteration (each element in the List) this variable contains the current element (current String).

Set

Java's Set interface (java.util.Set) can be generified. In other words, instances of Set can be given a type, so only instances of that type can be inserted and read from that Set. Here is an example:
Set<String> set = new HashSet<String>;
This set is now targeted at only String instances, meaning only String instances can be put into this set. If you try to put something else into this Set, the compiler will complain.
The generic type checks only exists at compile time. At runtime it is possible to tweak your code so that a String Set has other objects that String's inserted. This is a bad idea, though.

Adding Elements to a Generic Set

Adding elements to a generic Set is done using the add() method, just like you have always done:
Set<String> set = new HashSet<String>;

String string1 = "a string";
set.add(string1);

So what is the big difference? Well, if you try to add an element that is not a String instance, to the Set in the example above, the compiler will complain. That's a pretty nice extra type check to have.

Iterating a Generic Set
You can iterate a generic Set using an iterator, like this:
Set<String> set = new HashSet<String>;
    
Iterator<String> iterator = set.iterator();

while(iterator.hasNext()){
  String aString = iterator.next();
}
Notice how it is not necessary to cast the object returned from the iterator.next() next call. Because theSet is generified (has a type), the compiler knows that it contains String instances. Therefore it is not necessary to cast the objects obtained from it, even if it comes from its Iterator.
You can also use the new for-loop, like this:
Set<String> set = new HashSet<String>;

for(String aString : set) {
    System.out.println(aString);
}
Notice how a String variable is declared inside the parantheses of the for-loop. For each iteration (each element in the Set) this variable contains the current element (current String).

Map

Java's Map interface (java.util.Map) can be generified. In other words, you can set the specific type of both the keys and values in a generic Map instance. Here is an example:
Map<Integer, String> set = new HashMap<Integer, String>;
This Map can now only accept Integer instances as keys, and String instances as values.
The generic type checks only exists at compile time. At run time it is possible to tweak your code so that other instances can be inserted. This is a bad idea, though.

Accessing a Generic Map
Adding and getting elements to a generic Map is done using the put() and get() methods, just like you have always done:
Map<Integer, String> map = new HashMap<Integer, String>;

Integer key1   = new Integer(123);
String  value1 = "value 1";

map.put(key1, value1);

String value1_1 = map.get(key1);
So what is the big difference? Well, if you try to add a key, value pair that is not a Integer, String pair instance, to the Map in the example above, the compiler will complain. That's a pretty nice extra type check to have.

Also notice how it is not necessary to cast the String instance returned by the get() method. The compiler knows that this Map has String values, so casting is not necessary.

You can also use the new auto boxing features of Java 5 to make it easier to specify the Integer values, like this:
Map<Integer, String> map = new HashMap<Integer, String>;

Integer key1   = 123;
String  value1 = "value 1";

map.put(key1, value1);

//or

map.put(123, value1);


String value1_1 = map.get(123);
Iterating a Generic Map
Map has two collections you can iterate. The key Set and the value Set. Most often you iterate the key Setand access the values for each key via the Map.get() method.
Here are two examples:
Map<Integer, String> map = new HashMap<Integer, String>;

//... add key, value pairs to the Map

// iterate keys.
Iterator<Integer> keyIterator   = map.keySet().iterator();

while(keyIterator.hasNext()){
  Integer aKey   = iterator.next();
  String  aValue = map.get(aKey);
}


Iterator<String>  valueIterator = map.values().iterator();

while(valueIterator.hasNext()){
  String aString = valueIterator.next();
}
Notice how it is not necessary to cast the object returned from the iterator.next() next call. Because theMap is generified (has a type), the compiler knows that it contains Integer instances for keys, and Stringinstances for values. Therefore it is not necessary to cast the objects obtained from the Map, even if it comes from one of its Iterator's.
You can also use the new for-loop, like this:
Map<Integer, String> map = new HashMap<Integer, String>;

//... add key, value pairs to the Map

for(Integer aKey : map.keySet()) {
    String aValue = map.get(aKey);
    System.out.println("" + aKey + ":" + aValue);
}

for(String aValue : map.values()) {
    System.out.println(aValue);
}
Notice how an Integer and a String variable is declared inside the parantheses of each for-loop. For each iteration (each element in the Map's key set or value collection) this variable contains the current element (current Integer or String).


No comments:

Post a Comment