Java Collections, Files and Streams

Count the frequency of words with a HashMap

Description: Count the words of "the cat and the dog and the bird". Print each word with its count, in alphabetical order.

Hint:

  • Use a TreeMap (sorted keys) and merge(word, 1, Integer::sum).

Solution

import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        String text = "the cat and the dog and the bird";
        Map<String, Integer> counts = new TreeMap<>();

        for (String word : text.split(" ")) {
            counts.merge(word, 1, Integer::sum);
        }
        counts.forEach((word, n) -> System.out.println(word + " = " + n));
    }
}
Output

and = 2
bird = 1
cat = 1
dog = 1
the = 3

How it works

  • merge puts 1 if the word is new, or adds 1 to the old count.
  • A TreeMap keeps the keys sorted. A HashMap would print them in any order.

Read Java HashMap.

Remove duplicates from a list and keep the order

Description: Remove the duplicates from [4, 7, 4, 1, 7, 9, 1] and keep the order of the first appearance.

Hint:

  • A LinkedHashSet keeps the insert order. Or use stream().distinct().

Solution

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(4, 7, 4, 1, 7, 9, 1);

        List<Integer> a = new ArrayList<>(new LinkedHashSet<>(numbers));
        List<Integer> b = numbers.stream().distinct().toList();

        System.out.println(a);
        System.out.println(b);
    }
}
Output

[4, 7, 1, 9]
[4, 7, 1, 9]

How it works

  • A set does not allow duplicates, and the linked version remembers the order.

Read HashSet and LinkedList.

Use streams to filter and sum numbers

Description: From the numbers 1 to 10: keep the even ones, square them and print the list and the sum. Then group the words "kiwi", "fig", "apple", "pear", "plum" by their length.

Hint:

  • IntStream.rangeClosed, filter, map, sum and Collectors.groupingBy.

Solution

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> squares = IntStream.rangeClosed(1, 10)
                .filter(n -> n % 2 == 0)
                .map(n -> n * n)
                .boxed()
                .toList();
        System.out.println(squares);
        System.out.println(squares.stream().mapToInt(Integer::intValue).sum());

        Map<Integer, List<String>> byLength = List.of("kiwi", "fig", "apple", "pear", "plum").stream()
                .collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toList()));
        System.out.println(byLength);
    }
}
Output

[4, 16, 36, 64, 100]
220
{3=[fig], 4=[kiwi, pear, plum], 5=[apple]}

How it works

  • Each step takes the result of the one before it: filter, then map, then sum.
  • groupingBy makes a map from the length to the list of words.

Read Java Streams.

Read a file and count its lines and words

Description: Write a file poem.txt with three lines. Read it back and print the number of lines and the number of words.

Hint:

  • Use Files.write and Files.readAllLines. Count the words with split("\\s+").

Solution

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Path file = Path.of("poem.txt");
        Files.write(file, List.of("Roses are red", "Violets are blue", "Java is fun"));

        List<String> lines = Files.readAllLines(file);
        int words = 0;
        for (String line : lines) {
            words += line.trim().split("\\s+").length;
        }
        System.out.println("Lines: " + lines.size());
        System.out.println("Words: " + words);
    }
}
Output

Lines: 3
Words: 9

How it works

  • readAllLines gives a list with one item for each line.
  • \\s+ is a regular expression for one or more spaces.

Read Java File Handling.

Use a sealed interface and pattern matching

Description: Make a sealed interface Shape with the records Square(double side) and Circle(double radius). Write a method perimeter(Shape) with a switch expression and no default.

Hint:

  • The perimeter of a circle is 2 * Math.PI * radius.

Solution

sealed interface Shape permits Square, Circle { }

record Square(double side) implements Shape { }
record Circle(double radius) implements Shape { }

public class Main {
    static double perimeter(Shape s) {
        return switch (s) {
            case Square sq -> 4 * sq.side();
            case Circle c -> 2 * Math.PI * c.radius();
        };
    }

    public static void main(String[] args) {
        System.out.println(perimeter(new Square(5)));
        System.out.printf("%.2f%n", perimeter(new Circle(1)));
    }
}
Output

20.0
6.28

How it works

  • The interface is sealed, so the compiler knows that only Square and Circle exist, and the switch needs no default.
  • If you add a new shape, the compiler shows an error in this switch until you handle it.

Read Pattern Matching and Sealed Classes.