Index

Specialized Collections

Java Collections

9.1 EnumSet and EnumMap

When working with Java enumerations (enums), two specialized collection classes—EnumSet and EnumMap—offer highly efficient and expressive solutions tailored specifically for enum types. Both are part of the java.util package and leverage the fixed, finite nature of enums to optimize performance beyond what general-purpose collections can provide.

What is EnumSet?

EnumSet is a specialized Set implementation designed exclusively for use with enum types. Internally, it uses a bit vector representation where each bit corresponds to a particular enum constant. This means that adding, removing, or checking for the presence of an element can be done with very fast bitwise operations.

Because enums are known at compile-time and are fixed in number, EnumSet can allocate a compact bit mask that makes its operations both memory-efficient and very fast compared to, say, a HashSet.

Key Features of EnumSet:

Example of EnumSet usage:

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
EnumSet<Day> workdays = EnumSet.range(Day.MONDAY, Day.FRIDAY);

System.out.println("Weekend days: " + weekend);
System.out.println("Workdays: " + workdays);

What is EnumMap?

EnumMap is a specialized Map implementation where keys are enum constants. Like EnumSet, it is implemented internally using an array indexed by the enum's ordinal values. This structure makes lookups, inserts, and removals extremely efficient and faster than general-purpose maps like HashMap.

Key Features of EnumMap:

Example of EnumMap usage:

EnumMap<Day, String> dayDescriptions = new EnumMap<>(Day.class);
dayDescriptions.put(Day.MONDAY, "Start of the work week");
dayDescriptions.put(Day.FRIDAY, "End of the work week");
dayDescriptions.put(Day.SUNDAY, "Rest day");

System.out.println("Day descriptions: " + dayDescriptions);
Click to view full runnable Code

import java.util.EnumMap;
import java.util.EnumSet;

public class EnumCollectionsDemo {

    // Define the enum
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[] args) {
        // Using EnumSet
        EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
        EnumSet<Day> workdays = EnumSet.range(Day.MONDAY, Day.FRIDAY);

        System.out.println("Weekend days: " + weekend);
        System.out.println("Workdays: " + workdays);

        // Using EnumMap
        EnumMap<Day, String> dayDescriptions = new EnumMap<>(Day.class);
        dayDescriptions.put(Day.MONDAY, "Start of the work week");
        dayDescriptions.put(Day.FRIDAY, "End of the work week");
        dayDescriptions.put(Day.SUNDAY, "Rest day");

        System.out.println("Day descriptions:");
        for (Day day : dayDescriptions.keySet()) {
            System.out.println("  " + day + ": " + dayDescriptions.get(day));
        }
    }
}

Why Use EnumSet and EnumMap?

Typical Use Cases:

Summary: EnumSet and EnumMap provide elegant, efficient, and type-safe ways to work with enums in Java. Their internal optimizations make them stand out for enum-based collections, often outperforming traditional sets and maps in both speed and memory usage, making them invaluable tools for enum-centric programming tasks.

Index

9.2 BitSet

In Java, the BitSet class is a powerful and memory-efficient way to manage and manipulate a set of bits. Unlike collections that store objects, BitSet stores bits (binary values: 0 or 1) compactly and allows for fast bitwise operations. This makes it ideal for scenarios where you need to represent and manipulate large sets of boolean flags or simple numeric sets in an efficient manner.

What is BitSet?

BitSet represents a sequence of bits indexed by non-negative integers. Each bit can be individually set, cleared, or flipped, and multiple bitsets can be combined using logical operations like AND, OR, and XOR. Because it uses a compressed internal representation (typically a long array), it uses far less memory compared to storing booleans in a standard array or collection.

Common Use Cases:

Basic Operations

Runnable Examples

import java.util.BitSet;

public class BitSetDemo {
    public static void main(String[] args) {
        BitSet bits1 = new BitSet();
        BitSet bits2 = new BitSet();

        // Setting bits
        bits1.set(0); // Set bit 0 to true
        bits1.set(2); // Set bit 2 to true
        bits1.set(4); // Set bit 4 to true

        System.out.println("bits1: " + bits1); // Outputs: {0, 2, 4}

        // Clear bit 2
        bits1.clear(2);
        System.out.println("bits1 after clearing bit 2: " + bits1); // Outputs: {0, 4}

        // Flip bit 4
        bits1.flip(4);
        System.out.println("bits1 after flipping bit 4: " + bits1); // Outputs: {0}

        // Setting bits in bits2
        bits2.set(1);
        bits2.set(3);
        bits2.set(4);

        System.out.println("bits2: " + bits2); // Outputs: {1, 3, 4}

        // Logical OR operation: bits1 = bits1 OR bits2
        bits1.or(bits2);
        System.out.println("bits1 after OR with bits2: " + bits1); // Outputs: {0, 1, 3, 4}

        // Logical AND operation
        bits1.and(bits2);
        System.out.println("bits1 after AND with bits2: " + bits1); // Outputs: {1, 3, 4}

        // Check bit presence
        System.out.println("Is bit 3 set in bits1? " + bits1.get(3)); // true
        System.out.println("Is bit 0 set in bits1? " + bits1.get(0)); // false
    }
}

Explanation:

Summary

BitSet is an excellent choice when working with large sets of boolean flags or numeric sets that need to be represented efficiently. Its compact memory footprint and built-in bitwise operations provide a high-performance solution for many low-level tasks. By understanding and utilizing its core methods, you can implement fast and memory-efficient algorithms in Java.

Index

9.3 Stack and Vector (Legacy Collections)

Before the introduction of the modern Java Collections Framework (JCF) in Java 2 (Java 1.2), Stack and Vector were among the primary data structures available for managing groups of objects. These legacy classes are part of the java.util package and predate the interfaces and implementations that define today’s collections, such as Deque and ArrayList.

Historical Context

Why Are They Considered Legacy?

With the advent of the Collections Framework, newer, more flexible, and more efficient classes were introduced:

When Are Legacy Classes Still Used?

Basic Usage Examples

import java.util.Stack;
import java.util.Vector;

public class LegacyCollectionsDemo {
    public static void main(String[] args) {
        // Stack example
        Stack<String> stack = new Stack<>();
        stack.push("Apple");
        stack.push("Banana");
        stack.push("Cherry");

        System.out.println("Stack contents: " + stack);
        System.out.println("Popped from stack: " + stack.pop());
        System.out.println("Stack after pop: " + stack);

        // Vector example
        Vector<Integer> vector = new Vector<>();
        vector.add(10);
        vector.add(20);
        vector.add(30);

        System.out.println("Vector contents: " + vector);
        vector.remove(Integer.valueOf(20));
        System.out.println("Vector after removal: " + vector);
    }
}

Output Explanation

Caveats

Summary

While Stack and Vector are important historically and occasionally necessary for maintaining legacy systems, they are generally superseded by more efficient, flexible, and better-designed classes in the Java Collections Framework. Understanding their characteristics helps in working with older code and choosing appropriate modern alternatives for new development.

Index

9.4 Runnable Examples: Using specialized collections in real scenarios

import java.util.*;

// Example 1: Using EnumSet and EnumMap for managing enums
enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class SpecializedCollectionsDemo {

    public static void main(String[] args) {
        // EnumSet example: Represent working days
        EnumSet<Day> workingDays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
        System.out.println("Working Days: " + workingDays);

        // EnumMap example: Map tasks to specific days
        EnumMap<Day, String> dayTasks = new EnumMap<>(Day.class);
        dayTasks.put(Day.MONDAY, "Team Meeting");
        dayTasks.put(Day.WEDNESDAY, "Project Review");
        dayTasks.put(Day.FRIDAY, "Report Submission");

        System.out.println("Day Tasks:");
        for (Day day : Day.values()) {
            System.out.println(day + ": " + dayTasks.get(day));
        }

        // Example 2: BitSet for flag management
        BitSet permissions = new BitSet();
        int READ = 0, WRITE = 1, EXECUTE = 2;
        permissions.set(READ);
        permissions.set(EXECUTE);

        System.out.println("\nPermissions:");
        System.out.println("Read: " + permissions.get(READ));      // true
        System.out.println("Write: " + permissions.get(WRITE));    // false
        System.out.println("Execute: " + permissions.get(EXECUTE));// true

        // Example 3: Stack for expression evaluation (simple postfix calculator)
        Stack<Integer> stack = new Stack<>();
        String[] postfix = {"2", "3", "4", "*", "+"}; // Represents 2 + (3 * 4)

        for (String token : postfix) {
            if ("+".equals(token)) {
                int b = stack.pop();
                int a = stack.pop();
                stack.push(a + b);
            } else if ("*".equals(token)) {
                int b = stack.pop();
                int a = stack.pop();
                stack.push(a * b);
            } else {
                stack.push(Integer.parseInt(token));
            }
        }
        System.out.println("\nPostfix Expression Result: " + stack.pop()); // Outputs 14

        // Example 4: Vector in a synchronized list scenario
        Vector<String> synchronizedList = new Vector<>();
        synchronizedList.add("Apple");
        synchronizedList.add("Banana");
        synchronizedList.add("Cherry");

        System.out.println("\nVector contents:");
        for (String fruit : synchronizedList) {
            System.out.println(fruit);
        }
    }
}

Explanation:

  1. EnumSet and EnumMap:

    • EnumSet efficiently stores sets of enum constants, here representing working days Monday to Friday.
    • EnumMap maps enum keys to values, used to associate tasks with specific days.
  2. BitSet:

    • Used for managing boolean flags like permissions. Here, the bits at positions 0 (READ) and 2 (EXECUTE) are set true, while WRITE (position 1) is false.
  3. Stack:

    • Demonstrates a simple postfix expression evaluation. Operators pop operands from the stack, compute, and push results back.
  4. Vector:

    • A thread-safe list used here for storing fruits, illustrating synchronized access without additional locking.

These examples highlight practical scenarios where specialized collections shine, offering performance and clarity advantages.

Index