Java Collection Framework and Java Generics

  • Introduction of Collection Framework
  • Need for Collection Framework (with Example)
  • Problems with Array
  • Advantages of using the Collection Framework
  • Java Collection Hierarchy (Interfaces & Classes)
  • Collection Interfaces
  • Java List: ArrayList, LinkedList, Vector
  • Java Set: HashSet, LinkedHashSet, TreeSet
  • Java Deque: ArrayDeque, LinkedList
  • Iterators in Java Collections
  • Java Generics

Chapters (Clickable):
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast
class SampleClass {
	public static void main(String[] args) {
		Test<Integer> obj1 = new Test<Integer>(10);
		System.out.println(obj1.getObject());

		Test<String> obj2 = new Test<String>("AkashChauhan");
		System.out.println(obj2.getObject());
	}
}

//We use <T> to specify Parameter type
class Test<T> {

	// An object of type T is declared
	T a;

	// constructor
	Test(T b) {
		this.a = b;
	}

	T getObject() {
		return this.a;
	}

}

  • When creating 10000 variables, it is not good practice to create 1000 different individual variables
  • So array comes into the picture. An array can represent many values using a single variable.
  • Fix in Size -> It is compulsory to provide the size during compilation, or before using it.
  • Only Homogeneous -> Holds only homogeneous data type elements
  • We are always responsible for making our methods {Array is not based on some standard data structure, so readymade methods support is not there}
  • Growable in nature -> Not bound to fix the size at the start
  • Homogeneous + Hetrogeneoud {$Hetrogeneous Objects Collection}
  • Can use the Readymade methods from the standard DS
public class SampleClass {

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {
		ArrayList al = new ArrayList();
		al.add(1);
		al.add("abc");
		al.add(new String("def"));
		System.out.println(al);
	}

}
  1. A Collection represents a single unit of objects.
  2. The Collection interface (java.util.Collection) and Map interface (java.util.Map) are the two main “root” interfaces of Java collection classes.
  3. Java Collections can achieve all the operations you perform on data, such as searching, sorting, insertion, manipulation, and deletion, without writing boilerplate code.
  4. It provides readymade architecture/Classes/methods.

All these interfaces like list, set, queue and dequeu extend collection interface, that’s why they are called main components of collection framework.

  • List<T>
  • Set<T>
  • Queue<T>
  • Deque<T>
S.NoInterfaceDescription
1List<T>. It is an ordered collection of objects.
. In which duplicate values can be stored.
. Multiple Null Values are allowed.
2Set<T>. It is an unordered collection of objects.
. In which duplicate values cannot be stored, at most one null value.
3Queue<T>. Ordered, FIFO, Allows duplicates, Allows multiple null values.
. Insertion of an element from the end and deletion from the beginning.
4Deque<T>. It is also known as Array Double Ended Queue or Array Deck.
. Elements can be inserted and deleted from both of the ends.
  • ArrayList<T>, LinkedList<T>, Stack<T> and Vector<T> are the four implementation classes of List<T> interface
  • ArrayList<T> and LinkedList<T> are mainly used
S.NoImplementation
Classes
Description
1ArrayList<T>. The ArrayList class is a re-sizable array.
. In ArrayList accessing an element takes constant time O(1) {Fast random access} and adding an element takes O(n) time in the worst case(adding an item at the first position).
. Adding elements in between would cost more space, as shifting the rest of the elements would take place.
2LinkedList<T>. LinkedList adding an element takes O(n) time(at the end of the list) and accessing also takes O(n) time.
. LinkedList uses more memory than ArrayList because of extra overhead for the next and previous pointers for each node in the linked list.
. It is not synchronized.
public class SampleClass {

	public static void main(String[] args) {
		List<Integer> l1 = new ArrayList<Integer>();
		l1.add(0, 1); 
		l1.add(1, 2); 
		l1.add(2, 2);
		l1.add(3, null);
		l1.add(4, null);
		
		System.out.println(l1);

		// Creating another list
		List<Integer> l2 = new ArrayList<Integer>();
		l2.add(3);
		l2.add(4);
		l2.add(5);

		// Will add list l2 from 5 index
		l1.addAll(5, l2);
		System.out.println(l1);

		// Removes element from index 1
		l1.remove(2);
		System.out.println(l1);

		// Print element at index 3
		System.out.println(l1.get(3));

		// Replace 0th element with 5
		l1.set(0, 5);
		System.out.println(l1);
	}

}
public class SampleClass {

	public static void main(String[] args) {
		
		// Creating a list
		List<Integer> l1 = new LinkedList<Integer>();
		l1.add(0, 1); 
		l1.add(1, 2); 
		System.out.println(l1);

		// Creating another list
		List<Integer> l2 = new LinkedList<Integer>();
		l2.add(1);
		l2.add(2);
		l2.add(3);

		// Will add list l2 from 1 index
		l1.addAll(1, l2);
		System.out.println(l1);

		// Removes element from index 1
		l1.remove(1);
		System.out.println(l1); // [1, 2, 3, 2]

		// Prints element at index 3
		System.out.println(l1.get(3));

		// Replace 0th element with 5
		l1.set(0, 5);
		System.out.println(l1);
	}
}
  • HashSet<T>, LinkedHashSet<T>, and TreeSet<T> are implementation classes of the Set<T> interface.
  • HashSet<T> and LinkedHashSet<T> are mainly used.
S.NoImplementation
Classes
Description
1HashSet<T>. It uses HashMap.
. It does not guarantee that the Insertion order will remain constant over time.
2LinkedHashSet<T>. It uses a doubly-linked list.
. Preserve insertion order.
public class SampleClass {

	public static void main(String[] args) {

		Set<String> hash_Set = new HashSet<String>();

		hash_Set.add("Amit");
		hash_Set.add("For");
		hash_Set.add("Amit");
		hash_Set.add("Example");
		hash_Set.add("Set");
		hash_Set.add(null);
		hash_Set.add(null);/* It would not make sense, as at most one null element is allowed */

		System.out.println("Set output without the duplicates");
		System.out.println(hash_Set);

		hash_Set.remove(null);

		System.out.println("\nSorted Set after passing into TreeSet");
		Set<String> tree_Set = new TreeSet<String>(hash_Set);
		System.out.println(tree_Set);
	}

}
public class SampleClass {

	public static void main(String[] args) {

		Set<String> hash_Set = new LinkedHashSet<String>();

		hash_Set.add("Amit");
		hash_Set.add("For");
		hash_Set.add("Amit");
		hash_Set.add("Example");
		hash_Set.add("Set");
		hash_Set.add(null);
		hash_Set.add(null);/* It would not make sense, as at most one null element is allowed */

		System.out.println("Set output without the duplicates");
		System.out.println(hash_Set);

		hash_Set.remove(null);

		System.out.println("\nSorted Set after passing into TreeSet");
		Set<String> tree_Set = new TreeSet<String>(hash_Set);
		System.out.println(tree_Set);
	}

}
  • PriorityQueue<T>, LinkedList<T>, PriorityBlockingQueue<T> and LinkedBlockingQueue<E> are four implementation classes of Queue interface.
  • PriorityQueue<T> and LinkedList<T> are mainly used implementation classes of the queue.
S.NoImplementation
Classes
Description
1PriorityQueue<T>. Order not preserved, allows duplicates, no null values, not thread-safe.
. It does not follow the FIFO order and store elements based on their priority.
. Priorities can be set using the ‘Comparable Interface’ and its compareTo() method.
2LinkedList<T>. Not thread-safe, Allows duplicates, Allows multiple null values, preserved order
1. Code for PriorityQueue<T>
public class SampleClass {

	public static void main(String[] args) {
		Queue<Integer> q = new PriorityQueue<Integer>();

		// Adds elements {0, 1, 2, 3, 4} to queue
		for (int i = 0; i < 5; i++)
			q.add(i);

		q.add(2);

		// Display contents of the queue.
		System.out.println("Elements of queue-" + q);

		// To remove the head of queue.
		int removedEle = q.remove();
		System.out.println("removed element-" + removedEle);

		System.out.println(q);

		// To view the head of queue
		int head = q.peek();
		System.out.println("head of queue-" + head);

		//Size of the queue
		int size = q.size();
		System.out.println("Size of queue-" + size);
	}

}
public class SampleClass {

	public static void main(String[] args) {
		Queue<Integer> q = new LinkedList<Integer>();

		// Adds elements {0, 1, 2, 3, 4} to queue
		for (int i = 0; i < 5; i++)
			q.add(i);

		q.add(2);

		// Display contents of the queue.
		System.out.println("Elements of queue-" + q);

		// To remove the head of queue.
		int removedEle = q.remove();
		System.out.println("removed element-" + removedEle);

		System.out.println(q);

		// To view the head of queue
		int head = q.peek();
		System.out.println("head of queue-" + head);

		// Size of the queue
		int size = q.size();
		System.out.println("Size of queue-" + size);
	}

}
  • ArrayDeque<T> and LinkedList<T> are the two main implementation classes of the deque interface.
  • ArrayDeque<T> is the deque interface’s main implementation class.
S.NoImplementation
Classes
Description
1ArrayDeque<T>. Faster than linked list due to the array-based implementation
. not thread-safe (not synchronized)
. can not contain null values.
. follow contagious memory allocation
2LinkedList<T>. Uses a doubly-linked list as its internal data structure.
. can contain null values
. extra memory overhead due to previous and next pointer
. it is also not thread-safe
public class DequeueUsingArrayDequeue {

	public static void main(String[] args) {

		Deque<Integer> de_que = new ArrayDeque<Integer>();

		//By Default FIFO order
		de_que.add(10);
		de_que.add(20);
		de_que.add(30);
		de_que.add(40);
		de_que.add(50);

		for (Integer element : de_que) {
			System.out.print(element);
			System.out.print(",");
		}
		System.out.print("\n");

//		Clearing all elements using clear() method
		de_que.clear();
//
		// Inserting at the start
		de_que.addFirst(564);
		de_que.addFirst(291);

		// Inserting at end
		de_que.addLast(24);
		de_que.addLast(14);

		for (Iterator itr = de_que.iterator(); itr.hasNext();) {
			System.out.println(itr.next());
		} 
	}

}
import java.util.*;

public class IteratorExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        Iterator<String> iterator = names.iterator();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
  • Take a list of integers as input.
  • Print all the elements in the forward direction using Iterator.
  • Reverse the list using ListIterator and print it.
  • Design a generic class named Pair that can hold a pair of values of any two types. The class should accept two type parameters, T and U, and store two fields accordingly. Include a constructor to initialize these fields and provide public getter methods getFirst(), and getSecond() to access the values. This exercise reinforces the concept of generic classes in Java and demonstrates how generics improve code reusability and type safety.


2 Comments.

  1. Thank you so much for your feedback! I’m delighted to hear that you find the notes helpful and easy to understand. I’m also glad that you’re enjoying the YouTube playlist on Java. Your appreciation motivates me to keep creating more valuable content.

Leave a Reply

Your email address will not be published. Required fields are marked *