Map Interface
Overview and Characteristics
The Map interface represents a collection that maps keys to values. Each key can map to at most one value.
interface Map<K,V> where K is the key type and V is the value type
Key characteristics:
- Key-value pair structure with one-to-one mapping between keys and values
- Keys must be unique within the collection
- Values can be duplicated across different keys
Basic implementation example:
public class MapExample {
public static void main(String[] args) {
// Create map instance
Map<String, String> actorMap = new HashMap<String, String>();
// Add key-value pairs using put method
actorMap.put("actor001", "Meryl Streep");
actorMap.put("actor002", "Leonardo DiCaprio");
actorMap.put("actor003", "Scarlett Johansson");
// Duplicate key overwrites previous value
actorMap.put("actor003", "Emma Stone");
// Display the complete map
System.out.println(actorMap);
}
}
Core Operations
Common methods available in Map implementations:
| Method | Descriptoin |
|---|---|
| V put(K key, V value) | Adds a key-value mapping |
| V remove(Object key) | Removes mapping by key |
| void clear() | Empties all mappings |
| boolean containsKey(Object key) | Checks for specific key existence |
| boolean containsValue(Object value) | Checks for specific value existence |
| boolean isEmpty() | Tests if map is empty |
| int size() | Returns number of key-value pairs |
Implementation example:
public class MapOperations {
public static void main(String[] args) {
Map<String, String> movieMap = new HashMap<String, String>();
// Adding entries
movieMap.put("director1", "Christopher Nolan");
movieMap.put("director2", "Quentin Tarantino");
movieMap.put("director3", "Martin Scorsese");
// Removing by key
// System.out.println(movieMap.remove("director2"));
// Check key existence
// System.out.println(movieMap.containsKey("director1"));
// Get map size
System.out.println("Number of entries: " + movieMap.size());
System.out.println(movieMap);
}
}
Retrieval Methods
Essential methods for accessing map data:
| Method | Purpose |
|---|---|
| V get(Object key) | Retrieves value by key |
| Set<K> keySet() | Gets all keys |
| Collection<V> values() | Gets all values |
| Set<Map.Entry<K,V>> entrySet() | Gets all key-value pairs |
Usage example:
public class MapRetrieval {
public static void main(String[] args) {
Map<String, String> directorMap = new HashMap<String, String>();
directorMap.put("movie1", "Steven Spielberg");
directorMap.put("movie2", "Ridley Scott");
directorMap.put("movie3", "James Cameron");
// Retrieve all values
Collection<String> directors = directorMap.values();
for(String director : directors) {
System.out.println(director);
}
}
}
Traversal Approach 1: Key-Based Access
This approach involves:
- Obtaining the complete set of keys
- Iterating through each key
- Using each key to retrieve its corresponding value
Implementation:
public class MapTraversalKeys {
public static void main(String[] args) {
Map<String, String> characterMap = new HashMap<String, String>();
characterMap.put("hero1", "Superman");
characterMap.put("hero2", "Batman");
characterMap.put("hero3", "Wonder Woman");
Set<String> keyCollection = characterMap.keySet();
for (String key : keyCollection) {
String value = characterMap.get(key);
System.out.println(key + ": " + value);
}
}
}
Traversal Approach 2: Entry-Based Access
Alternative approach using entry objects:
- Acquire the complete set of key-value entry objects
- Iterate through each entry object
- Extract both key and value from each entry
Implementation:
public class MapTraversalEntries {
public static void main(String[] args) {
Map<String, String> filmMap = new HashMap<String, String>();
filmMap.put("film1", "Inception");
filmMap.put("film2", "Pulp Fiction");
filmMap.put("film3", "Goodfellas");
Set<Map.Entry<String, String>> entryCollection = filmMap.entrySet();
for (Map.Entry<String, String> entry : entryCollection) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ": " + value);
}
}
}
HashMap Implementation
Structure and Uniqueness
HashMap uses a hash table underlying structure. It relies on hashCode() and equals() methods to ansure key uniqueness. For custom objects as keys, these methods must be properly overridden.
Practical Example with Custom Objects
Scenario: Creating a map with Person objects as keys and location strings as values.
Person class implementation:
public class Person {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
Testing implementation:
public class HashMapExample {
public static void main(String[] args) {
HashMap<Person, String> locationMap = new HashMap<Person, String>();
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Bob", 30);
Person p3 = new Person("Charlie", 28);
Person p4 = new Person("Alice", 25); // Same as p1
locationMap.put(p1, "New York");
locationMap.put(p2, "Los Angeles");
locationMap.put(p3, "Chicago");
locationMap.put(p4, "Miami"); // Should overwrite p1's entry
Set<Person> keySet = locationMap.keySet();
for (Person key : keySet) {
String value = locationMap.get(key);
System.out.println(key.getName() + ", " + key.getAge() + ", " + value);
}
}
}
TreeMap Implementation
Structure and Sorting
TreeMap uses a red-black tree structure. It sorts entries based on natural ordering of keys or a custom comparator. For custom objects, implement Comparable or provide a custom comparator.
Sorting Example with Custom Objects
Person class with sorting implementation:
class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
@Override
public int compareTo(Person other) {
// Primary sort by age (descending)
int ageComparison = Integer.compare(other.age, this.age);
// Secondary sort by name if ages are equal
if (ageComparison == 0) {
return other.name.compareTo(this.name);
}
return ageComparison;
}
}
TreeMap usage example:
public class TreeMapExample {
public static void main(String[] args) {
TreeMap<Person, String> sortedMap = new TreeMap<>();
Person p1 = new Person("John", 28);
Person p2 = new Person("Sarah", 25);
Person p3 = new Person("Mike", 25);
sortedMap.put(p1, "California");
sortedMap.put(p2, "Texas");
sortedMap.put(p3, "Florida");
sortedMap.forEach((person, location) -> {
System.out.println(person + " --- " + location);
});
}
}
Character Counting Example
Count occurrences of each character in a string:
public class CharacterCounter {
public static void main(String[] args) {
String input = "aaabbbcccdde";
TreeMap<Character, Integer> charCount = new TreeMap<>();
for (int i = 0; i < input.length(); i++) {
char currentChar = input.charAt(i);
if (!charCount.containsKey(currentChar)) {
charCount.put(currentChar, 1);
} else {
int existingCount = charCount.get(currentChar);
charCount.put(currentChar, existingCount + 1);
}
}
charCount.forEach((ch, count) -> {
System.out.print(ch + "(" + count + ")");
});
}
}
Variable Arguments
Definition and Usage
Variable arguments allow methods to accept an arbitrary number of parameters of the same type. The variable argument must be the last parameter in the method signature.
Definition syntax:
modifier returnType methodName(dataType... variableName) { }
Implementation example:
public class VariableArgsExample {
public static void main(String[] args) {
System.out.println(calculateSum(5, 10));
System.out.println(calculateSum(5, 10, 15));
System.out.println(calculateSum(5, 10, 15, 20));
System.out.println(calculateSum(5, 10, 15, 20, 25, 30));
}
public static int calculateSum(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
}
Immutable Collections
Creating unmodifiable collections using static factory methods:
public class ImmutableCollections {
public static void main(String[] args) {
// Create immutable list
List<String> immutableList = List.of("apple", "banana", "cherry");
// Create immutable map with entries
Map<String, String> immutableMap = Map.ofEntries(
Map.entry("first", "value1"),
Map.entry("second", "value2")
);
// Convert immutable to mutable
ArrayList<String> mutableList = new ArrayList<>(List.of("item1", "item2", "item3"));
System.out.println(immutableList);
System.out.println(immutableMap);
System.out.println(mutableList);
}
}
Stream API
Introduction and Benefits
Stream API provides a functional programming approach for processing sequences of elements. It allows for concise and readable operations on collections.
Traditional approach vs. Stream approach:
public class StreamComparison {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<String>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("David");
names.add("Alex");
names.add("Anna");
// Traditional approach
ArrayList<String> filteredNames = new ArrayList<String>();
for (String name : names) {
if (name.startsWith("A")) {
filteredNames.add(name);
}
}
ArrayList<String> finalResult = new ArrayList<String>();
for (String name : filteredNames) {
if (name.length() == 4) {
finalResult.add(name);
}
}
for (String name : finalResult) {
System.out.println(name);
}
System.out.println("--- Stream approach ---");
// Stream approach
names.stream()
.filter(name -> name.startsWith("A"))
.filter(name -> name.length() == 4)
.forEach(System.out::println);
}
}
Stream Creation Methods
Different ways to create streams:
public class StreamCreation {
public static void main(String[] args) {
// From Collection
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
// From Map
Map<String, Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
// From Array
String[] array = {"hello", "world", "java"};
Stream<String> arrayStream = Arrays.stream(array);
// From individual values
Stream<String> valueStream = Stream.of("one", "two", "three");
Stream<Integer> numberStream = Stream.of(100, 200, 300);
}
}
Intermediate Operasions
Operations that return another stream for continued processing:
| Method | Purpose |
|---|---|
| Stream<T> filter(Predicate<T>) | Filters elements based on condition |
| Stream<T> limit(long) | Takes first n elements |
| Stream<T> skip(long) | Skips first n elements |
| static <T> Stream<T> concat(Stream<T>, Stream<T>) | Combines two streams |
| Stream<T> distinct() | Removes duplicates |
Examples:
public class StreamIntermediate {
public static void main(String[] args) {
ArrayList<String> items = new ArrayList<String>();
items.add("Apple");
items.add("Banana");
items.add("Avocado");
items.add("Cherry");
items.add("Apricot");
items.add("Blueberry");
// Filter example
items.stream().filter(item -> item.startsWith("A")).forEach(System.out::println);
// Limit and skip example
items.stream().skip(2).limit(2).forEach(System.out::println);
// Concatenate streams
Stream<String> stream1 = items.stream().limit(3);
Stream<String> stream2 = items.stream().skip(3);
Stream.concat(stream1, stream2).distinct().forEach(System.out::println);
}
}
Terminal Operations
Operations that produce a result and end the stream pipeline:
| Method | Purpose |
|---|---|
| void forEach(Consumer<T>) | Performs action on each element |
| long count() | Counts elements in stream |
Example:
public class StreamTerminal {
public static void main(String[] args) {
ArrayList<String> colors = new ArrayList<String>();
colors.add("Red");
colors.add("Green");
colors.add("Blue");
colors.add("Yellow");
colors.add("Purple");
// Count elements starting with 'B'
long blueCount = colors.stream().filter(color -> color.startsWith("B")).count();
System.out.println("Count: " + blueCount);
}
}
Collection Operations
Collecting stream results back into collections:
| Method | Purpose |
|---|---|
| R collect(Collector<T>) | Gathers results into specified collection |
Collector utilities:
| Method | Purpose |
|---|---|
| public static <T> Collector<T> toList() | Collects to List |
| public static <T> Collector<T> toSet() | Collects to Set |
| public static Collector<T> toMap(Function<K>, Function<V>) | Collects to Map |
Example implementation:
public class StreamCollection {
public static void main(String[] args) {
List<String> words = Arrays.asList("hello", "world", "java", "programming");
// Collect filtered elements to List
List<String> shortWords = words.stream()
.filter(word -> word.length() < 6)
.collect(Collectors.toList());
// Process string data and collect to Map
String[] data = {"John,25", "Jane,30", "Bob,35"};
Map<String, Integer> personMap = Stream.of(data)
.filter(entry -> Integer.parseInt(entry.split(",")[1]) > 25)
.collect(Collectors.toMap(
entry -> entry.split(",")[0],
entry -> Integer.parseInt(entry.split(",")[1])
));
personMap.forEach((name, age) -> System.out.println(name + ": " + age));
}
}
Comprehensive Exercise
Combining multiple stream operations:
public class Actor {
private String name;
public Actor(String name) {
this.name = name;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public class StreamExercise {
public static void main(String[] args) {
ArrayList<String> maleActors = new ArrayList<String>();
maleActors.add("Tom Hanks");
maleActors.add("Leonardo");
maleActors.add("Brad Pitt");
maleActors.add("Johnny Depp");
maleActors.add("Robert Downey");
maleActors.add("Chris Evans");
ArrayList<String> femaleActors = new ArrayList<String>();
femaleActors.add("Scarlett Johansson");
femaleActors.add("Jennifer Lawrence");
femaleActors.add("Emma Watson");
femaleActors.add("Natalie Portman");
femaleActors.add("Reese Witherspoon");
femaleActors.add("Sandra Bullock");
Stream.concat(
maleActors.stream().filter(name -> name.length() == 10).limit(2),
femaleActors.stream().filter(name -> name.startsWith("J")).skip(1)
).map(Actor::new)
.forEach(actor -> System.out.println(actor.getName()));
}
}