Java Generics and Collections • Maurice Naftalin & Stuart Marks

Maurice Naftalin and Stuart Marks discuss the second edition of "Java Generics and Collections," 19 years after the first. They explore the evolution of Java from version 5 to 25, focusing on major shifts like the move toward immutability, the introduction of sequenced collections and streams, and the critical design principles of collection encapsulation. The conversation delves into the anemic domain model anti-pattern, the controversial `UnsupportedOperationException`, and the framework's inconsistent null handling, reflecting on decades of experience with the Java Collections Framework.

4 min read
Java Generics and Collections • Maurice Naftalin & Stuart Marks
source: https://www.youtube.com/watch?v=at1hxEo7IjA

In the 19 years since the first edition of “Java Generics and Collections” was published for Java 5, the language and its programming style have evolved significantly. The release of Java 21, which introduced sequenced collections, served as a key catalyst for the second edition. However, the changes run deeper, reflecting a stylistic shift that makes code from the Java 5 era feel “antiquated.” Modern Java development has been reshaped by features like streams, records, unmodifiable collection factory methods, and the deprecation of wrapper class constructors (e.g., new Integer()).

The Shift Towards Immutability

A central theme in Java’s evolution is the increasing emphasis on immutability, a trend influenced by functional programming paradigms. While Java is not and will not become a purely functional language, the benefits of immutability are now widely recognized, especially with the advent of concurrent collections in Java 5.

This shift is evident in modern coding practices:

  • Unmodifiable Collections: The introduction of factory methods like List.of() and Map.of() in Java 9 made it easier to create unmodifiable collections, which are now common in examples.
  • Streams: The Streams API encourages a transformational approach to data processing. Instead of modifying a collection in place, developers are more likely to stream its elements, apply transformations, and collect the results into a new collection. This pattern pairs naturally with unmodifiability.

Guidance on Collection Usage and Encapsulation

A new chapter on “Usage Guidance” addresses critical design principles that have been learned over decades. The most important of these is the ownership and encapsulation of collections.

An object should own its state entirely.

This means preventing clients from obtaining a reference to an internal collection and modifying it, either accidentally or maliciously. To achieve this, developers must go beyond simple private fields and consider:

  • Defensive Copying: When accepting a collection in a constructor or method, make a copy to store internally.
  • Unmodifiable Wrappers: When returning an internal collection, wrap it using methods like Collections.unmodifiableList() or return a defensive copy to prevent external modification.

A crucial distinction is made between unmodifiability (shallow immutability) and true immutability (deep immutability). An unmodifiable collection prevents changes to the collection itself, but if it contains mutable elements, the object’s state can still be altered. Proper encapsulation requires ensuring that no part of the state graph exposed to a client can be mutated.

The Anemic Domain Model Anti-Pattern

A common mistake is using bare collection types where a dedicated domain object is needed. For example, representing a list of projects as a List<Set<Task>>. This is an instance of the anemic domain model anti-pattern, where objects are merely data containers with no associated logic.

A better approach is to define a proper Project class that contains the Set<Task>. This allows the Project object to encapsulate its own state and behavior, such as calculating the total duration of its tasks. This avoids the need to request highly specialized collection classes with features like callbacks, as the custom logic can be implemented directly in the domain object’s methods.

A Retrospective on the Collections Framework’s Design

Another new chapter offers a design retrospective on the Collections Framework, acknowledging its remarkable longevity and thoughtful design by Joshua Bloch.

Extending the Framework Correctly

The book highlights the fragile base class problem, where subclassing a concrete implementation (e.g., ArrayList) can lead to broken code if the superclass’s implementation details change in a future JDK version. The framework was designed for extension through its abstract base classes (e.g., AbstractList), which provide a stable and safe way to create custom collection implementations.

The UnsupportedOperationException Controversy

A long-standing point of contention is the use of UnsupportedOperationException for unmodifiable collections, which seemingly violates the Interface Segregation Principle. The book defends this design choice as a pragmatic trade-off between conflicting forces, such as keeping the framework’s API surface small.

The issue is often a symptom of poor encapsulation and unclear ownership.

You should only be modifying things that you own. And so if you own it, you know that it’s modifiable.

The type system is just one way to enforce constraints; well-documented contracts and coding conventions are equally powerful.

The Challenge of Null Handling

The framework’s handling of null is described as its “least proudest moment” and a “mess.” The policy has been inconsistent throughout its history:

  • JDK 1.0: Legacy collections like Vector and Hashtable disallowed null.
  • JDK 1.2: The new Collections Framework generally allowed null (e.g., HashMap).
  • Java 5: Concurrent collections disallowed null again.

This inconsistency has led to contradictions in specifications and behavior, creating a challenge that persists today. Despite these issues, the Collections Framework has proven to be a durable and effective part of the Java platform for nearly 30 years.

Share post

Stay In The Loop! Subscribe to Our Newsletter.

Get updates straight to your inbox. No spam, just useful content.

Recommended for You

Clean Architecture with Python • Sam Keen & Max Kirchoff • GOTO 2025

Clean Architecture with Python • Sam Keen & Max Kirchoff • GOTO 2025

Sam Keen discusses his book “Clean Architecture with Python,” explaining how to apply architectural principles in a pragmatic, Pythonic way. The conversation covers thoughtful dependency management, the onion model, and the crucial link between good architecture and testability. Sam also explores how these principles provide a robust foundation for AI-assisted coding by creating well-defined, scoped problems for AI agents, ultimately leading to more maintainable and resilient software.

GenAI Grows Up: Building Production-Ready Agents on the JVM • Rod Johnson • GOTO 2025

GenAI Grows Up: Building Production-Ready Agents on the JVM • Rod Johnson • GOTO 2025

Rod Johnson explains the high failure rate of enterprise GenAI projects, attributing it to the misuse of the technology and a disconnect from established software engineering principles. He argues for a paradigm shift away from Python-centric approaches towards the JVM, introducing Embabel, a framework designed to build reliable, testable, and domain-integrated AI agents by tackling non-determinism and leveraging the strengths of the enterprise Java ecosystem.