· What are Collection
related features in Java 8?
Java 8 has brought major changes in
the Collection API. Some of the changes are:
- Java Stream API for collection classes for supporting sequential as well as parallel processing
- Iterable interface is extended with forEach() default method that we can use to iterate over a collection. It is very helpful when used with lambda expressions because it’s argument Consumer is a function interface.
- Miscellaneous Collection API improvements such as forEachRemaining(Consumer action) method in Iterator interface, Map replaceAll(), compute(), merge() methods.
Collections are used in every
programming language and initial java release contained few classes for
collections: Vector, Stack, Hashtable, Array. But
looking at the larger scope and usage, Java 1.2 came up with Collections
Framework that group all the collections interfaces, implementations and
algorithms.
Java Collections have come through a long way with usage of Generics and Concurrent Collection classes for thread-safe operations. It also includes blocking interfaces and their implementations in java concurrent package.
Some of the benefits of collections framework are;
Java Collections have come through a long way with usage of Generics and Concurrent Collection classes for thread-safe operations. It also includes blocking interfaces and their implementations in java concurrent package.
Some of the benefits of collections framework are;
- Reduced development effort by using core collection classes rather than implementing our own collection classes.
- Code quality is enhanced with the use of well tested collections framework classes.
- Reduced effort for code maintenance by using collection classes shipped with JDK.
- Reusability and Interoperability
Java 1.5 came with Generics and all
collection interfaces and implementations use it heavily. Generics allow us to
provide the type of Object that a collection can contain, so if you try to add
any element of other type it throws compile time error.
This avoids ClassCastException at Runtime because you will get the error at compilation. Also Generics make code clean since we don’t need to use casting and instanceof operator. I would highly recommend to go through Java Generic Tutorial to understand generics in a better way.
This avoids ClassCastException at Runtime because you will get the error at compilation. Also Generics make code clean since we don’t need to use casting and instanceof operator. I would highly recommend to go through Java Generic Tutorial to understand generics in a better way.
Collection is the root of the collection hierarchy. A collection
represents a group of objects known as its elements. The Java platform doesn’t
provide any direct implementations of this interface.
Set is a collection that cannot contain duplicate elements. This
interface models the mathematical set abstraction and is used to represent
sets, such as the deck of cards.
List is an ordered collection and can contain duplicate
elements. You can access any element from it’s index. List is more like array
with dynamic length.
A Map
is an object that maps keys to values. A map cannot contain duplicate keys:
Each key can map to at most one value.
Collection interface specifies group
of Objects known as elements. How the elements are maintained is left up to the
concrete implementations of Collection. For example, some Collection
implementations like List allow duplicate elements whereas other
implementations like Set don’t.
A lot of the Collection implementations have a public clone method. However, it does’t really make sense to include it in all implementations of Collection. This is because Collection is an abstract representation. What matters is the implementation.
The semantics and the implications of either cloning or serializing come into play when dealing with the actual implementation; so concrete implementation should decide how it should be cloned or serialized, or even if it can be cloned or serialized.
So mandating cloning and serialization in all implementations is actually less flexible and more restrictive. The specific implementation should make the decision as to whether it can be cloned or serialized.
A lot of the Collection implementations have a public clone method. However, it does’t really make sense to include it in all implementations of Collection. This is because Collection is an abstract representation. What matters is the implementation.
The semantics and the implications of either cloning or serializing come into play when dealing with the actual implementation; so concrete implementation should decide how it should be cloned or serialized, or even if it can be cloned or serialized.
So mandating cloning and serialization in all implementations is actually less flexible and more restrictive. The specific implementation should make the decision as to whether it can be cloned or serialized.
Although Map interface and it’s
implementations are part of Collections Framework, Map are not collections and
collections are not Map. Hence it doesn’t make sense for Map to extend
Collection or vice versa.
If Map extends Collection interface, then where are the elements? Map contains key-value pairs and it provides methods to retrieve list of Keys or values as Collection but it doesn’t fit into the “group of elements” paradigm.
If Map extends Collection interface, then where are the elements? Map contains key-value pairs and it provides methods to retrieve list of Keys or values as Collection but it doesn’t fit into the “group of elements” paradigm.
Iterator interface provides methods
to iterate over any Collection. We can get iterator instance from a Collection
using iterator() method. Iterator takes the place of Enumeration in the
Java Collections Framework. Iterators allow the caller to remove elements from
the underlying collection during the iteration. Java Collection iterator
provides a generic way for traversal through the elements of a collection and
implements Iterator Design Pattern.
Enumeration is twice as fast as
Iterator and uses very less memory. Enumeration is very basic and fits to basic
needs. But Iterator is much safer as compared to Enumeration because it always
denies other threads to modify the collection object which is being iterated by
it.
Iterator takes the place of Enumeration in the Java Collections Framework. Iterators allow the caller to remove elements from the underlying collection that is not possible with Enumeration. Iterator method names have been improved to make it’s functionality clear.
Iterator takes the place of Enumeration in the Java Collections Framework. Iterators allow the caller to remove elements from the underlying collection that is not possible with Enumeration. Iterator method names have been improved to make it’s functionality clear.
The semantics are unclear, given
that the contract for Iterator makes no guarantees about the order of iteration.
Note, however, that ListIterator does provide an add operation, as it does
guarantee the order of the iteration.
It can be implemented on top of
current Iterator interface but since it’s use will be rare, it doesn’t make
sense to include it in the interface that everyone has to implement.
- We can use Iterator to traverse Set and List collections whereas ListIterator can be used with Lists only.
- Iterator can traverse in forward direction only whereas ListIterator can be used to traverse in both the directions.
- ListIterator inherits from Iterator interface and comes with extra functionalities like adding an element, replacing an element, getting index position for previous and next elements.
We can iterate over a list in two
different ways – using iterator and using for-each loop.
1
2
3
4
5
6
7
8
9
10
11
|
List<String> strList = new ArrayList<>();
//using for-each loop
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
|
Using iterator is more thread-safe
because it makes sure that if underlying list elements are modified, it will
throw ConcurrentModificationException.
Iterator fail-fast property checks
for any modification in the structure of the underlying collection everytime we
try to get the next element. If there are any modifications found, it throws ConcurrentModificationException. All the implementations of Iterator in Collection classes
are fail-fast by design except the concurrent collection classes like
ConcurrentHashMap and CopyOnWriteArrayList.
Iterator fail-safe property work
with the clone of underlying collection, hence it’s not affected by any
modification in the collection. By design, all the collection classes in java.util package are fail-fast whereas collection classes in java.util.concurrent are fail-safe.
Fail-fast iterators throw ConcurrentModificationException whereas fail-safe iterator never throws ConcurrentModificationException.
Check this post for CopyOnWriteArrayList Example.
Fail-fast iterators throw ConcurrentModificationException whereas fail-safe iterator never throws ConcurrentModificationException.
Check this post for CopyOnWriteArrayList Example.
We can use concurrent collection
classes to avoid ConcurrentModificationException while iterating over a collection, for example
CopyOnWriteArrayList instead of ArrayList.
Check this post for ConcurrentHashMap Example.
Check this post for ConcurrentHashMap Example.
Iterator interface declare methods
for iterating a collection but it’s implementation is responsibility of the
Collection implementation classes. Every collection class that returns an
iterator for traversing has it’s own Iterator implementation nested class.
This allows collection classes to chose whether iterator is fail-fast or fail-safe. For example ArrayList iterator is fail-fast whereas CopyOnWriteArrayList iterator is fail-safe.
This allows collection classes to chose whether iterator is fail-fast or fail-safe. For example ArrayList iterator is fail-fast whereas CopyOnWriteArrayList iterator is fail-safe.
UnsupportedOperationException is the exception used to indicate that the operation is not
supported. It’s used extensively in JDK
classes, in collections framework java.util.Collections.UnmodifiableCollection throws this exception for all add and remove
operations.
HashMap stores key-value pair in Map.Entry static nested class implementation. HashMap works on
hashing algorithm and uses hashCode() and equals() method in put and get
methods.
When we call put method by passing key-value pair, HashMap uses Key
hashCode() with hashing to find out the index to store the key-value pair. The
Entry is stored in the LinkedList, so if there are already existing entry, it
uses equals() method to check if the passed key already exists, if yes it
overwrites the value else it creates a new entry and store this key-value
Entry.
When we call get method by passing Key, again it uses the hashCode() to find
the index in the array and then use equals() method to find the correct Entry
and return it’s value. Below image will explain these detail clearly.
The other important things to know
about HashMap are capacity, load factor, threshold resizing. HashMap initial default
capacity is 32 and load factor is 0.75. Threshold is capacity multiplied by
load factor and whenever we try to add an entry, if map size is greater than
threshold, HashMap rehashes the contents of map into a new array with a larger
capacity. The capacity is always power of 2, so if you know that you need to
store a large number of key-value pairs, for example in caching data from
database, it’s good idea to initialize the HashMap with correct capacity and
load factor.
HashMap uses Key object hashCode()
and equals() method to determine the index to put the key-value pair. These
methods are also used when we try to get value from HashMap. If these methods
are not implemented correctly, two different Key’s might produce same
hashCode() and equals() output and in that case rather than storing it at
different location, HashMap will consider them same and overwrite them.
Similarly all the collection classes
that doesn’t store duplicate data use hashCode() and equals() to find
duplicates, so it’s very important to implement them correctly. The
implementation of equals() and hashCode() should follow these rules.
- If o1.equals(o2), then o1.hashCode() == o2.hashCode()should always be true.
- If o1.hashCode() == o2.hashCode is true, it doesn’t mean that o1.equals(o2) will be true.
We can use any class as Map Key,
however following points should be considered before using them.
- If the class overrides equals() method, it should also override hashCode() method.
- The class should follow the rules associated with equals() and hashCode() for all instances. Please refer earlier question for these rules.
- If a class field is not used in equals(), you should not use it in hashCode() method.
- Best practice for user defined key class is to make it
immutable, so that hashCode() value can be cached for fast performance.
Also immutable classes make sure that hashCode() and equals() will not
change in future that will solve any issue with mutability.
For example, let’s say I have a class MyKey that I am using for HashMap key.
1
2
3
4
5
6
7
8
9
10
11
12
|
//MyKey name argument passed is used for equals() and
hashCode()
MyKey key = new
MyKey("Pankaj"); //assume hashCode=1234
myHashMap.put(key, "Value");
// Below code will change the key hashCode() and equals()
// but it's location is not changed.
key.setName("Amit"); //assume new hashCode=7890
//below will return null, because HashMap will try to look
for key
//in the same index as it was stored but since key is
mutated,
//there will be no match and it will return null.
myHashMap.get(new
MyKey("Pankaj"));
|
- This is the reason why String and Integer are mostly used as HashMap keys.
Map interface provides three
collection views:
- Set keySet(): Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator’s own remove operation), the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.
- Collection values(): Returns a Collection view of the values contained in this map. The collection is backed by the map, so changes to the map are reflected in the collection, and vice-versa. If the map is modified while an iteration over the collection is in progress (except through the iterator’s own remove operation), the results of the iteration are undefined. The collection supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Collection.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
- Set: Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator’s own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
No comments:
Post a Comment