Convenience Factory Methods for Collections

Quick Summary:

  • Introducing static methods on List, Set, and Map interfaces to easily create immutable instances, streamlining one-liner collection initialisation.
  • Introduced in Java 9 (2017).
List<Integer> fibonacci = List.of(1, 1, 2, 3, 5, 8, 13);
Set<String> faveLanguages = Set.of("Java", "TypeScript", "Rust");
Map<Integer, String> statusCodes = Map.of(200, "OK", 404, "Not Found", 500, "Internal Server Error");

Key Features

  • Simplicity in Code: Reduces the verbosity involved in creating and initialising collections, making code more concise and readable.
  • Immutability: The created collections are immutable, which enhances the safety and predictability of applications by making collections unmodifiable once created.
  • Performance Optimization: Immutable collections are optimized for memory, potentially using less memory than their mutable counterparts, depending on the implementation.

Examples

1. Creating Immutable List

Before Java 9, creating an unmodifiable list involved multiple steps:

List<Integer> fibonacci = new ArrayList<>();
fibonacci.add(1);
fibonacci.add(1);
fibonacci.add(2);
fibonacci.add(3);
fibonacci.add(5);
fibonacci.add(8);
fibonacci.add(13);
fibonacci = Collections.unmodifiableList(fibonacci);

With Java 9 and later:

List<Integer> fibonacci = List.of(1, 1, 2, 3, 5, 8, 13);

2. Creating Immutable Set

Before Java 9, creating an unmodifiable set was verbose:

Set<String> faveLanguages = new HashSet<>();
faveLanguages.add("Java");
faveLanguages.add("TypeScript");
faveLanguages.add("Rust");
faveLanguages = Collections.unmodifiableSet(faveLanguages);

With Java 9 and later:

Set<String> faveLanguages = Set.of("Java", "TypeScript", "Rust");

3. Creating Immutable Map

Before Java 9, creating an unmodifiable map required multiple steps:

Map<String, Integer> statusCodes = new HashMap<>();
statusCodes.put(200, "OK");
statusCodes.put(404, "Not Found");
statusCodes.put(500, "Internal Server Error");
statusCodes = Collections.unmodifiableMap(statusCodes);

With Java 9 and later:

Map<Integer, String> statusCodes = Map.of(200, "OK", 404, "Not Found", 500, "Internal Server Error");

👩‍💻 Hands-on Demo: Convenience Factory Methods for Collections

  1. In a ConvenienceFactoryMethodsDemo-class, create and initialise a static list of descriptions (using a convenience factory method) for a weekly weather forecast (i.e. Sunny, Partly Cloudy, Cloudy, Rainy, Rainy, Partly Cloudy, Rainy, etc.).
  2. In the main-method, attempt to add another weather condition like “Sunny”. What happens? Why?
  3. In an enhanced for-loop, immediately build a set of prime numbers (using a convenience factory method) and print them.
  4. In one line, using Map’s convenience factory method, print the name and unit price of 10 products and then the total price.
  5. Try to add an eleventh product. What happens? Why?

Solutions

🕵️‍♂️ Click here to reveal the solutions
public class ConvenienceFactoryMethodsDemo {
    private static List<String> weeklyWeatherForecast = List.of("Sunny", "Partly Cloudy", "Cloudy", "Rainy", "Sunny", "Partly Cloudy", "Cloudy");

    public static void main(String[] args) {
//        weeklyWeatherForecast.add("Sunny"); // ❌ Immutable list throws UnsupportedOperationException when you try to add an element

        for (Integer primeNum : Set.of(2, 3, 5, 7)) {
            System.out.println(primeNum);
        }

        System.out.printf("Total cost: $%.2f.\n", Map.of(
                        "Clean Code", 45.99,
                        "Refactoring", 47.50,
                        "Design Patterns", 55.20,
                        "Effective Java", 40.45,
                        "Code Complete", 36.75,
                        "The Pragmatic Programmer", 42.00,
                        "Structure and Interpretation of Computer Programs", 38.95,
                        "Introduction to Algorithms", 80.00,
                        "The Art of Computer Programming", 250.00,
                        "Head First Design Patterns", 44.10
//                        "You Don't Know JS", 29.99 ❌ Map.of() only allows 10 key-value pairs
                ).entrySet().stream()
                .peek(e -> System.out.printf("%s costs $%,.2f.\n", e.getKey(), e.getValue()))
                .mapToDouble(Map.Entry::getValue)
                .sum());
    }
}

Some Considerations

  • Immutability: Collections created with factory methods are immutable. Once defined, their content cannot be altered, which ensures safety in concurrent environments but requires upfront completeness of data.
  • Entry Limit in Maps: Map.of() is convenient for small maps and supports up to 10 key-value pairs. For larger maps, use Map.ofEntries(), which accepts an unlimited number of Map.Entry objects, allowing for greater flexibility but requiring explicit entry creation.

Summary

  • Simplified Initialization: Java 9 introduced List.of(), Set.of(), and Map.of() for one-liner, immutable collection creation, enhancing code readability and reducing verbosity.
  • Enhanced Safety: Immutable collections prevent modifications after creation, securing applications against unintended changes.
  • Memory Efficiency: Immutable collections are optimized for memory usage, which can lead to performance improvements.
  • Limitations: Maps created with Map.of() support up to 10 entries. For more, use Map.ofEntries() with explicit Map.Entry objects for flexibility.