Algorithms and Data Structures in Java: From Theory to Practice
Understanding algorithms and data structures is the foundation upon which the career of any Java developer is built. Without this knowledge, it is difficult to write efficient, scalable, and maintainable code. In this article, we will break down the key data structures and algorithms you will use daily and show their implementation in Java with practical examples.
1. Basic Data Structures in the Java Collections Framework
Java offers a powerful collections framework that includes basic data structures. Let's look at the three most popular ones.
ArrayList — Dynamic Array
ArrayList is an implementation of a dynamic array. It automatically expands when new elements are added, making it an excellent choice for lists with an unknown size in advance.
import java.util.ArrayList;
public class ArrayListExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange");
System.out.println("Second element: " + fruits.get(1)); // Banana fruits.remove(0); System.out.println("Size after removal: " + fruits.size()); // 2 for (String fruit : fruits) { System.out.println(fruit); } }}Operation complexity: access by index — O(1), insertion/deletion in the middle — O(n).
HashMap — Hash Table
HashMap stores key-value pairs and provides nearly instant access to data by key.
import java.util.HashMap;
public class HashMapExample { public static void main(String[] args) { HashMap<String, Integer> ages = new HashMap<>(); ages.put("Anna", 25); ages.put("Ivan", 30); ages.put("Maria", 28);
System.out.println("Anna's age: " + ages.get("Anna")); // 25 // Checking for key existence if (ages.containsKey("Ivan")) { System.out.println("Ivan found!"); } // Iterating over all entries for (String name : ages.keySet()) { System.out.println(name + ": " + ages.get(name) + " years old"); } }}Operation complexity: insertion, search, deletion — average O(1).
LinkedList — Doubly Linked List
LinkedList consists of nodes, each storing references to the previous and next elements. Ideal for frequent insertions and deletions at the beginning or end of the list.
import java.util.LinkedList;
public class LinkedListExample { public static void main(String[] args) { LinkedList<Integer> queue = new LinkedList<>(); // Using as a queue (FIFO) queue.addLast(1); queue.addLast(2); queue.addLast(3); System.out.println("First in queue: " + queue.getFirst()); // 1 queue.removeFirst(); System.out.println("After removal: " + queue); // [2, 3] // Using as a stack (LIFO) queue.push(10); // add to the beginning queue.push(20); System.out.println("Stack top: " + queue.pop()); // 20 }}Operation complexity: insertion/deletion at beginning/end — O(1), access by index — O(n).
2. Sorting Algorithms in Java
Sorting is one of the most common tasks. Let's look at two classic implementations.
Bubble Sort
A simple but slow algorithm. Used for educational purposes.
public class BubbleSort { public static void bubbleSort(int[] arr) { int n = arr.length; boolean swapped; for (i