Java - Data Structures

Welcome, future programmers! Today, we're diving into the exciting world of Java data structures. As your friendly neighborhood computer science teacher, I'm here to guide you through this journey, step by step. Don't worry if you're new to programming – we'll start from the basics and work our way up. So, grab your virtual hardhats, and let's start building our knowledge!

Java - Data Structures

Introduction to Data Structures

Before we jump into specific Java data structures, let's understand what data structures are and why they're important.

Imagine you're organizing a library. You wouldn't just throw all the books in a pile, would you? Of course not! You'd organize them in a way that makes it easy to find and manage them. That's exactly what data structures do for our data in programming.

Data structures are ways of organizing and storing data so that we can access and modify it efficiently. In Java, we have several built-in data structures that we can use, each with its own strengths and use cases.

Java Built-in Data Structures

Let's explore some of the most common data structures in Java:

The Enumeration

Enumeration is like a ticket machine that gives out numbers one by one. It's an interface in Java that allows us to access elements in a collection one at a time.

Here's a simple example:

import java.util.*;

public class EnumerationExample {
    public static void main(String args[]) {
        Vector<String> dayNames = new Vector<>();
        dayNames.add("Monday");
        dayNames.add("Tuesday");
        dayNames.add("Wednesday");

        Enumeration<String> days = dayNames.elements();

        while (days.hasMoreElements()) {
            System.out.println(days.nextElement());
        }
    }
}

In this example, we create a Vector of day names and use Enumeration to iterate through them. The hasMoreElements() method checks if there are more elements, and nextElement() retrieves the next element.

The BitSet

BitSet is like a row of light switches – each can be either on (1) or off (0). It's useful when you need to store a series of true/false values efficiently.

Here's an example:

import java.util.BitSet;

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

        // set some bits
        for(int i = 0; i < 16; i++) {
            if((i % 2) == 0) bits1.set(i);
            if((i % 5) != 0) bits2.set(i);
        }

        System.out.println("Initial pattern in bits1: " + bits1);
        System.out.println("Initial pattern in bits2: " + bits2);

        // AND bits
        bits2.and(bits1);
        System.out.println("bits2 AND bits1: " + bits2);
    }
}

This example demonstrates creating BitSets, setting bits, and performing bitwise operations.

The Vector

Vector is like a magical array that can grow or shrink as needed. It's similar to an ArrayList but is synchronized, making it thread-safe.

Here's how you might use a Vector:

import java.util.*;

public class VectorExample {
    public static void main(String args[]) {
        Vector<Integer> vec = new Vector<>(3, 2);
        System.out.println("Initial size: " + vec.size());
        System.out.println("Initial capacity: " + vec.capacity());

        vec.addElement(1);
        vec.addElement(2);
        vec.addElement(3);
        vec.addElement(4);
        System.out.println("Capacity after four additions: " + vec.capacity());

        vec.addElement(5);
        System.out.println("Current capacity: " + vec.capacity());

        System.out.println("First element: " + vec.firstElement());
        System.out.println("Last element: " + vec.lastElement());
    }
}

This example shows how to create a Vector, add elements, and check its size and capacity.

The Stack

Stack is like a stack of plates – you can only add or remove from the top. It follows the Last-In-First-Out (LIFO) principle.

Let's see a Stack in action:

import java.util.*;

public class StackExample {
    public static void main(String args[]) {
        Stack<String> stack = new Stack<>();
        stack.push("Bottom");
        stack.push("Middle");
        stack.push("Top");

        System.out.println("Stack: " + stack);
        System.out.println("Popped: " + stack.pop());
        System.out.println("Stack after pop: " + stack);
        System.out.println("Peek: " + stack.peek());
        System.out.println("Stack after peek: " + stack);
    }
}

This example demonstrates pushing elements onto a stack, popping them off, and peeking at the top element.

The Dictionary

Dictionary is an abstract class that represents a key-value data structure. It's like a real dictionary where each word (key) has a definition (value).

While Dictionary is abstract and can't be instantiated directly, its subclass Hashtable is commonly used:

import java.util.*;

public class DictionaryExample {
    public static void main(String args[]) {
        Dictionary<String, String> dict = new Hashtable<>();

        dict.put("Apple", "A fruit");
        dict.put("Java", "A programming language");
        dict.put("Computer", "An electronic device");

        System.out.println("Dictionary: " + dict);
        System.out.println("Value for key 'Java': " + dict.get("Java"));

        System.out.println("Keys: ");
        for (Enumeration<String> keys = dict.keys(); keys.hasMoreElements();) {
            System.out.println(keys.nextElement());
        }
    }
}

This example shows how to use a Dictionary (via Hashtable) to store and retrieve key-value pairs.

The Hashtable

Hashtable is like a super-efficient filing cabinet. It stores key-value pairs and allows for quick retrieval of values based on their keys.

Here's an example of using a Hashtable:

import java.util.*;

public class HashtableExample {
    public static void main(String args[]) {
        Hashtable<String, Integer> numbers = new Hashtable<>();
        numbers.put("one", 1);
        numbers.put("two", 2);
        numbers.put("three", 3);

        System.out.println("Hashtable: " + numbers);
        System.out.println("Value of 'two': " + numbers.get("two"));
        System.out.println("Is 'four' a key? " + numbers.containsKey("four"));
        System.out.println("Is 3 a value? " + numbers.containsValue(3));

        numbers.remove("two");
        System.out.println("Hashtable after removing 'two': " + numbers);
    }
}

This example demonstrates adding key-value pairs to a Hashtable, retrieving values, checking for keys and values, and removing entries.

The Properties

Properties is a special kind of Hashtable designed to store string key-value pairs. It's often used for configuration settings.

Let's see how Properties works:

import java.util.*;

public class PropertiesExample {
    public static void main(String args[]) {
        Properties capitals = new Properties();
        capitals.put("USA", "Washington D.C.");
        capitals.put("France", "Paris");
        capitals.put("Japan", "Tokyo");

        System.out.println("Properties: " + capitals);
        System.out.println("Capital of France: " + capitals.getProperty("France"));

        // Set a default value
        System.out.println("Capital of Spain: " + capitals.getProperty("Spain", "Not Found"));

        capitals.list(System.out);
    }
}

This example shows how to use Properties to store and retrieve string key-value pairs, with a default value for missing keys.

Conclusion

Congratulations! You've just taken your first steps into the world of Java data structures. Each of these structures has its own unique properties and use cases. As you continue your programming journey, you'll find yourself reaching for different structures depending on your specific needs.

Remember, choosing the right data structure can make a big difference in how efficiently your program runs. It's like choosing the right tool for a job – a hammer is great for nails, but not so great for screws!

Keep practicing with these structures, try to use them in your own projects, and don't be afraid to experiment. Happy coding!

Credits: Image by storyset