Java to Kotlin

A Refactoring Guidebook

Duncan McGregor and Nat Pryce

Java to Kotlin

by Duncan McGregor and Nat Pryce

Copyright © 2020 O’Reilly Media. All rights reserved.

Printed in the United States of America.

Published by O’Reilly Media, Inc. , 1005 Gravenstein Highway North, Sebastopol, CA 95472.

O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles ( http://oreilly.com ). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com .

- Editors: Suzanne McQuade and Sarah Grey

- Production Editor: FILL IN PRODUCTION EDITOR

- Copyeditor: FILL IN COPYEDITOR

- Proofreader: FILL IN PROOFREADER

- Indexer: FILL IN INDEXER

- Interior Designer: David Futato

- Cover Designer: Karen Montgomery

- Illustrator: Rebecca Demarest

- October 2021: First Edition

Revision History for the First Edition

- 2020-11-05: First Early Release

See http://oreilly.com/catalog/errata.csp?isbn=9781492082279 for release details.

The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Refactoring to Kotlin, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.

The views expressed in this work are those of the author(s), and do not represent the publisher’s views. While the publisher and the author(s) have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the author(s) disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.

978-1-492-08227-9

[FILL IN]

Preface

Conventions Used in This Book

The following typographical conventions are used in this book:

Italic

Indicates new terms, URLs, email addresses, filenames, and file extensions.

Constant width

Used for program listings, as well as within paragraphs to refer to program elements such as variable or function names, databases, data types, environment variables, statements, and keywords.

Constant width bold

Shows commands or other text that should be typed literally by the user.

Constant width italic

Shows text that should be replaced with user-supplied values or by values determined by context.

Tip

This element signifies a tip or suggestion.

Note

This element signifies a general note.

Warning

This element indicates a warning or caution.

Using Code Examples

Supplemental material (code examples, exercises, etc.) is available for download at https://github.com/oreillymedia/title_title.

If you have a technical question or a problem using the code examples, please send email to bookquestions@oreilly.com.

This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission.

We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Book Title by Some Author (O’Reilly). Copyright 2012 Some Copyright Holder, 978-0-596-xxxx-x.”

If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com.

O’Reilly Online Learning

Note

For more than 40 years, O’Reilly Media has provided technology and business training, knowledge, and insight to help companies succeed.

Our unique network of experts and innovators share their knowledge and expertise through books, articles, conferences, and our online learning platform. O’Reilly’s online learning platform gives you on-demand access to live training courses, in-depth learning paths, interactive coding environments, and a vast collection of text and video from O’Reilly and 200+ other publishers. For more information, please visit http://oreilly.com.

How to Contact Us

Please address comments and questions concerning this book to the publisher:

- O’Reilly Media, Inc.

- 1005 Gravenstein Highway North

- Sebastopol, CA 95472

- 800-998-9938 (in the United States or Canada)

- 707-829-0515 (international or local)

- 707-829-0104 (fax)

Email bookquestions@oreilly.com to comment or ask technical questions about this book.

For more information about our books, courses, conferences, and news, see our website at http://www.oreilly.com.

Find us on Facebook: http://facebook.com/oreilly

Follow us on Twitter: http://twitter.com/oreillymedia

Watch us on YouTube: http://www.youtube.com/oreillymedia

Acknowledgments

Many thanks to the following who gave us lots of useful feedback during the writing process: Uberto Barbini, Jack Bolles, Dave Denton, Kevin Peel, James Richardson, Ivan Sanchez, and Jordan Stewart.

Chapter 1. Introduction

We’re writing this book for people who are in the same situation now as we were when we first adopted Kotlin.

We expect you’re an experienced programmer who knows Java and the Java ecosystem well. You probably have experience in a number of other languages. You’ve learned the basics of Kotlin, and you recognise that to get the best out of the language you will need to design your systems differently. Some designs that are cumbersome in Java are much easier to achieve in Kotlin. Conversely, Kotlin deliberately removes some features of Java (such as checked exceptions) and de-emphasizes others (such as reflection). You don’t want to end up merely writing Java code in Kotlin syntax.

You have skin in the game. Maybe you’re in a technical leadership position and/or have successfully convinced your team to adopt Kotlin. You’ve spent some political capital to get Kotlin into the project. Now you need to ensure that the transition goes smoothly and doesn’t destabilise the existing, business critical Java code you are responsible for.

This book is not an introduction to Kotlin language features. Instead, we explore how to adapt your design approach to take advantage of those features, and how you can do so safely in a mixed codebase by gradually migrating from Java to Kotlin.

The Grain of a Programming Language

Every programming language has a grain  —  if you work with the grain, things go smoothly; if you go against the grain, things are more difficult.

For example, it has always been possible to write Java code in a functional style, but few programmers did before Java 8  —  for good reasons. Look what was required in Java 1 to do something as simple as calculating the sum of a list of numbers by reducing them with the addition operator.

Java 1 did not have first-class functions, so we would have had to define our own interfaces for function types:

public interface Function2 {
Object apply(Object arg1, Object arg2);
}

Then we would have had to write the reduce higher-order function, hiding the iteration and mutation required by the Vector class. (The Java standard library didn’t include the collections framework yet).

public class Vectors {
public static Object reduce(Vector l, Object initial, Function2 f) {
Object result = initial;
for (int i = 0; i < l.size(); i++) {
result = f.apply(result, l.get(i));
}
return result;
}

// ... and other operations on vectors
}

We would have had to define a separate class for every function you wanted to pass to our reduce function. The addition operator couldn’t be passed around as a value, and the language had no method references, lambdas or closures back then, not even inner classes. Nor did Java 1 have generics or autoboxing  —  we would have had to cast the arguments to the expected type and write the boxing between reference types and primitives.

public class AddIntegers implements Function2 {
public Object apply(Object arg1, Object arg2) {
int i1 = ((Integer) arg1).intValue();
int i2 = ((Integer) arg2).intValue();
return new Integer(i1 + i2);
}
}

And finally we could use all that to calculate the sum:

int sum = ((Integer) Vectors.reduce(counts, new Integer(0), new AddIntegers()))
.intValue();

That’s a lot of effort for what can be achieved with a single expression in a modern language, but that’s not the end of it. Because Java had no standard function types, we couldn’t easily combine different libraries written in a functional style. We had to write adapter classes to map between the function types defined in different libraries. And because the virtual machine had no JIT and a simple garbage collector, functional code would have worse performance than the imperative alternative.

There was just not enough pay-off. Java programmers found it easier to write imperative code that iterated over collections and mutated state.

int sum = 0;
for (int i = 0; i < counts.size(); i++) {
sum += ((Integer)counts.get(i)).intValue();
}

Writing functional code went against the grain of Java.

Every programming language has a grain. It’s easier to work with the grain of a language. Going against the grain involves constant effort with an uncertain pay-off. A language’s grain forms over time as its designers and users build a common understanding of how language features interact, and encode their understanding and preferences in libraries that others build upon. The grain influences the way that programmers write code in the language, which influences the evolution of the language, its libraries and programming tools, changing the grain, altering the way that programmers write code in the language, on and on in a continual cycle of mutual feedback and evolution.

For example, Java 1.1 added anonymous inner classes to the language, and Java 2 added the collections framework to the standard library. Anonymous inner classes meant that we wouldn’t need to write a named class for each function we wanted to pass to our reduce function, but the resulting code would arguably be harder to read.

int sum = ((Integer) Lists.reduce(counts, new Integer(0),
new Function2() {
public Object apply(Object arg1, Object arg2) {
int i1 = ((Integer) arg1).intValue();
int i2 = ((Integer) arg2).intValue();
return new Integer(i1 + i2);
}
})).intValue();

Functional idioms still went against the grain of Java 2.

Java 5 was the next release that significantly changed the language. It added generics and autoboxing, which improved type safety and reduced boilerplate code:

public interface Function2<A,B,R> {
R apply(A arg1, B arg2);
}

int sum = Lists.reduce(counts, 0,
new Function2<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer arg1, Integer arg2) {
return arg1 + arg2;
}
});

Google’s Guava library included some common higher-order functions over collections, although reduce was not among them. Even the authors of Guava recommended writing imperative code where possible, because it had better performance and was usually easier to read.

Functional programming still went largely against the grain of Java 5, but we could see the start of a trend.

Java 8 added anonymous functions (aka “lambda expressions”) and method references to the language, and the Streams API to the standard library. The compiler and virtual machine optimised lambdas to avoid the performance overhead of anonymous inner classes. The Streams API fully embraced functional idioms.

int sum = counts.stream().reduce(0, Integer::sum);

Still, it wasn’t entirely plain sailing. We still could not pass the addition operator as a parameter to our reduce function, but we now had have the standard library function Integer::sum that did the same thing. Java’s type system still created awkward edge cases because of its distinction between reference and primitive types. The Streams API was missing some common higher-order functions that we would expect to find if coming from a functional language (or even Ruby). Checked exceptions didn’t play well with the Streams API and functional programming in general. And making immutable classes with value semantics still involved a lot of boilerplate code. But with Java 8, Java had fundamentally changed to make a functional style work, if not completely with the grain, at least not against it.

In the case of Java, the grain of the language, and the way programmers adapted to it, evolved through several distinct programming styles. Kotlin has features designed specifically to simplify interoperability with code written in these styles.

An Opinionated History of Java Programming Style

Like ancient poets, we divide the development of Java programming style into four distinct ages: the Primeval Age, the Beans Age, the Enterprise Age, and the Modern Age. We’ll use these names as a convenient shorthand when discussing Kotlin’s interop features.

Primeval Style

Originally intended for use in domestic appliances and interactive TV, Java only took off when Netscape adopted Java applets in their hugely popular Navigator browser. Sun released the Java development kit 1.0, Microsoft included Java in Internet Explorer, and suddenly everyone with a web browser had a Java runtime environment. Interest in Java as a programming language exploded.

The fundamentals of Java were in place by this time: the Java virtual machine and its bytecode and classfile format; primitive and reference types; null references; garbage collection; classes and interfaces; methods and control flow statements; checked exceptions for error handling; the abstract windowing toolkit; classes for networking with Internet and web protocols; and the loading and linking of code at runtime, sandboxed by a security manager. However, Java wasn’t yet ready for general purpose programming: the JVM was slow, and the standard library sparse.

Java looked like a cross between C++ and Smalltalk, and those two languages influenced the Java programming style of the time. The “getFoo/setFoo” and “AbstractSingletonProxyFactoryBean” conventions that programmers of other languages poke fun at were not yet widespread.

One of Java’s unsung innovations was an official coding convention that spelled out how programmers should name packages, classes, methods, and variables. C and C++ programmers followed a seemingly infinite variety of coding conventions, and code that combined multiple libraries ended up looking like a right dog’s dinner somewhat inconsistent. Java’s one true coding convention meant that Java programmers could seamlessly integrate strangers’ libraries into their programs, and encouraged widespread use of open source libraries in the Java community.

Bean Style

After its initial success, Sun set out to make Java a practical tool for building applications. Java 1.1 (1996) added to the language (most notably inner classes), improved the runtime (most notably just-in-time compilation and reflection), and extended the standard library. Java 1.2 (1998) added a standard collections API and the Swing cross-platform GUI framework, which ensured Java applications looked and felt equally awkward on every desktop operating system.

At this time, Sun was eyeing Microsoft and Borland’s domination of corporate software development. Java had the potential to be a strong competitor to Visual Basic and Delphi. Sun added a slew of APIs that were heavily inspired by Microsoft APIs: JDBC for data base access (equivalent to Microsoft’s ODBC); Swing for desktop GUI programming (equivalent to Microsoft’s MFC); and the model that had the greatest influence on Java programming style: Java Beans.

Java Beans was Sun’s attempt to compete with Microsoft’s ActiveX component model for low-code, graphical, drag-and-drop programming. Programmers could use ActiveX components in their Visual Basic programs, or embed them in office documents, or in web pages on their corporate intranet. It was notoriously difficult to write an ActiveX component. Java Beans were much easier: you merely had to follow some additional coding conventions for your class to be considered a “bean” that could be instantiated and configured in a graphical designer.

For a class to be a Java Bean, it had to have a constructor that took no arguments, be serializable, and expose an interface made up of public properties that could be read and optionally written, methods that could be invoked, and events that objects of the class would emit. In an application designer, programmers could instantiate beans, set their properties, and connect a bean’s events to the methods of other beans. By default, the Beans API defined properties by methods with names that started with “get” and “set”. This default could be overridden, but doing so required additional classes of boilerplate code. Programmers usually went to the effort when retrofitting existing classes to act as Java Beans. In new code, it was much easier to go with the grain.

The drawback of Beans style is that it relies heavily on mutable state, and requires more of that state to be public than plain old Java objects because you cannot pass parameters to an object’s constructor. Object state must be mutable so that it can be set after construction and objects must often be instantiated in an invalid state  —  with required properties initialised to null, for example  —  and then put into a valid state by setting their public properties. User interface components work well as beans, because they can safely be initialised with default content and styling and adjusted after construction. When we have classes that have no reasonable defaults treating them in the same way is error prone, because the type checker can’t tell us when we have provided all the required values. The Beans conventions make writing correct code harder and changes in dependencies can silently break client code.

In the end, graphical composition of Java beans never became mainstream, but the coding conventions stuck. Java programmers followed the Java Bean conventions even when they had no intention of their class being used as a Java Bean. Beans had an enormous, lasting, and not entirely positive, influence on Java programming style.

Enterprise Style

Java did eventually spread through the enterprise. It didn’t replace Visual Basic on the corporate desktop as expected, but unseated C++ as the server-side language of choice. In 1998, Sun released Java 2 Enterprise Edition (J2EE, later rebranded as JavaEE), a suite of standard APIs for programming server-side, transaction processing systems.

The J2EE APIs suffer from abstraction inversion. The Java Beans and applets APIs also suffer from abstraction inversion  —  they both disallow passing parameters to constructors, for example  —  but it is far more severe in J2EE. J2EE applications don’t have a single entry point. They are composed of many small components whose lifetime is managed by an “application container”, and are exposed to one another through a JNDI name service. Applications need a lot of boilerplate code and mutable state to look up and store the resources they depend on. Programmers responded by inventing dependency injection (DI) frameworks that did all the resource look up and binding and managed lifetimes. The most successful of these is Spring. It builds upon the Java Beans coding conventions, and uses reflection to compose applications from Bean-like objects.

Abstraction Inversion

Abstraction inversion is an architectural flaw in which a software platform prevents client code from using lower level mechanisms that it requires. This forces programmers to reimplement those lower level mechanisms using the higher-level facilities exposed by the platform’s API, which in turn use the very features being reimplemented. The end result is unnecessary code, poor performance, and additional maintenance and testing costs.

In terms of programming style, DI encourages programmers to avoid direct use of the new keyword, and rely on the DI framework to instantiate objects. (We count Android APIs in these as well: they also exhibit abstraction inversion, and Android programmers also turn to DI frameworks to help them write to the APIs). DI frameworks focus on mechanism over domain modelling lead to “enterprisey” class names, such as Spring’s infamous AbstractSingletonProxyFactoryBean.

On the plus side, though, 1999 saw the release of Java 5, which added generics and autoboxing to the language, the most significant change to date. This era also saw a massive uptake of Open Source libraries in the Java community, powered by the Maven packaging conventions and central package repository. The availability of top-notch open source libraries fueled the adoption of Java for business critical application development, and led to more open source libraries, in a virtuous circle. This was followed by best in class development tools, including IntelliJ which we use in this book.

Modern Style

Java 8 brought the next big change to the language  —  lambdas  —  and significant additions to the standard library to take advantage of them. The Streams API encouraged a functional programming style, in which processing is performed by transforming streams of immutable values rather than changing the state of mutable objects. A new date/time API ignored Java Beans coding conventions for property accessors, and followed coding conventions common to the Primeval Style.

The growth of the cloud platforms meant that programmers didn’t need to deploy their servers into JavaEE application containers. Lightweight web application frameworks let programmers write a main function to compose their applications. Many server-side programmers stopped using DI frameworks  —  function and object composition were good enough  —  and DI frameworks released greatly simplified APIs to stay relevant. With no DI framework or mutable state, there’s less need to follow Java Bean coding conventions. Within a single codebase, exposing fields of immutable values works fine, because the IDE can encapsulate a field behind accessors in an instant if they’re needed.

Java 11 introduced modules, but so far they have not seen widespread adoption outside the JDK itself. The most exciting thing about recent Java releases has been the modularisation of the JDK and removal of seldom-used modules, such as CORBA, from the JDK into optional extensions.

The Future

The future of Java promises more features to make Modern Style easier to apply: records, pattern matching, user defined value types, and eventually the unification of primitive and reference types into a uniform type system.

However, this is a challenging effort that will take many years to complete. Java started off with some deep-seated inconsistencies and edge cases that are hard to unify into clean abstractions while staying backwards-compatible. Kotlin has the benefit of 25 years of hindsight, and a clean slate from which to start afresh.

The Grain of Kotlin

Kotlin is a young language, but it clearly has a different grain to Java.

The “Why Kotlin” section of kotlinlang.org lists four design goals: “Concise”, “Safe”, “Interoperable”, and “Tool-friendly”. The designers of the language and its standard library also encoded implicit preferences that contribute to these design goals.

Kotlin prefers the transformation of immutable data to mutation of state.

Data classes make it easy to define new types with value semantics. The standard library makes it easier and more concise to transform collections of immutable data than iterate and mutate data in place.

Kotlin prefers behaviour to be explicit.

For example, there is no implicit coercion between types, even from smaller to larger range. Java implicitly converts int values to long values, because there is no loss of precision. In Kotlin you have to explicitly call Int.toLong(). The preference for explicitness is especially strong when it comes to control flow. Although you can overload arithmetic and comparison operators for your own types, you cannot overload the shortcut logical operators (&& and ||), because that would allow you to define different control flow. Polymorphism is opt-in, not opt-out.

Kotlin prefers static over dynamic binding.

Kotlin encourages a type-safe, compositional coding style. Extension functions are bound statically. By default, classes are not extensible and methods are not polymorphic. You must explicitly opt in to polymorphism and inheritance. If you want to use reflection, you have to add a platform-specific library dependency. Kotlin is designed from the outset to be used with a language aware editor that statically analyses the code to guide the programmer automate navigation and automate program transformation.

Kotlin doesn’t like special cases.

Compared to Java, Kotlin has fewer special cases that interact in unpredictable ways. There is no distinction between primitive and reference types. There is no void type for functions that return but do not return a value  —  functions in Kotlin either return a value, or never return at all. Extension functions allow you to add new operations to existing types that look the same at the call point. You can write new control structures as inline functions and the break, continue and return statements act the same as they do in built-in control structures.

Kotlin breaks its own rules to make migration easier.

The Kotlin language has features to allow idiomatic Java and Kotlin code to coexist in the same codebase. Some of those features remove guarantees provided by type checker, and should only be used to interop with legacy Java. For example, lateinit opens a hole in the type system so that Java dependency injection frameworks that initialise objects by reflection can inject values through the encapsulation boundaries that are normally enforced by the compiler. If you declare a property as lateinit var, it’s up to you to ensure the code initialises the property before reading it  —  the compiler will not catch your mistakes.

Adapting to Kotlin’s Grain

When we look back on the earliest code we wrote in Kotlin, it tends to look like Java dressed in Kotlin syntax. We came to Kotlin after years writing a lot of Java, and had ingrained habits that affected how we wrote Kotlin code. We wrote unnecessary boilerplate, didn’t make good use of the standard library, and avoided using null because we weren’t yet used to the type checker enforcing null safety. The Scala programmers on our team went too far the other way  —  their code looked like Kotlin cosplaying as Scala cosplaying as Haskell. None of us had yet found the sweet spot that comes with working with the grain of Kotlin.

The path to idiomatic Kotlin is complicated by the Java code we have to keep working along the way. In practice, it is not enough to just learn Kotlin. We have to work with the different grains of Java and Kotlin, being sympathetic to both as we gradually transition from one to the other.

Refactoring to Kotlin

When we started our journey to Kotlin we were responsible for maintaining and enhancing business-critical systems. So we were never able to focus only on converting our Java codebase to Kotlin. We always had to migrate code to Kotlin at the same time as changing the system to meet new business needs, maintaining a mixed Java/Kotlin codebase as we did so. We managed the risk by working in small changes, making each easy to understand, and cheap to discard if we found out it broke something. Our process was to first convert Java code to Kotlin, giving us a Java-esque design in Kotlin syntax. We then incrementally applied Kotlin language features to make the code increasingly easier to understand, more type safe, more concise, and with a more compositional structure that is easier to change without unpleasant surprises.

Small, safe, reversible changes that improved the design: we refactored from idiomatic Java to idiomatic Kotlin.

These days most projects are multilingual. A “Java” web app, for example, will involve code written in Java, HTML, CSS, JavaScript, at least one templating language, maybe more if you have templating on the server and in the browser, JSX if you’re using React, and one or more query languages (SQL, etc). It’s common to refactor logic between these languages: persistence logic from Java into a query language or vice versa, browser-side presentation between Javascript code and CSS rules or HTML elements as browsers evolve, or between JavaScript code manipulating the DOM and browser-side templating; server-side presentation between the template language and functions or objects in the host language; and so on.

Refactoring between languages is usually harder than refactoring within a single language because refactoring tools do not work well across the boundaries between the languages, if they work at all. Porting logic from one language to another must be done manually, which takes longer and introduces more risk. Once multiple languages are in use, the language boundary impedes refactoring because when you refactor code in one language, the IDE does not update dependent code written in other languages to be compatible.

What makes the combination of Java and Kotlin unique is how seamless is the boundary between the two languages. Thanks to the design of the Kotlin language, the way it is mapped to the JVM platform, and JetBrains’ investment in developer tooling, refactoring Java to Kotlin and refactoring a combined Java/Kotlin codebase is almost as easy as refactoring in a single codebase.

Our experience has been that we can refactor Java to Kotlin without impacting productivity, and that as more of the codebase is converted to Kotlin, productivity accelerates.

We Minimise Text Edits

When the IDE does not have distinct user-interface actions for a large-scale transformation we wish to do, we have to perform it as a sequence of more granular refactorings. We use the IDE’s automatic refactoring whenever we can, and fall back on text editing when the IDE does not automate a transformation we need.

It’s tedious and error prone to refactor by editing text. To reduce the risk, and our boredom, we minimise the amount of text editing we have to do. If we must edit text, we prefer that edit to affect a single expression. So, we use automatic refactorings to transform the code so that is possible, edit one expression, and then use automatic refactorings to tidy back up to the final state we’re aiming for.

The first time we describe a large-scale refactoring we’ll go through it step by step, and show how the code changes at each step. This takes quite a lot of space on the page, and will take a bit of reading time to follow. In practice, however, these large refactorings are quick to apply. They typically take a few seconds, a few minutes at most.

We Assume Good Test Coverage

If you want to refactor, the essential precondition is having solid tests.
Martin Fowler, Refactoring (1999)

Good test coverage ensures that the code transformations we want to merely improve design have not inadvertently changed our system’s behaviour. In this book, we assume that you have good test coverage. We do not cover how to write automated tests. Other authors have addressed these topics in more detail than we could in the book. For example: [Freeman and Pryce], [Beck]. However, we do show how to apply Kotlin features to simplify JUnit tests.

As we walk through multi-step code transformations, we do not always state when we run the tests. Assume we run our tests after every change, no matter how small.

If your system does not already have good test coverage it can be difficult (and expensive) to retrofit tests to the code because the logic you want to test is entangled with other aspects of the system. You’re in a chicken and egg situation: you have refactor so that you can improve test coverage so that you can refactor. We do not cover how to retrofit tests to existing code. Again, other authors have addressed these topics in more detail than we could. For example: [Feathers].

We Commit For Git Bisect

Just as we don’t explicitly state when we run our tests, nor do we explicitly state when we commit our changes. Assume we commit our changes whenever they have added value to the code, no matter how small.

We know our test suite isn’t perfect. If we do, by accident, break something that is not caught by our tests, we want to find the commit that introduced the fault and fix it as quick as we can.

The git bisect command automates that search. We write a new test that demonstrates the error and git bisect does a binary search of the history to find the first commit that makes that test fail.

If the commits in our history are large, and contain a mish-mash of unrelated changes, git bisect won’t help as much as it could. It cannot tell which of the source changes within a commit introduced the error. If commits mix refactoring and changes to behaviour, reverting a bad refactoring step is likely to break other behaviour in the system.

Therefore, we commit small, focused changes that separate refactorings from each other, and from changes to behaviour, to make it easy to understand what changed and fix any erroneous change.

What Are We Working On?

In the chapters that follow we demonstrate refactorings from Java to Kotlin in the codebase of Travelator, a (fictional) application for planning and booking international, overland travel. Our users plan routes by sea, rail, and road; search for places to stay and sights to see; compare their options by price, time and spectacle; book their trips; and share photos and writings from their travels.

Travelator has web and mobile front-ends that invoke web services via HTTP.

Each chapter pulls an informative example from a different part of the Travelator system, but they share common domain concepts: money, currency conversion, journeys, itineraries, bookings, and so on.

We hope that, like our Travelator application, this book will help you plan your journey from Java to Kotlin.

How This Book is Organised

This book is about how to transition from Java to Kotlin, mainly focused on code, but touching on projects and organisations. The chapters each address an aspect of this transition, looking at some aspect of typical Java projects that can be improved on the journey. They are named in the pattern Java Way to Kotlin Way, where we recommend that you prefer the latter over the former. Maybe Kotlin makes easier an approach that was difficult in Java, or Kotlin discourages an approach that is common in Java, to guide design in a direction that is less error prone, more concise, and more tool-friendly.

We don’t just recommend you adopt the Kotlin way though; the chapters also show how to make the transformation. Not by just re-writing the Java, but by gradually refactoring it to Kotlin it in a way that is safe and allows us to maintain a mixed language codebase.

How did we choose the topics? We looked for places where the grain of Kotlin and Java differ significantly. The topics are not exhaustive: we cannot cover every way in which the languages differ. We cover the areas that we found most notable as we adopted Kotlin. We also favour transformations that result in code that is less complex.

Complexity

How should we judge the internal quality of our software? Assuming that it does what our customers want or need it to do, how can we compare two potential implementations, or decide whether a change makes one better or worse? The answer that your authors choose is complexity. Other things being equal, we favour simple designs that yield predictable behaviour.

Of course to some extent simplicity and complexity are in the eye of the beholder. Even your authors sometimes disagree over whether one implementation or another is better. Where we disagree, we sometimes discuss it in the relevant chapter. We do though have a shared belief in the power of functional programming to reduce the complexity of our systems, especially when combined with object-oriented message passing.

Java has been moving in this direction over the years. Scala ran full-tilt towards functional programming but away from OO. We have found that the grain of Kotlin allows the mixing of functional and object programming in a way that reduces complexity and allows the writing of high-quality software by mere mortal developers.

Code Conventions

Our code follows (our interpretation of) the standard coding conventions of Java and Kotlin where possible.

We have though had to split more lines than we might usually to make the code fit horizontally in the pages of the book. Where our production Kotlin would usually have up to 4 or 5 parameters to a function definition, or arguments to a function call, on one line; to fit in the book we will often move to one item per line after only 3. That said, we find that Kotlin feels natural taking more vertical space than Java, and scrolling sideways is almost as inconvenient in an IDE as a book - so maybe our production style will move to be more like that you see here.

We will sometimes hide code that isn’t relevant to the discussion. A line comment that starts with an ellipsis of three dots indicates that we have omitted some code for clarity or brevity. For example, in the following code, the comment is not part of the example, but indicates that we have omitted overloads of the Money function that are not relevant to the text.

fun Money(amount: String, currency: Currency) =
Money(BigDecimal(amount), currency)

// ... and other convenience overloads

Directions in Code

There are lots of places in this book where we show how to move code, or some aspect of code, from one place to another. For example in Chapter 3, we talk about moving the mutability in an interaction from a mutable class to a mutable property. We’d like to have a shorthand for the direction of these changes, but there are many ways to think about the orientation of code: base or super classes, layer diagrams, onion architecture, hexagaonal architecture etc. In some cases our execution paths are top-down, some bottom up, some outside in, some outside to inside to outside to inside to outside.

Unless stated otherwise, we will refer to direction in terms of dependencies, with the entry points (main and event handlers) at the top of the system. This top-level code should be at the highest level of abstraction, and will be built with types and functions provided by lower-level code, which will eventually depend on the core Kotlin and Java runtimes, until at the bottom we reach code provided by the operating system and chipset.

Unless we are specifically writing a library to be depended on by other systems, we are usually writing the top-level code in a system, and some of the layers beneath it. We are only able to refactor within the layers that we are able to change. If we say that we are moving some aspect of code (functionality or mutability or IO or error handling) up, it means towards the top of the call-stack and hence the entry point - in the direction of the code with the most dependencies.

The reality of software is that this metaphor will sometimes fail us. In these cases we hope to make ourselves clear in context.

Chapter 2. Java To Kotlin Projects

What is the first step of the journey from pure Java to a mixed and then increasingly Kotlin codebase?

The first time we introduced Kotlin to a Java codebase, we were a small team that included six developers, building a relatively greenfield project. We had already deployed some web applications with Kotlin, but our enterprise architects insisted that we write the new system in Java 8. This was shortly after Kotlin 1.0 had been released, but before Google announced that Kotlin was an official language for Android, so the architects were understandably wary about committing to a language with an uncertain future for a strategic system that they expected to be around for decades.

In Java, we leaned towards a functional approach, designing the core application domain model as immutable data types transformed by pipelines. However, we kept bumping into Java’s limitations: the verbosity required to implement immutable value types, the distinction between primitive and reference types, null references, Streams lacking common higher-order functions. Meanwhile, we could see Kotlin being adopted at an ever-increasing rate across the industry, and even within the company. When we saw Google’s announcement, we decided to start converting our Java to Kotlin.

Our judgement was that starting in the core domain model would give us the biggest bang for our buck. Kotlin’s data classes shrunk the code significantly, in some cases replacing hundreds of lines of code with a single declaration. We started carefully, using IntelliJ to convert a small value class that had no dependencies on other classes beyond those in the standard library, and examined how that affected the rest of our Java codebase. It had no effect at all! Emboldened by this success we picked up the pace. Whenever a new feature needed changes to a Java domain model class, we would first convert it to a Kotlin data class, commit the conversion, and then implement the feature.

As more of the domain model logic became pure Kotlin, we were able to make better use of Kotlin features. For example, we replaced calls to the Stream API with Kotlin’s standard functions on collections and sequences. The biggest improvement though, was replacing our use of Java’s Optional type with nullable references. This simplified our code and gave us greater confidence in its null safety.

Another project in the company adopted Kotlin for a different reason. They had a mature Java system that was built on Spring. The developers found that the use of reflection and annotations made the code difficult to understand and navigate in the IDE. Kotlin’s lightweight syntax for closures offered a way to define the structure of their application and distinguish between object graphs instantiated for the whole application, for each HTTP request, or each database transaction. They gradually refactored the underpinnings of their system from a framework that obscured the architecture to a compositional style in which the architecture was visible in the code. This work became the HTTP4K HTTP toolkit.

As these two examples show, the choice of starting point depends on a number of factors: why is the team adopting Kotlin, how large is the codebase, how frequently is it changed, … You probably have reasons aren’t in this list.

If you are choosing Kotlin for its language features it makes sense to convert the classes you are working in most frequently, as we did in the first example above. If you are choosing Kotlin to use a specific library then it makes sense to start writing Kotlin against the API, annotate it to make your Kotlin code convenient to the Java code in the rest of the app, and continue from there.

In a small team it’s easy to establish the Kotlin coding style for your system (beyond the standard style guide) — eg error handling conventions, how code is to be organised into files, what should be a top-level declaration and what should be in an object, etc.

Above a certain size, you run the risk of Kotlin code becoming inconsistent as people establish their own conventions in different parts of the system. So it may be worth starting with a small sub-team working in one area of the system, who establish conventions and build up a body of example code. Once there are some established conventions, you can expand the effort to the rest of the team and other parts of the system.

In the rest of this book we will examine in detail how to progress, how to keep your Java code maintainable while you are introducing Kotlin that it depends upon, how to take advantage of Kotlin’s features to further simplify the code after IntelliJ has performed its conversion magic. But all that follows the first small step.

Migrating a Java Build to Kotlin

If we want to refactor our Java to Kotlin, the first change we must make is to give ourselves the ability to write Kotlin code in our codebase. Happily, the Kotlin build tools and IDE make this very straightforward. It takes a few additional lines in our Gradle build configuration for it to compile Kotlin as well as Java. The IntelliJ IDE will pick up that configuration when we resync the build file, allowing us to navigate, autocomplete and refactor across both languages almost seamlessly.

To add Kotlin to our Gradle build we need to add the Kotlin plugin. There is a different plugin for each target that Kotlin supports (JVM, JavaScript and native code), and a plugin for building multiplatform projects. Because we have a Java project, we can ignore other platforms and use the Kotlin JVM plugin.

We also need to add the Kotlin standard library to our dependencies, and specify the minimumev JVM version that the output bytecode will support. Our project targets JDK 11 (the latest LTS at the time of writing). At the time of writing, the Kotlin compiler can generate bytecode compatible with JDK 1.6 or JDK 1.8. JDK 1.8 bytecode is more efficient and runs fine on JDK 11, so we will pick that.

Here are the relevant parts of our Gradle build file before changes.

plugins {
id("java")
}

java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
// ... and other project settings ...

dependencies {
implementation "com.fasterxml.jackson.core:jackson-databind:2.10.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.10.0"
// ... and the rest of our app's implementation dependencies

testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
testImplementation "org.junit.jupiter:junit-jupiter-params:5.4.2"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.5.2"
testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.4.2"
// ... and the rest of our app's test dependencies
}

// ... and the rest of our build rules

and after

plugins {
id 'org.jetbrains.kotlin.jvm' version "1.4.10"
}

java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
// ... and other project settings ...

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

implementation "com.fasterxml.jackson.core:jackson-databind:2.10.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.10.0"
// ... and the rest of our app's implementation dependencies

testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
testImplementation "org.junit.jupiter:junit-jupiter-params:5.4.2"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.5.2"
testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.4.2"
// ... and the rest of our app's test dependencies
}

compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}

// ... and the rest of our build rules

Given those changes, we can rerun our build, and see that …

…

1. the build still works!

We can also resync the Gradle project in IntelliJ, and after waiting a while for IntelliJ to do some indexing, we can run our tests and programs within the IDE.

Our tests still pass, so we haven’t broken anything, but neither have we proved that we can use Kotlin in our project. Let’s test that by writing a hello world program in the root package of our Java source tree:

fun main() {
println("hello, world")
}

We can run that within the IDE by clicking on the little green arrow in the left hand margin next to “fun main()”.

And we can run our build, and then run it from the command line with the java command. Compiling the source file named HelloWorld.kt creates a Java class file named HelloWorldKt. We’ll look into how Kotlin source gets translated into Java class files in more detail later, but for now, we can run our program with the java command like so:

$ java -cp build/classes/kotlin/main HelloWorldKt
hello, world

It lives!

Let’s delete HelloWorld.kt  —  it’s done its job  —  commit and push.

We now have the option to use Kotlin in our project - the first part of this chapter gives some pointers to where to start using it.

Conclusions

Adding Kotlin to a Java build is straightforward, even if we expect the instructions here to be out of date for most of the life of this book.

Devising a strategy for moving code from Java to Kotlin is more complicated and context-specific. Individual projects should examine where Java is and isn’t working for them, and where using Kotlin would alleviate problems and improve the code. In ???, we’ll look at what is involved in converting an existing Java class to Kotlin.

Chapter 3. Beans to Values

Many Java projects have settled on mutable JavaBeans or POJO (plain old Java object) conventions for representing data. Mutability brings complications though - why are immutable values a better choice, and how can we reduce the cost of mutability in a codebase?

As we discuss in “Bean Style”, JavaBeans were introduced to allow the development of drag-and-drop GUI builders in the Visual Basic style. A developer could drop a button onto a form, change its title and icon and then wire in an on-click handler. Behind the scenes the GUI builder would write code to instantiate a button object and then call setters for the properties that the developer had changed.

To define a JavaBean, a class needs a default (no-argument) constructor, getters for its properties and setters for its mutable properties.[1] This makes a lot of sense for objects that have a lot of properties. GUI components typically have foreground and background colors, font, label, borders, size, alignments, paddings etc. Mostly the defaults for these properties are fine, so calling setters for just the special values minimizes the amount of code to be generated. In fact, if we were to write a GUI toolkit today, I think we’d probably still opt for a similar mutable component model.

When JavaBeans were introduced though, we thought of most objects as mutable, not just UI components. I mean, why not? - the point of objects was to encapsulate properties and manage the relationships between them. They were designed to solve problems like updating the width of a component when its bounds are changed, or the total of a shopping cart as items are added. Objects were the solution to the problem of managing mutable state. Java was quite radical at the time in having an immutable String class (although it couldn’t help itself and still plumped for a mutable Date).

We have a more sophisticated understanding these days, speaking as both your authors and the industry. We appreciate that we can use objects to represent different types of things - values, entities, services, actions, transactions etc. And yet the default pattern for a Java object is still the JavaBean - a mutable object with getters and setters for its properties. Although it may be appropriate for a UI toolkit, this is not a good default pattern. For most things that we want to represent with objects, a value would be better.

What is a Value?

Value is a much overloaded term in English. In computing we say that variables, parameters and fields have values - the primitive or reference that they are bound to. When we refer to a value in this book we are referring to a specific type of primitive or reference - those with value semantics. An object has value semantics if only its value is significant in its interactions, not its identity. Java primitives all have value semantics: every 7 is equal to every other 7. The objects that are the targets of references may or may not have value semantics though; in particular, mutable objects do not. In later chapters we’ll look at finer distinctions, but for now, let’s just define a value to be an immutable piece of data, and a value type to be a type that defines the behaviour of an immutable piece of data.

So 7 is a value, and the boxed Integer is a value type (because boxed types are immutable). banana is a value (because Strings are immutable), a URI is a value (because URIs are immutable), but java.util.Date is not a value type (because we can call setYear etc on the date).

An instance of an immutable DBConnectionInfo is a value, but an instance of Database is not a value, even if all its properties are immutable. This is because it is not a piece of data, it is a means of accessing pieces of data. ??? discusses the difference between data and other immutable objects.

Are JavaBeans values? In the case of UI components JavaBeans are not values. UI components aren’t just data - two otherwise identical buttons have different identities. In the case of beans used to represent plain data, it will depend on whether they are immutable. It is possible to create immutable beans - just don’t define any setter methods and add the ability to initialise the properties in at least one non-default constructor. This is (serialization aside) technically a bean, but most developers would think of it more as a POJO.

Are POJOs values? The term was coined to refer to classes that don’t have to extend from framework types in order to be useful. They usually represent data and conform to the JavaBeans conventions for accessor methods. Many POJOs will not have a default constructor, but instead define constructors to initialize properties that don’t have sensible defaults. Because of this, immutable POJOs are common, and may have value semantics. Mutable POJOs still seem to be the default though, so much so that many people consider that object-oriented programming in Java is synonymous with mutable objects. Mutable POJOs are not values.

In summary, a bean could technically be a value, but rarely is. POJOs more often have value semantics, especially in the modern Java age. So whilst Beans to Values is snappy, in this chapter we’re really looking at migrating from mutable objects to immutable data, so maybe we should have called it Mutable POJOs to Values. We hope you’ll forgive the clickbait chapter title.

Why Should We Prefer Values?

A value is immutable data. Why we should prefer immutable objects to mutable objects, and objects that represent data to other types of object. This is a theme that we will visit time and again in this book. But for now, we prefer immutable objects because they are much easier to reason about:

- We can put them into sets or use them as map keys.

- We never have to worry about an immutable collection changing as we iterate over its contents.

- We can explore different scenarios without having to deep-copy initial states, and simply implement undo and redo.

- We can safely share immutable objects between different threads or different processes.

The advantages of plain data over more active objects are more nuanced and are discussed in detail in ???, ???, ???.

Migrating Beans to Values

Let’s look at migrating a use of a mutable bean or POJO to a value.

We represent preferences in the Travelator mobile app with a UserPreferences JavaBean.

public class UserPreferences {

private String greeting;
private Locale locale;
private Currency currency;

public UserPreferences() {
this("Hello", Locale.UK, Currency.getInstance(Locale.UK));
}

public UserPreferences(String greeting, Locale locale, Currency currency) {
this.greeting = greeting;
this.locale = locale;
this.currency = currency;
}

public String getGreeting() {
return greeting;
}

public void setGreeting(String greeting) {
this.greeting = greeting;
}

public Locale getLocale() {
return locale;
}

public void setLocale(Locale locale) {
this.locale = locale;
}

public Currency getCurrency() {
return currency;
}

public void setCurrency(Currency currency) {
this.currency = currency;
}
}

The Application has a preferences property, which it passes to views that need it.

public class Application {

private final UserPreferences preferences;

public Application(UserPreferences preferences) {
this.preferences = preferences;
}

public void showWelcome() {
new WelcomeView(preferences).show();
}

public void editPreferences() {
new PreferencesView(preferences).show();
}
...
}

Finally PreferencesView updates its preferences when the user makes changes.

public class PreferencesView extends View {

private final UserPreferences preferences;
private final GreetingPicker greetingPicker = new GreetingPicker();
private final LocalePicker localePicker = new LocalePicker();
private final CurrencyPicker currencyPicker = new CurrencyPicker();

public PreferencesView(UserPreferences preferences) {
this.preferences = preferences;
}

public void show() {
greetingPicker.setGreeting(preferences.getGreeting());
localePicker.setLocale(preferences.getLocale());
currencyPicker.setCurrency(preferences.getCurrency());
super.show();
}

protected void onGreetingChange() {
preferences.setGreeting(greetingPicker.getGreeting());
}

protected void onLocaleChange() {
preferences.setLocale(localePicker.getLocale());
}

protected void onCurrencyChange() {
preferences.setCurrency(currencyPicker.getCurrency());
}
...
}

This design, whilst simple, is fraught with complications typical of mutable data, such as:

- If the PreferencesView and WelcomeView are both active, the WelcomeView can get out of sync with the current values.

- UserPreferences equality and hashCode depend on the values of its properties, which may be changed. So we can’t reliably use it in sets or as keys in maps.

- There is nothing to indicate that the WelcomeView only reads from the preferences. If it accidentally changed a value that would probably be a defect.

- Errors in the PreferencesView could lead to data corruption, especially if there is coupling between preference properties, or between preferences and other application data.

- If reading and writing occur on different threads we have to manage synchronisation at the preference property level.

Before we refactor to using an immutable value, let’s convert Application and UserPreferences to Kotlin, which will help us see the nature of our model. Application is simple:

class Application(
private val preferences: UserPreferences
) {
fun showWelcome() {
WelcomeView(preferences).show()
}

fun editPreferences() {
PreferencesView(preferences).show()
}
...
}

UserPreferences is more complicated. At the time of writing, IntelliJ yields this:

class UserPreferences @JvmOverloads constructor(
var greeting: String = "Hello",
var locale: Locale = Locale.UK,
var currency: Currency = Currency.getInstance(Locale.UK)
)

That @JVMOverloads annotation tells the compiler to generate constructors allowing combinations of greeting, locale or currency to be defaulted. This wasn’t what our original Java did - it had just two constructors. Arguably the conversion is more flexible, but it is unnecessary complication, so let’s simplify.

class UserPreferences(
var greeting: String,
var locale: Locale,
var currency: Currency
) {
constructor() : this(
greeting = "Hello",
locale = Locale.UK,
currency = Currency.getInstance(Locale.UK)
)
}

At this stage we haven’t changed the functioning of our application, just simplified its expression. Those var (as opposed to val) properties are the sign that we have mutable data. It’s worth reminding ourselves at this point, that the Kotlin compiler is going to generate a private field, a getter method, and a setter method for each property, so that our Java continues to see the data class as a bean. Kotlin embraces the beans naming convention.

How now do we make UserPreferences immutable? After all, we do want the preferences seen in the app to reflect any changes the user makes. The answer is to move the mutation. In common with many of the refactorings in this book, we’re going to move the problematic thing (in this case mutation) up. Which is to say, towards the entry point, or into the higher-level, more application-specific code (see “Directions in Code”). Instead of mutating the preferences, we are going to update the reference in the Application. The reference we’re going to use will be an updated copy returned by PreferencesView.

Instead of making all the changes at once, let’s start by converting PreferencesView to Kotlin and return the existing mutable preferences from a showModal method:

class PreferencesView(
private val preferences: UserPreferences
) : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()

fun showModal(): UserPreferences {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
show()
return preferences
}

protected fun onGreetingChange() {
preferences.greeting = greetingPicker.greeting
}

protected fun onLocaleChange() {
preferences.locale = localePicker.locale
}

protected fun onCurrencyChange() {
preferences.currency = currencyPicker.currency
}
...
}

Now make Application.preferences a mutable property, set from the result of showModal:

class Application(
private var preferences: UserPreferences
) {
...

fun editPreferences() {
preferences = PreferencesView(preferences).showModal()
}
...
}

This gives us the worst of both worlds, a mutable reference to mutable data, but we haven’t finished. PreferencesView can now copy rather than mutate its preferences.

class PreferencesView(
private var preferences: UserPreferences
) : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()

fun showModal(): UserPreferences {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
show()
return preferences
}

protected fun onGreetingChange() {
preferences = UserPreferences(
greetingPicker.greeting,
preferences.locale,
preferences.currency
)
}

protected fun onLocaleChange() {
preferences = UserPreferences(
preferences.greeting,
localePicker.locale,
preferences.currency
)
}

protected fun onCurrencyChange() {
preferences = UserPreferences(
preferences.greeting,
preferences.locale,
currencyPicker.currency
)
}
...
}

Now there are no setters called on preferences - we can make it a proper value. While we’re there we’ll get rid of that irritating default constructor by inlining it into the one place in the code that it was used.

data class UserPreferences(
val greeting: String,
val locale: Locale,
val currency: Currency
)

The eagle-eyed reader will notice that we sneakily made UserPreferences a data class. We didn’t do that before now, because it was mutable, and while Kotlin allows mutable data classes they are suspect, because the meaning of equality is then much more complicated.

What have we achieved so far? We’ve replaced two immutable references to a mutable value, with two mutable references to immutable values. Now we can see at a glance which views can update the preferences, and if we had to manage updates across threads we could do that at the application level. Having a mutable reference in PreferencesView is a bit irritating though. We can fix that by not holding a reference at all, but instead passing the preferences into showModal. In this scheme there is no point in updating the individual preference properties when their respective pickers update.

class PreferencesView : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()

fun showModal(preferences: UserPreferences): UserPreferences {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
show()
return UserPreferences(
greeting = greetingPicker.greeting,
locale = localePicker.locale,
currency = currencyPicker.currency
)
}
...
}

class Application(
private var preferences: UserPreferences
) {
...

fun editPreferences() {
preferences = PreferencesView().showModal(preferences)
}
...
}

It may seem a bit of a waste to create a new UserPreferences to return from showModal even if nothing has changed. If you’re used to sharing mutable objects it may even seem dangerous. In the world of values though, two UserPreferences with the same values are to most intents and purposes the same object, and you would have to be in a very constrained environment to detect the extra allocation.

Conclusions

In this chapter we’ve examined some advantages of immutable values over mutable objects. We’ve seen how to migrate mutation towards our application’s entry points and event handlers by replacing immutable references to mutable objects with mutable references to immutable objects. The end result is that less of our code has to deal with the consequences and complications of mutability.

That said, JavaBeans were designed for use in user-interface frameworks, and UIs are in many ways the last bastion of mutable objects. If we had more exacting liveness requirements, for example updating a WelcomeView when the greeting preference changed, we might prefer using shared objects with change events, rather than immutable values.

Converting mutable objects to values and transformations is a repeating motif. ??? continues the discussion with respect to collections. ??? looks at how to translate code using accumulating parameters to higher order functions over collections.

[1] We’ll gloss over the Serializable requirement as even Sun never really took this seriously.

About the Authors

Between them, Nat and Duncan have over five decades of professional experience in software development. They’ve written a lot of software in a lot of different industries. They’ve also written a lot about writing software, written software to help them write about writing software, given talks about writing software, run workshops about writing software at conferences, and helped organise conferences about writing software.

A lot of the software they wrote they wrote in Java. Not all, but a lot. They are old enough to remember when Java was a breath of fresh air compared to C++. And now they find Kotlin to be a breath of fresh air compared to Java.

So they’ve written a book about writing software with Kotlin (and wrote some software to help them write it).

1. Preface

1. Conventions Used in This Book

2. Using Code Examples

3. O’Reilly Online Learning

4. How to Contact Us

5. Acknowledgments

2. 1. Introduction

1. The Grain of a Programming Language

2. An Opinionated History of Java Programming Style

1. Primeval Style

2. Bean Style

3. Enterprise Style

4. Modern Style

5. The Future

3. The Grain of Kotlin

4. Adapting to Kotlin’s Grain

5. Refactoring to Kotlin

1. We Minimise Text Edits

2. We Assume Good Test Coverage

3. We Commit For Git Bisect

6. What Are We Working On?

7. How This Book is Organised

8. Complexity

9. Code Conventions

10. Directions in Code

3. 2. Java To Kotlin Projects

1. Migrating a Java Build to Kotlin

2. Conclusions

4. 3. Beans to Values

1. What is a Value?

2. Why Should We Prefer Values?

3. Migrating Beans to Values

4. Conclusions