Chapter 15. Encapsulated Collections to Type Aliases

In Java, we encapsulate collections of objects in classes to control mutation and add operations. Controlling mutation is less of a concern in Kotlin, and we can use extension functions to add operations. How would our designs be better without the encapsulation, and how do we get there?

In Chapter 6 we looked at the differences between the grains of Java and Kotlin when it comes to collections. Java’s collection interfaces, in keeping with its object-oriented roots, are fundamentally mutable, whereas Kotlin treats collections as value types. As we saw, if we mutate shared collections, we can run into all sorts of trouble. We could avoid that trouble by not mutating shared collections (“Don’t Mutate Shared Collections”), but in Java that’s hard to do when those add and set methods are just an autocomplete away. Instead of convention and discipline, most Java code sensibly opts for the safer approach of simply not sharing raw collections. Instead, collections are hidden inside another object.

Here, for example, is a Route in Travelator:

public class Route {
    private final List<Journey> journeys; 1

    public Route(List<Journey> journeys) {
        this.journeys = journeys; 2

    public int size() { 
        return journeys.size();


Get Java to Kotlin now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.