The Well-Grounded Java Developer, Second Edition
By Benjamin Evans, Martijn Verburg and Jason Clark
()
About this ebook
In The Well-Grounded Java Developer, Second Edition you will learn:
The new Java module system and why you should use it
Bytecode for the JVM, including operations and classloading
Performance tuning the JVM
Working with Java’s built-in concurrency and expanded options
Programming in Kotlin and Clojure on the JVM
Maximizing the benefits from your build/CI tooling with Maven and Gradle
Running the JVM in containers
Planning for future JVM releases
The Well-Grounded Java Developer, Second Edition introduces both the modern innovations and timeless fundamentals you need to know to become a Java master. Authors Ben Evans, Martijn Verburg, and Jason Clark distill their decades of experience as Java Champions, veteran developers, and key contributors to the Java ecosystem into this clear and practical guide. You’ll discover how Java works under the hood and learn design secrets from Java’s long history. Each concept is illustrated with hands-on examples, including a fully modularized application/library and creating your own multithreaded application.
Foreword by Heinz Kabutz.
About the technology
Java is the beating heart of enterprise software engineering. Developers who really know Java can expect easy job hunting and interesting work. Written by experts with years of boots-on-the-ground experience, this book upgrades your Java skills. It dives into powerful features like modules and concurrency models and even reveals some of Java’s deep secrets.
About the book
With The Well-Grounded Java Developer, Second Edition you will go beyond feature descriptions and learn how Java operates at the bytecode level. Master high-value techniques for concurrency and performance optimization, along with must-know practices for build, test, and deployment. You’ll even look at alternate JVM languages like Kotlin and Clojure. Digest this book and stand out from the pack.
What's inside
The new Java module system
Performance tuning the JVM
Maximizing CI/CD with Maven and Gradle
Running the JVM in containers
Planning for future JVM releases
About the reader
For intermediate Java developers.
About the author
Benjamin J. Evans is a senior principal engineer at Red Hat. Martijn Verburg is the principal SWE manager for Microsoft’s Java Engineering Group. Both Benjamin and Martijn are Java Champions. Jason Clark is a principal engineer and architect at New Relic.
Table of Contents
PART 1 - FROM 8 TO 11 AND BEYOND!
1 Introducing modern Java
2 Java modules
3 Java 17
PART 2 - UNDER THE HOOD
4 Class files and bytecode
5 Java concurrency fundamentals
6 JDK concurrency libraries
7 Understanding Java performance
PART 3 - NON-JAVA LANGUAGES ON THE JVM
8 Alternative JVM languages
9 Kotlin
10 Clojure: A different view of programming
PART 4 - BUILD AND DEPLOYMENT
11 Building with Gradle and Maven
12 Running Java in containers
13 Testing fundamentals
14 Testing beyond JUnit
PART 5 - JAVA FRONTIERS
15 Advanced functional programming
16 Advanced concurrent programming
17 Modern internals
18 Future Java
Benjamin Evans
Ben Evans is a principal engineer at New Relic, a Java Champion, and the Java/JVM track lead at InfoQ, as well as a frequent contributor to Oracle's Java Magazine, and a regular speaker at conferences worldwide. He co-founded the Adopt-a-JSR and AdoptOpenJDK initiatives, and served on the Java Community Process Executive Committee for 6 years. Ben has also authored Optimizing Java, Java in a Nutshell, 7th Edition, and Java: The Legend.
Related to The Well-Grounded Java Developer, Second Edition
Related ebooks
Data-Oriented Programming: Reduce software complexity Rating: 4 out of 5 stars4/5MongoDB in Action: Covers MongoDB version 3.0 Rating: 0 out of 5 stars0 ratingsTesting Java Microservices: Using Arquillian, Hoverfly, AssertJ, JUnit, Selenium, and Mockito Rating: 0 out of 5 stars0 ratingsModern Java in Action: Lambdas, streams, functional and reactive programming Rating: 0 out of 5 stars0 ratingsDocker in Action, Second Edition Rating: 3 out of 5 stars3/5Netty in Action Rating: 0 out of 5 stars0 ratingsJavaScript Application Design: A Build First Approach Rating: 0 out of 5 stars0 ratingsKotlin in Action Rating: 5 out of 5 stars5/5Node.js in Action Rating: 0 out of 5 stars0 ratingsKafka in Action Rating: 0 out of 5 stars0 ratingsFunctional Programming in C#, Second Edition Rating: 0 out of 5 stars0 ratings.NET Core in Action Rating: 0 out of 5 stars0 ratingsJava Persistence with Spring Data and Hibernate Rating: 0 out of 5 stars0 ratingsFunctional Programming in JavaScript: How to improve your JavaScript programs using functional techniques Rating: 0 out of 5 stars0 ratingsMetaprogramming in .NET Rating: 5 out of 5 stars5/5Functional Programming in Scala Rating: 4 out of 5 stars4/5Python Concurrency with asyncio Rating: 0 out of 5 stars0 ratingsSoftware Mistakes and Tradeoffs: How to make good programming decisions Rating: 0 out of 5 stars0 ratingsThe Joy of JavaScript Rating: 0 out of 5 stars0 ratingsExpress in Action: Writing, building, and testing Node.js applications Rating: 4 out of 5 stars4/5Secrets of the JavaScript Ninja Rating: 4 out of 5 stars4/5GraphQL in Action Rating: 2 out of 5 stars2/5Rust in Action Rating: 4 out of 5 stars4/5Scala in Action Rating: 0 out of 5 stars0 ratingsC# in Depth Rating: 5 out of 5 stars5/5React Quickly: Painless web apps with React, JSX, Redux, and GraphQL Rating: 0 out of 5 stars0 ratingsDSLs in Action Rating: 4 out of 5 stars4/5Go in Practice Rating: 5 out of 5 stars5/5Haskell in Depth Rating: 0 out of 5 stars0 ratingsTypeScript Quickly Rating: 0 out of 5 stars0 ratings
Programming For You
Learn to Code. Get a Job. The Ultimate Guide to Learning and Getting Hired as a Developer. Rating: 5 out of 5 stars5/5Learn PowerShell in a Month of Lunches, Fourth Edition: Covers Windows, Linux, and macOS Rating: 5 out of 5 stars5/5Python Programming : How to Code Python Fast In Just 24 Hours With 7 Simple Steps Rating: 4 out of 5 stars4/5Coding All-in-One For Dummies Rating: 4 out of 5 stars4/5SQL QuickStart Guide: The Simplified Beginner's Guide to Managing, Analyzing, and Manipulating Data With SQL Rating: 4 out of 5 stars4/5HTML in 30 Pages Rating: 5 out of 5 stars5/5Excel : The Ultimate Comprehensive Step-By-Step Guide to the Basics of Excel Programming: 1 Rating: 5 out of 5 stars5/5Excel 101: A Beginner's & Intermediate's Guide for Mastering the Quintessence of Microsoft Excel (2010-2019 & 365) in no time! Rating: 0 out of 5 stars0 ratingsPython Data Structures and Algorithms Rating: 5 out of 5 stars5/5PYTHON: Practical Python Programming For Beginners & Experts With Hands-on Project Rating: 5 out of 5 stars5/5C# Programming from Zero to Proficiency (Beginner): C# from Zero to Proficiency, #2 Rating: 0 out of 5 stars0 ratingsHTML & CSS: Learn the Fundaments in 7 Days Rating: 4 out of 5 stars4/5Python QuickStart Guide: The Simplified Beginner's Guide to Python Programming Using Hands-On Projects and Real-World Applications Rating: 0 out of 5 stars0 ratingsLinux Command-Line Tips & Tricks Rating: 0 out of 5 stars0 ratingsBeginning Programming with C++ For Dummies Rating: 4 out of 5 stars4/5SQL: For Beginners: Your Guide To Easily Learn SQL Programming in 7 Days Rating: 5 out of 5 stars5/5Learning JavaScript Data Structures and Algorithms Rating: 5 out of 5 stars5/5Python Games from Zero to Proficiency (Beginner): Python Games From Zero to Proficiency, #1 Rating: 0 out of 5 stars0 ratingsThe Most Concise Step-By-Step Guide To ChatGPT Ever Rating: 3 out of 5 stars3/5Grokking Algorithms: An illustrated guide for programmers and other curious people Rating: 4 out of 5 stars4/5
Reviews for The Well-Grounded Java Developer, Second Edition
0 ratings0 reviews
Book preview
The Well-Grounded Java Developer, Second Edition - Benjamin Evans
The Well-Grounded Java Developer
Second Edition
Benjamin Evans, Jason Clark, and Martijn Verburg
Foreword by Heinz Kabutz
To comment go to liveBook
Manning
Shelter Island
For more information on this and other Manning titles go to
www.manning.com
Copyright
For online information and ordering of these and other Manning books, please visit www.manning.com. The publisher offers discounts on these books when ordered in quantity.
For more information, please contact
Special Sales Department
Manning Publications Co.
20 Baldwin Road
PO Box 761
Shelter Island, NY 11964
Email: orders@manning.com
©2022 by Manning Publications Co. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps.
♾ Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine.
ISBN: 978161728875
Praise for the First Edition
At the cutting edge of Java development...learn to speak Java 7 and next-gen languages.
—Paul Benedict, Corporate Personnel & Associates
Buy this book for what's new in Java 7. Keep it open for lessons in expert Java.
—Stephen Harrison, PhD, FirstFuel Software
A great collection of knowledge on the JVM platform.
—Rick Wagner, Red Hat
How to become a well-grounded Java developer—and how to stay that way.
—From the Foreword by Dr. Heinz Kabutz, The Java Specialists’ Newsletter
brief contents
Part 1. From 8 to 11 and beyond!
1 Introducing modern Java
2 Java modules
3 Java 17
Part 2. Under the hood
4 Class files and bytecode
5 Java concurrency fundamentals
6 JDK concurrency libraries
7 Understanding Java performance
Part 3. Non-Java languages on the JVM
8 Alternative JVM languages
9 Kotlin
10 Clojure: A different view of programming
Part 4. Build and deployment
11 Building with Gradle and Maven
12 Running Java in containers
13 Testing fundamentals
14 Testing beyond JUnit
Part 5. Java frontiers
15 Advanced functional programming
16 Advanced concurrent programming
17 Modern internals
18 Future Java
Appendix A. Selecting your Java
Appendix B. Recap of streams in Java 8
contents
Front matter
foreword
preface
acknowledgements
about this book
about the authors
about the cover illustration
Part 1. From 8 to 11 and beyond!
1 Introducing modern Java
1.1 The language and the platform
1.2 The new Java release model
1.3 Enhanced type inference (var keyword)
1.4 Changing the language and the platform
Sprinkling some sugar
Changing the language
JSRs and JEPs
Incubating and preview features
1.5 Small changes in Java 11
Collections factories (JEP 213)
Remove enterprise modules (JEP 320)
HTTP/2 (Java 11)
Single-file source-code programs (JEP 330)
2 Java modules
2.1 Setting the scene
Project Jigsaw
The module graph
Protecting the internals
New access control semantics
2.2 Basic modules syntax
Exporting and requiring
Transitivity
2.3 Loading modules
Platform modules
Application modules
Automatic modules
Unnamed module
2.4 Building a first modular app
Command-line switches for modules
Executing a modular app
Modules and reflection
2.5 Architecting for modules
Split packages
Java 8 Compact Profiles
Multi-release JARs
2.6 Beyond modules
3 Java 17
3.1 Text Blocks
3.2 Switch Expressions
3.3 Records
Nominal typing
Compact record constructors
3.4 Sealed Types
3.5 New form of instanceof
3.6 Pattern Matching and preview features
Part 2. Under the hood
4 Class files and bytecode
4.1 Class loading and class objects
Loading and linking
Class objects
4.2 Class loaders
Custom class loading
Modules and class loading
4.3 Examining class files
Introducing javap
Internal form for method signatures
The constant pool
4.4 Bytecode
Disassembling a class
The runtime environment
Introduction to opcodes
Load and store opcodes
Arithmetic opcodes
Execution flow control opcodes
Invocation opcodes
Platform operation opcodes
Shortcut opcode forms
4.5 Reflection
Introducing reflection
Combining class loading and reflection
Problems with reflection
5 Java concurrency fundamentals
5.1 Concurrency theory primer
But I already know about Thread
Hardware
Amdahl’s law
Explaining Java’s threading model
Lessons learned
5.2 Design concepts
Safety and concurrent type safety
Liveness
Performance
Reusability
How and why do the forces conflict?
Sources of overhead
5.3 Block-structured concurrency (pre-Java 5)
Synchronization and locks
The state model for a thread
Fully synchronized objects
Deadlocks
Why synchronized?
The volatile keyword
Thread states and methods
Immutability
5.4 The Java Memory Model (JMM)
5.5 Understanding concurrency through bytecode
Lost Update
Synchronization in bytecode
Synchronized methods
Unsynchronized reads
Deadlock revisited
Deadlock resolved, revisited
Volatile access
6 JDK concurrency libraries
6.1 Building blocks for modern concurrent applications
6.2 Atomic classes
6.3 Lock classes
Condition objects
6.4 CountDownLatch
6.5 ConcurrentHashMap
Understanding a simplified HashMap
Limitations of Dictionary
Approaches to a concurrent Dictionary
Using ConcurrentHashMap
6.6 CopyOnWriteArrayList
6.7 Blocking queues
Using BlockingQueue APIs
Using WorkUnit
6.8 Futures
CompletableFuture
6.9 Tasks and execution
Modeling tasks
Executors
Single-threaded executor
Fixed-thread pool
Cached thread pool
ScheduledThreadPoolExecutor
7 Understanding Java performance
7.1 Performance terminology: Some basic definitions
Latency
Throughput
Utilization
Efficiency
Capacity
Scalability
Degradation
7.2 A pragmatic approach to performance analysis
Know what you’re measuring
Know how to take measurements
Know what your performance goals are
Know when to stop
Know the cost of achieving higher performance
Know the dangers of premature optimization
7.3 What went wrong? Why do we have to care?
Moore’s law
Understanding the memory latency hierarchy
7.4 Why is Java performance tuning hard?
The role of time in performance tuning
Understanding cache misses
7.5 Garbage collection
Basics
Mark and sweep
Areas of memory
Young collections
Full collections
Safepoints
G1: Java’s default collector
The Parallel collector
GC configuration parameters
7.6 JIT compilation with HotSpot
Why have dynamic compilation?
Introduction to HotSpot
Inlining methods
Dynamic compilation and monomorphic calls
Reading the compilation logs
Deoptimization
7.7 JDK Flight Recorder
Flight Recorder
Mission Control
Part 3. Non-Java languages on the JVM
8 Alternative JVM languages
8.1 Language zoology
Interpreted vs. compiled languages
Dynamic vs. static typing
Imperative vs. functional languages
Reimplementation vs. original
8.2 Polyglot programming on the JVM
Why use a non-Java language?
Up-and-coming languages
Languages we could have picked but didn’t
8.3 How to choose a non-Java language for your project
Is the project area low-risk?
Does the language interoperate well with Java?
Is there good tooling and test support for the language?
How hard is the language to learn?
Are there lots of developers using this language?
8.4 How the JVM supports alternative languages
Performance
Runtime environments for non-Java languages
Compiler fictions
9 Kotlin
9.1 Why use Kotlin?
Installing
9.2 Convenience and conciseness
Starting with less
Variables
Equality
Functions
Collections
Express yourself
9.3 A different view of classes and objects
Data classes
9.4 Safety
Null safety
Smart casting
9.5 Concurrency
9.6 Java interoperability
10 Clojure: A different view of programming
10.1 Introducing Clojure
Hello World in Clojure
Getting started with the REPL
Making a mistake
Learning to love the brackets
10.2 Looking for Clojure: Syntax and semantics
Special forms bootcamp
Lists, vectors, maps, and sets
Arithmetic, equality, and other operations
Working with functions in Clojure
Loops in Clojure
Reader macros and dispatch
10.3 Functional programming and closures
10.4 Introducing Clojure sequences
Sequences and variable-arity functions
10.5 Interoperating between Clojure and Java
Calling Java from Clojure
The nature of Clojure calls
The Java type of Clojure values
Using Clojure proxies
Exploratory programming with the REPL
Using Clojure from Java
10.6 Macros
Part 4. Build and deployment
11 Building with Gradle and Maven
11.1 Why build tools matter for a well-grounded developer
Automating tedious operations
Managing dependencies
Ensuring consistency between developers
11.2 Maven
The build lifecycle
Commands/POM intro
Building
Controlling the manifest
Adding another language
Testing
Dependency management
Reviewing
Moving beyond Java 8
Multirelease JARs in Maven
Maven and modules
Authoring Maven plugins
11.3 Gradle
Installing Gradle
Tasks
What’s in a script?
Using plugins
Building
Work avoidance
Dependencies in Gradle
Adding Kotlin
Testing
Automating static analysis
Moving beyond Java
Using Gradle with modules
Customizing
12 Running Java in containers
12.1 Why containers matter for a well-grounded developer
Host operating systems vs. virtual machines vs. containers
Benefits of containers
Drawbacks of containers
12.2 Docker fundamentals
Building Docker images
Running Docker containers
12.3 Developing Java applications with Docker
Selecting your base image
Building an image with Gradle
Running the build in Docker
Ports and hosts
Local development with Docker Compose
Debugging in Docker
Logging with Docker
12.4 Kubernetes
12.5 Observability and performance
Observability
Performance in containers
13 Testing fundamentals
13.1 Why we test
13.2 How we test
13.3 Test-driven development
TDD in a nutshell
A TDD example with a single use case
13.4 Test doubles
Dummy object
Stub object
Fake object
Mock object
Problems with mocking
13.5 From JUnit 4 to 5
14 Testing beyond JUnit
14.1 Integration testing with Testcontainers
Installing testcontainers
An example with Redis
Gathering container logs
An example with Postgres
An example for end-to-end testing with Selenium
14.2 Specification-style testing with Spek and Kotlin
14.3 Property-based testing with Clojure
clojure.test
clojure.spec
test.check
clojure.spec and test.check
Part 5. Java frontiers
15 Advanced functional programming
15.1 Introduction to functional programming concepts
Pure functions
Immutability
Higher-order functions
Recursion
Closures
Laziness
Currying and partial application
15.2 Limitations of Java as a FP language
Pure functions
Mutability
Higher-order functions
Recursion
Closures
Laziness
Currying and partial application
Java’s type system and collections
15.3 Kotlin FP
Pure and higher-order functions
Closures
Currying and partial application
Immutability
Tail recursion
Lazy evaluation
Sequences
15.4 Clojure FP
Comprehensions
Lazy sequences
Currying in Clojure
16 Advanced concurrent programming
16.1 The Fork/Join framework
A simple F/J example
Parallelizing problems for F/J
Work-stealing algorithms
16.2 Concurrency and functional programming
CompletableFuture revisited
Parallel streams
16.3 Under the hood with Kotlin coroutines
How coroutines work
Coroutine scoping and dispatching
16.4 Concurrent Clojure
Persistent data structures
Futures and pcalls
Software transactional memory
Agents
17 Modern internals
17.1 Introducing JVM internals: Method invocation
Invoking virtual methods
Invoking interface methods
Invoking special
methods
Final methods
17.2 Reflection internals
17.3 Method handles
MethodHandle
MethodType
Looking up method handles
Reflection vs. proxies vs. method handles
17.4 Invokedynamic
Implementing lambda expressions
17.5 Small internal changes
String concatenation
Compact strings
Nestmates
17.6 Unsafe
17.7 Replacing Unsafe with supported APIs
VarHandles
Hidden classes
18 Future Java
18.1 Project Amber
18.2 Project Panama
Foreign Function and Memory API
18.3 Project Loom
Virtual threads
Thread builders
Programming with virtual threads
When will Project Loom arrive?
18.4 Project Valhalla
Changing the language model
Consequences of value objects
Generics revisited
18.5 Java 18
Appendix A. Selecting your Java
Appendix B. Recap of streams in Java 8
index
front matter
foreword
Well-grounded? You mean, well-rounded
? Two years of pandemic would do that without the need for a book.
Merriam-Webster defines well-grounded as having a firm foundation.
I like that. We want to have a firm foundation in Java—a practical knowledge of what we need to know to call ourselves Java experts. This book picks up where Effective Java stops.
This is the second edition of a great book. The first taught us all that we needed to know for Java 7. That seems like eons ago. Java 7 belonged to another age, when features were added to the language at best every three years. Back then, it was easy to keep versions apart. Java 5? Generics and enums. Java 7? try-with-resource. Java 8? Streams and lambdas. Those comfortable easy days ended when Oracle introduced the six-month cycle. Records—were those Java 14, 15, or 17? Enhanced Switch? Was that already in Java 11?
The fast release cycle is great for programmers who work for adventurous companies. Every six months, they get new toys to play with. They might even get to try out previews of what will come next. The myriad of new features is wonderful for programmers, but not so nice for authors. Before the ink has dried, a new feature release makes a bunch of things obsolete.
Ben, Jason, and Martijn have done a fantastic job with this new Java book. The basic premise remains the same. In my words: If you wanted to hire a professional Java programmer, what would you expect them to already know? What skills would they need to prove they are well grounded?
This new version of the book is as current as is possible with the six-month release cadence. At the same time, the authors don’t overwhelm us with new stuff. The stark reality is that most enterprises are still stuck on an older version of Java. Even with Java 18 released, a lot of banks, insurance companies, and government departments are still on Java 8.
This book is about 200 pages longer than the previous edition. The fonts are a bit larger—well, we have all aged by nine years, haven’t we? But the margins are smaller. Quite a few of the sections have completely new content. This is one case where the new edition does not make the old one obsolete. Both belong on a serious Java programmer’s bookshelf.
Benjamin J. Evans, Jason R. Clark, and Martijn Verburg are Java experts. They hold senior Java positions at Red Hat, New Relic, and Microsoft. Let’s take advantage of their collective wisdom. This book will help us discover areas of weakness that we can then improve on. In the end, with enough work, we can call ourselves Well-Grounded Java programmers.
Heinz Kabutz
The Java Specialists’ Newsletter
preface
The first edition of this book started life as a set of training notes written for new graduate developers in the foreign exchange department of a bank. One of us (Ben), looking at the existing books on the market, found a lack of up-to-date material aimed at Java developers who wanted to level up. Partway through writing that material, he found he was writing that missing book and enlisted Martijn to help.
That was more than 10 years ago—we were writing as Java 7 was being developed—and the world is very different now. In response, the book has changed substantially since the first edition. So, although our original primary goals were to introduce topics like
Polyglot programming
Dependency injection
Multithreaded programming
Sound build and CI practices
What’s new in Java 7
when we came to write the second edition, we found that we needed to make some changes, including
Trimming down polyglot a bit
Adding a new emphasis on functional programming
Enhancing the discussion of multithreading
Putting a different spin on build and deploy (including containers)
Talking about what’s new in Java 11 and 17
One very important change was that the first edition included Scala as one of three non-Java languages discussed (the others being Groovy and Clojure—Kotlin didn’t really exist at the time we wrote it). At that time, many of the developers exploring Scala were looking for Java, but a better mousetrap,
which is essentially the view of Scala that we presented in the first edition.
However, since then, the world has moved on. Java 8 and 11 became hugely dominant, and the better mousetrap
crew are mostly writing Kotlin (or just sticking with Java). Scala, in the meantime, has become a very powerful statically typed, FP-first JVM language. This is great for the folks who want that, but it has come with costs, such as an ever-more-complex runtime and a language that has less and less in common with Java as time goes by.
This development is sometimes abbreviated to the phrase that Scala wants to be Haskell on the JVM,
although this is not entirely accurate and is more a convenient conversational shorthand than anything else. So, after having made the decision to drop Groovy from the second edition, we thought long and hard about whether to keep Scala or replace it with Kotlin.
Our eventual conclusion was basically that Scala is heading in its own, FP-heavy direction, and that we wanted to present a language that was more approachable to Java developers who were coming fresh to non-Java languages, such as Kotlin. This left us with a dilemma. The parts of Scala that are easily accessible to Java folks are very similar to Kotlin (with near-identical syntax in some cases), but the philosophy and direction of travel of the two languages are totally different. We felt that explaining what Scala is in sufficient depth—so that the coverage was distinct from Kotlin—would take up far too much space in the book.
Therefore, our eventual decision was to drop from three to two additional languages to make space and give extra depth to the coverage of the remaining languages (Kotlin and Clojure). For this reason, although we make the occasional comment about Scala, we don’t devote entire sections (let alone chapters) to it.
Clojure is a very different story—and, indeed, a very different language—than either Kotlin or Java. For example, in chapter 15 we struggle a bit because many of the concepts that we’re introducing in the other languages (e.g., higher-order functions and recursion) have already been introduced and are just part of the landscape
in Clojure. Rather than follow the template used by Java and Kotlin, the discussion goes in a different direction. Clojure is, fundamentally, a much more functionally oriented language, and we would just end up repeating ourselves a lot if we were to follow the exact same structure as for the other languages.
In this book, we hope that the theme of software development as a social activity rings out clearly. We believe that the technical aspects of the craft are important, but the more subtle concerns of communication and interaction between people are at least as important. It can be hard to explain these facets easily in a book, but that theme is present throughout.
Developers are sustained throughout their careers by their engagement with technology and the passion to keep learning. In this book, we hope that we’ve been able to highlight some of the topics that will ignite that passion. It’s a sightseeing tour, rather than an encyclopedic study, but that’s the intention—to get you started and then leave you to follow up on those topics that capture your imagination.
We take you from the new features of the recent versions of Java through to best practices of modern software development and the future of the platform. Along the way, we show you some of the highlights that have had relevance to us on our own journey as Java technologists.
Concurrency, performance, bytecode, and class loading are some of the core techniques that fascinated us the most. We also talk about new, non-Java languages on the JVM for two reasons:
Non-Java languages continue to gain importance in the overall Java ecosystem.
Understanding the different perspectives that different languages bring makes you a better programmer in any language you write in.
Above all, this is a journey that’s forward looking and puts you and your interests front and center. We feel that becoming a well-grounded Java developer will help to keep you engaged and in control of your own development and will help you learn more about the changing world of Java and the ecosystem that surrounds it. We hope that the distilled experience that you’re holding in your hands is useful and interesting to you and that reading it is thought provoking and fun. Writing it certainly was!
acknowledgements
We would like to thank the following people for their contributions to the book:
Elesha Hyde, for being a most excellent development editor; Jonathon Thoms, for his great work in the technical reviews; Alex Buckley, for a very detailed discussion of the class loading process; Heinz Kabutz, for excellent suggestions and discussions (and even PRs!) about the details of the concurrency chapters, as well as another wonderful foreword; Holly Cummins, not only for helping inspire the original edition but also for her consistently grounded and practical advice; Bruce Durling, for discussion of the Clojure material; Dan Heidinga, for detailed feedback on the current state of Project Valhalla; Piotr Jagielski, Louis Jacomet, József Bartók, and Tom Tresansky for corrections regarding some of the details about how Gradle really works; and Andrew Binstock, for a very meticulous close reading of several chapters and sound advice, as always.
We would also like to thank the staff at Manning: Mihaela Batinić, our reviewing editor; Michael Haller, our technical reviewer; Deirdre Hiam, our project editor; Pamela Hunt, our copyeditor; and Jason Everett, our proofreader. To all the reviewers: Adam Koch, Alain Lompo, Alex Gout, Andres Sacco, Andy Keffalas, Anshuman Purohit, Ashley Eatly, Christian Thoudahl, Christopher Kardell, Claudia Maderthaner, Conor Redmond, Dr. Irfan Ullah, Eddú Meléndez Gonzales, Ezra Simeloff, George Thomas, Gilberto Taccari, Hugo da Silva Possani, Igor Karp, Jared Duncan, Javid Asgarov, Jean-François Morin, Jerome Meyer, Kent R. Spillner, Kimberly L Winston-Jackson, Konstantin Eremin, Matt Deimel, Michael Haller, Michael Wall, Mikhail Kovalev, Patricia Gee, Ramanan Natarajan, Raphael Villela, Satej Kumar Sahu, Sergio Edgar Martínez Pacheco, Simona Ruso, Steven K. Makunzva, Theofanis Despoudis, Troi Eisler, Yogesh Shetty, and William E. Wheeler, your suggestions helped make this a better book.
From Jason
Thanks are due to many different people across many years.
To Mrs. Nimmo, thank you for all the extra credit in middle school English for my silly little stories. It’s no exaggeration that your encouragement set me on a lifelong path of writing.
To Mom, thank you for your infectious love of reading, which I’m so glad to have inherited.
To Dad, thank you for sharing your love of computers. It has provided me not only with a career but the chance to write and share that joy with others.
To Ben, thank you first and foremost for your friendship. It has been a blast to be drawn deeper into the JVM by your awesome curiosity and enthusiasm. And, of course, thanks for asking me along on this second edition. It was more work than any of us expected, but a better book in the end as well!
And last but not least, thanks to my wife, Amber, and my kids, Coraline and Asher, for their continued love and support throughout the strange and wonderful process that is making a book.
From Martijn
Firstly I’d like to thank Ben and Jason for inviting me to be part of this second edition. My contribution was very minimal in comparison to theirs, and they were most gracious in insisting my name still be on the cover!
To Kerry, you’ve been a mountain of support during the whirlwind career moments of the past decade, not to mention reacting with a smile when I said, It’s just a few edits this time around, I promise!
To Hunter, your enthusiasm for life reminds me why I got into the creative joy of programming in the first place. I hope you’ll find that same joy in life no matter what path you take.
To the fine folks of the Java Engineering Group at Microsoft, the Eclipse Adoptium Community, the London Java Community, the Java Champions Community, and too many others to mention. You inspire me every day, and I always walk away each day having learned something new and added another five things on my list to read for the next day!
From Ben
To my parents, Sue and Martin, for their unwavering faith that we would find, and make, our own way on the path less traveled.
To my wife, Anna, for her illustrations, her artistic vision, and her tireless support and understanding through yet another book.
In memory of Marianito, who, partway through the development of this book, discovered that laptops that have been left open make a marvelously warm spot upon which to sleep.
To Joselito, who overcame some of his fear by being curious about why I would sit and be so fascinated by the screen that was so much less interesting than the one in the other room that has spaceships and explosions on it.
about this book
Who should read this book?
Welcome to The Well-Grounded Java Developer. This book is aimed at turning you into a Java developer for the next decade, reigniting your passion for both the language and platform. Along the way, you’ll discover new Java features, ensure that you’re familiar with essential modern software techniques (such as test-driven development, and container-based deployments), and start to explore the world of non-Java languages on the JVM.
To begin, let’s consider the description of the Java language provided by James Iry in a wonderful blog post, A Brief, Incomplete, and Mostly Wrong History of Programming Languages
available at http://mng.bz/2rz9:
1996—James Gosling invents Java. Java is a relatively verbose, garbage collected, class-based, statically typed, single dispatch, object-oriented language with single implementation inheritance and multiple interface inheritance. Sun loudly heralds Java’s novelty.
Although the point of Java’s entry is mostly to set up a gag where C# is given the same writeup, this is not bad as descriptions of languages go. The full blog post contains a bunch of other gems, and it’s well worth a read in an idle moment.
This does present a very real question: why are we still talking about a language that is now over 26 years old? Surely it’s stable, and not much new or interesting can be said about it?
If that were the case, this would be a short book. We are still talking about it because one of Java’s greatest strengths has been its ability to build on a few core design decisions, which have proven to be very successful in the marketplace:
Automatic management of the runtime environment (e.g., garbage collection, just-in-time compilation)
A simple syntax and relatively few concepts in the core language
A conservative approach to evolving the language
Additional functionality and complexity in libraries
Broad, open ecosystem
These design decisions have kept innovation moving in the Java world—the simple core has kept the barrier to joining the developer community low, and the broad ecosystem has made it easy for newcomers to find pre-existing components that fit their needs. These traits have kept the Java platform and language strong and vibrant—even if the language has had a historical tendency to change slowly. It turns out that the mix of strong consistency and evolutionary change has won quite a few fans among software developers.
How to use this book
The material in this book is broadly designed to be read end-to-end, but we understand that some readers may want to dive straight into particular topics, so we have done our best to also accommodate that style of reading.
We strongly believe in hands-on learning, so we recommend that readers try out the sample code that comes with the book as they read through the text. The rest of this section deals with how you can approach the book if you are more of a standalone chapter style of reader.
The Well-Grounded Java Developer is split into the following five parts:
From 8 to 11, and beyond
Under the hood
Non-Java languages on the JVM
Build and deployment
Java frontiers
Part 1 (chapters 1–3) contains three chapters on the most recent versions of Java. The book as a whole uses Java 11 syntax and semantics throughout and calls out specific uses of post-11 syntax.
Part 2 (chapters 4–7) contains a first peek behind the curtain. It is a truism of art that one needs to know the rules before one can credibly break them. These chapters outline how one first bends, and then breaks, the rules of the Java programming language.
Part 3 (chapters 8–10) covers polyglot programming on the JVM. Chapter 8 should be considered required reading because it sets the stage by discussing the categorization and use of alternative languages on the JVM.
The following two language chapters cover a Java-like OO-functional language (Kotlin) and a truly functional one (Clojure). Those languages can be read standalone, although developers new to functional programming will probably want to read them in order.
Part 4 (chapters 11–14) introduces build, deployment, and testing as they are done in modern projects, and they assume that the reader has at least a basic understanding of unit testing as showcased in, for example, JUnit.
Part 5 (chapters 15–18) builds on topics that have been introduced earlier to delve deeper into functional programming, concurrency, and the internals of the platform. Although the chapters can be read standalone, in some sections, we assume that you’ve read the earlier chapters and/or already have familiarity with certain topics.
This book is firmly aimed at Java developers who wants to modernize their knowledge base in both the language and the platform. If you want to get up to speed with what modern Java has to offer, this is the book for you.
If you are looking to brush up on your techniques and understanding of topics such as functional programming, concurrency, and advanced testing, this book will give you a good grounding in those topics. This is also a book for those developers who are curious about what non-Java languages can teach them and how broadening their horizons will make them a better programmer.
About the code
The initial download and installation you’ll need is Java 17 (or 11). Simply follow the download and installation instructions for the binary you need for the OS you use. You can find binaries and instructions online at your usual Java vendor, or at the vendor-neutral Adoptium project, run by the Eclipse Foundation, at https://adoptium.net/.
Java 11 (and 17) runs on Mac, Windows, Linux, and pretty much any other modern OS and hardware platform.
Note If you’re concerned about details of Java licensing and so on, you can head to appendix A where a full discussion can be found.
This book contains many examples of source code both in numbered listings and in line with normal text. In both cases, source code is formatted in a fixed-width font like this to separate it from ordinary text.
In many cases, the original source code has been reformatted; we’ve added line breaks and reworked indentation to accommodate the available page space in the book. Additionally, comments in the source code have often been removed from the listings when the code is described in the text. Code annotations accompany many of the listings, highlighting important concepts.
You can get executable snippets of code from the liveBook (online) version of this book at https://livebook.manning.com/book/the-well-grounded-java-developer-second-edition. The complete code for the examples in the book is available for download from the Manning website at https://www.manning.com/books/the-well-grounded-java-developer-second-edition, and from GitHub at https://github.com/well-grounded-java/resources.
However, most readers will probably want to try out the code samples in an IDE. Java 11/17 and the latest versions of Kotlin and Clojure are well supported by recent versions of the main IDEs:
Eclipse IDE
IntelliJ IDEA Community Edition (or Ultimate Edition)
Apache NetBeans
liveBook Discussion Forum
Purchase of The Well-Grounded Java Developer, 2nd Edition includes free access to liveBook, Manning’s online reading platform. Using liveBook’s exclusive discussion features, you can attach comments to the book globally or to specific sections or paragraphs. It’s a snap to make notes for yourself, ask and answer technical questions, and receive help from the author and other users. To access the forum, go to https://livebook.manning.com/book/the-well-grounded-java-developer-second-edition/discussion. You can also learn more about Manning’s forums and the rules of conduct at https://livebook.manning.com/discussion.
Manning’s commitment to our readers is to provide a venue where a meaningful dialogue between individual readers and between readers and the authors can take place. It is not a commitment to any specific amount of participation on the part of the authors, whose contribution to the forum remains voluntary (and unpaid). We suggest you try asking the authors some challenging questions lest their interest stray! The forum and the archives of previous discussions will be accessible from the publisher’s website as long as the book is in print.
Other online resources
https://github.com/well-grounded-java/resources
about the authors
Ben Evans
is a Java Champion and Senior Principal Software Engineer at Red Hat. Previously he was Lead Architect for Instrumentation at New Relic, and co-founded jClarity, a performance tools startup acquired by Microsoft. He has also worked as Chief Architect for Listed Derivatives at Deutsche Bank and as Senior Technical Instructor for Morgan Stanley. He served for six years on the Java Community Process Executive Committee, helping define new Java standards.
Ben is the author of six books, including Optimizing Java and the new editions of Java in a Nutshell and his technical articles are read by thousands of developers every month. Ben is a regular speaker and educator on topics such as the Java platform, systems architecture, performance and concurrency for companies and conferences all over the world.
Jason Clark
is a principal engineer and architect at New Relic where he has worked on everything from Ruby instrumentation libraries to container orchestration platforms. He was previously an architect at WebMD building .Net-based web services.
A regular conference speaker, Jason contributes to the open-source project Shoes, aiming to make GUI programming easy and fun for beginners and students.
Martijn Verburg
is the Principal SWE Group Manager for the Java Engineering Group at Microsoft. He is the co-leader of the London Java User Group (aka the LJC) where he co-founded AdoptOpenJDK (now Eclipse Adoptium), the world's leading (non- Oracle) OpenJDK distribution. Martijn is the co-author of The Well-Grounded Java Developer, 1st edition, and sits on numerous Java standards bodies (JCP, Jakarta EE, et al).
about the cover illustration
The figure on the cover of The Well-Grounded Java Developer, titled A Posy Seller,
is taken from Sylvain Maréchal’s nineteenth-century compendium of regional dress customs, published in France.
In those days, it was easy to identify where people lived and what their trade or station in life was just by their dress. Manning celebrates the inventiveness and initiative of the computer business with book covers based on the rich diversity of regional culture centuries ago, brought back to life by pictures from collections such as this one.
Part 1. From 8 to 11 and beyond!
These first three chapters are about ramping up to Java 17. You’ll ease in with an introductory chapter that covers some quality-of-life changes that came in with Java 11. You’ll see how the Java ecosystem and release cycle has changed since Java 8, including the following, and what that means to developers:
var keyword
Collections factories
New HTTP client with HTTP/2 support
Single-file source code programs
From there, you’ll get a deep dive on one of the biggest changes in the Java landscape in many years—the addition of a full module system. You’ll see why this dramatic change was necessary. It’s been carefully designed for incremental adoption, and along with understanding the concepts, you’ll come away knowing how to start taking advantage of it in your applications and libraries.
Under the new release cycle, Java 17 brings together a significant batch of new language features, including
Text blocks
Switch expressions
Records
Sealed types
By the end of part 1, you’ll be thinking and writing naturally in Java 17, ready to use this new knowledge throughout the remainder of the book.
1 Introducing modern Java
This chapter covers
Java as a platform and a language
The new Java release model
Enhanced Type inference (var)
Incubating and preview features
Changing the language
Small language changes in Java 11
Welcome to Java in 2022. It is an exciting time. Java 17, the latest Long-Term-Support (LTS) release shipped in September 2021, and the first and most adventurous teams are starting to move to it.
At the time of writing, apart from a few trailblazers, Java applications are more or less evenly split between running on Java 11 (released September 2018) and the much older Java 8 (2014). Java 11 offers a lot to recommend, especially for teams that are deploying in the cloud, but some have been a little slow to adopt it.
So, in the first part of this book, we are going to spend some time introducing some of the new features that have arrived in Java 11 and 17. Hopefully, this discussion will help convince some teams and managers who may be reluctant to upgrade from Java 8 that things are better than ever in the newer versions.
Our focus for this chapter is going to be Java 11 because a) it’s the LTS version with the largest market share and b) no noticeable adoption of Java 17 has occurred yet. However, in chapter 3, we will introduce the new features in Java 17 to bring you all the way up to date.
Let’s get underway by discussing the language-versus-platform duality that lies at the heart of modern Java. This is a critically important point that we’ll come back to several times throughout the book, so it’s essential to grasp it right at the start.
1.1 The language and the platform
Java as a term can refer to one of several related concepts. In particular, it could mean either the human-readable programming language or the much broader Java platform.
Surprisingly, different authors sometimes give slightly different definitions of what constitutes a language and a platform. This can lead to a lack of clarity and some confusion about the differences between the two and about which provides the various programming features that application code uses.
Let’s make that distinction clear right now, because it cuts to the heart of a lot of the topics in this book. Here are our definitions:
The Java language—The Java language is the statically typed, object-oriented language that we lightly lampooned in the About this book
section. Hopefully, it’s already very familiar to you. One obvious point about source code written in the Java language is that it’s human-readable (or it should be!).
The Java platform—The platform is the software that provides a runtime environment. It’s the JVM that links and executes your code as provided to it in the form of (not human-readable) class files. It doesn’t directly interpret Java language source files but instead requires them to be converted to class files first.
One of the big reasons for the success of Java as a software system is that it’s a standard. This means that it has specifications that describe how it’s supposed to work. Standardization allows different vendors and project groups to produce implementations that should all, in theory, work the same way. The specs don’t make guarantees about how well different implementations will perform when handling the same task, but they can provide assurances about the correctness of the results.
Several separate specs govern the Java system—the most important are the Java Language Specification (JLS) and the JVM Specification (VMSpec). This separation is taken very seriously in modern Java; in fact, the VMSpec no longer makes any reference whatsoever to the JLS directly. We’ll have a bit more to say about the differences between these two specs later in the book.
Note These days the JVM is actually quite a general-purpose and language-agnostic environment for running programs. This is one reason for the separation of the specs.
One obvious question, when you’re faced with the described duality, is, What’s the link between them?
If they’re now separate, how do they come together to make the Java system?
The link between the language and platform is the shared definition of the class file format (the .class files). A serious study of the class file definition will reward you (and we provide one in chapter 4)—in fact, it’s one of the ways a good Java programmer can start to become a great one. In figure 1.1, you can see the full process by which Java code is produced and used.
Figure 1.1 Java source code is transformed into .class files, then manipulated at load time before being JIT-compiled.
As you can see in the figure, Java code starts life as human-readable Java source, and it’s then compiled by javac into a .class file and loaded into a JVM. It’s common for classes to be manipulated and altered during the loading process. Many of the most popular Java frameworks transform classes as they’re loaded to inject dynamic behavior such as instrumentation or alternative lookups for classes to load.
Note Class loading is an essential feature of the Java platform, and we will learn a lot more about it in chapter 4.
Is Java a compiled or interpreted language? The standard picture of Java is of a language that’s compiled into .class files before being run on a JVM. If pressed, many developers can also explain that bytecode starts off by being interpreted by the JVM but will undergo just-in-time (JIT) compilation at some later point. Here, however, many people’s understanding breaks down into a somewhat hazy conception of bytecode as basically being machine code for an imaginary or simplified CPU.
In fact, JVM bytecode is more like a halfway house between human-readable source and machine code. In the technical terms of compiler theory, bytecode is really a form of intermediate language (IL) rather than actual machine code. This means that the process of turning Java source into bytecode isn’t really compilation in the sense that a C++ or a Go programmer would understand it, and javac isn’t a compiler in the same sense as gcc is—it’s really a class file generator for Java source code. The real compiler in the Java ecosystem is the JIT compiler, as you can see in figure 1.1.
Some people describe the Java system as dynamically compiled.
This emphasizes that the compilation that matters is the JIT compilation at runtime, not the creation of the class file during the build process.
Note The existence of the source code compiler, javac, leads many developers to think of Java as a static, compiled language. One of the big secrets is that at runtime, the Java environment is actually very dynamic—it’s just hidden a bit below the surface.
So, the real answer to Is Java compiled or interpreted?
is both.
With the distinction between language and platform now clearer, let’s move on to talk about the new Java release model.
1.2 The new Java release model
Java was not always an open source language, but following an announcement at the JavaOne conference in 2006, the source code for Java itself (minus a few bits that Sun didn’t own the source for) was released under the GPLv2+CE license (https://openjdk.java.net/legal/gplv2+ce.html).
This was around the time of the release of Java 6, so Java 7 was the first version of Java to be developed under an open source software (OSS) license. The primary focus for open source development of the Java platform since then has been the OpenJDK project (https://openjdk.java.net), and that continues to this day.
A lot of the project discussion takes place on mailing lists that cover aspects of the overall codebase. There are permanent
lists such as core-libs (core libraries), as well as more transient lists that are formed as part of specific OpenJDK projects such as lambda-dev (lambdas), which then become inactive when a particular project has been completed. In general, these lists have been the relevant forums for discussing possible future features, allowing developers from the wider community to participate in the process of producing new versions of Java.
Note Sun Microsystems was acquired by Oracle shortly before Java 7 was released. Therefore, all of Oracle’s releases of Java have been based on the open source codebase.
The open source releases of Java had settled into a feature-driven release cycle, where a single marquee feature effectively defines the release (e.g., lambdas in Java 8 or modules in Java 9).
With the release of Java 9, however, the release model changed. From Java 10 onward, Oracle decided that Java would be released on a strict, time-based model. This means that OpenJDK now uses a mainline development model, which includes the following:
New features are developed on a branch and merged only when they are code complete.
Releases can occur on a strict time cadence.
Late features do not delay releases but are held over for the next release.
The current head of the trunk should always be releasable (in theory).
If necessary, an emergency fix can be prepared and pushed out at any point.
Separate OpenJDK projects are used to explore and research longer-term, future directions.
A new version of Java is released every six months (feature releases
). The various providers (Oracle, Eclipse Adoptium, Amazon, Azul, et al.) can choose to make any of those releases a Long-Term Support (LTS) release. However, in practice, all of the vendors follow having one release every three years being named as the LTS release.
Note As of late 2021, discussions are underway to reduce the LTS gap from three years to two years. We may well see the next LTS version as Java 21 in 2023 as opposed to Java 23 in 2024.
The first LTS release was Java 11, with Java 8 retrospectively included in the set of LTS releases. Oracle’s intention was for the Java community to upgrade regularly and to take up the feature releases as they emerge. However, in practice, the community (and enterprise customers in particular) have proved to be resistant to this model, preferring instead to upgrade from one LTS release to the next.
This approach, of course, limits the uptake of new Java features and stifles innovation. However, the realities of enterprise software are what they are, and many people still view an upgrade of the Java version as a significant undertaking.
Figure 1.2 The timescale of recent and future releases
This means that whereas the release road map shown in figure 1.2 contains a major release every six months, the only releases that have significant usage are the LTS versions—Java 17 (which was just released in September 2021), Java 11 (which was released in September 2018), and the pre-modules release, Java 8, which is more than seven years old. Java 8 and Java 11 have roughly equal market share, with Java 11 recently having taken over 50% and rapidly accelerating. Java 17 adoption is expected to be much quicker than the move from Java 8 to Java 11 because the most difficult hurdles introduced by the module system and security restrictions will have already been overcome with the earlier migration.
The other significant change in the new release model is that Oracle has changed the license for their distribution. Although Oracle’s JDK is built from the OpenJDK sources, the binary is not licensed under an OSS license. Instead, Oracle’s JDK is proprietary software, and as of JDK 11, Oracle provides support and updates for only six months for each version. This means that many people who relied on Oracle’s free updates are now faced with a choice:
Pay Oracle for support and updates, or
Use a different distribution that produces open source binaries.
Alternative JDK vendors include Eclipse Adoptium (previously AdoptOpenJDK), Alibaba (Dragonwell), Amazon (Corretto), Azul Systems (Zulu), IBM, Microsoft, Red Hat, and SAP.
Note Two of the authors (Martijn and Ben) helped found the AdoptOpenJDK project, which has evolved into the vendor-neutral Eclipse Adoptium community project to build and release a high-quality, free, and open source Java binary distribution. See adoptium.net for more details.
With the licensing changes and with so many providers, picking the correct Java for you and your team is a choice that you should make with care. Thankfully, leaders in the Java ecosystem have written some very detailed guides, and appendix A distills them down for you.
Although the Java release model has changed to use timed releases, the vast majority of teams are still running on either JDK 8 or 11. These LTS releases are being maintained by the community (including major vendors) and still receive regular security updates and bug fixes. The changes made to the LTS versions are deliberately small in scope and are housekeeping updates.
Apart from security and small bug fixes, only a minimal set of changes are permitted. These include fixes needed to ensure that the LTS releases will continue to work correctly for their expected lifetime. This includes things like the following:
The addition of the new Japanese Era
Time zone database updates
TLS 1.3
Adding Shenandoah, a low-pause GC for large modern workloads
One other necessary change is that the build scripts for macOS needed to be updated to work with a recent version of Apple’s Xcode tool so that they will continue to work on new releases of Apple’s operating system.
Within the projects to maintain JDK 8 and 11 (sometimes called the updates
projects), some potential scope still exists for new features to be backported, but it is minimal. As an example, one of the guiding rules is that newly ported features may not change program semantics. Examples of permissible changes could include the support for TLS 1.3 or the backport of Java Flight Recorder to Java 8u272.
Now that we’ve set the scene by clarifying the difference between the language and platform and explaining the new release model, let’s meet our first technical feature of modern Java. The new feature we’re going to meet is something that developers have been asking for since almost the first release of Java—a way to reduce the amount of typing that writing Java programs seems to involve.
1.3 Enhanced type inference (var keyword)
Java has historically had a reputation as a verbose language. However, in recent versions, the language has evolved to make more and more use of type inference. This feature of the source code compiler enables the compiler to work out some of the type information in programs automatically. As a result, it doesn’t need to be told everything explicitly.
Note The aim of type inference is to reduce boilerplate content, remove duplication, and allow for more concise and readable code.
This trend started with Java 5, when generic methods were introduced. Generic methods permit a very limited form of type inference of generic type arguments, so that instead of having to explicitly provide the exact type that is needed, like this:
List
the generic type parameter can be omitted on the right-hand side, like so:
List
This way of writing a call to a generic method is so familiar that many developers will struggle to remember the form with explicit type arguments. This is a good thing—it means the type inference is doing its job and removing the superfluous boilerplate content so that the meaning of the code is clear.
The next significant enhancement to type inference in Java came with version 7, which introduced a change when dealing with generics. Before Java 7, it was common to see code like this:
Map
That is a really verbose way to declare that you have some users, whom you identify by userid (which is an integer), and each user has a set of properties (modeled as a map of string to strings) specific to that user.
In fact, almost half of the source is duplicated characters, and they don’t tell us anything. So, from Java 7 onward, we can write
Map
and have the compiler work out the type information on the right side. The compiler is working out the correct type for the expression on the right side— it isn’t just substituting the text that defines the full type.
Note Because the shortened type declaration looks like a diamond, this form is called diamond syntax.
In Java 8, more type inference was added to support the introduction of lambda expressions, like this example where the type inference algorithm can conclude that the type of s is a String:
Function
In modern Java, type inference has been taken one step further, with the arrival of Local Variable Type Inference (LVTI), otherwise known as var. This feature was added in Java 10 and allows the developer to infer the types of variables, instead of the types of values, like this:
var names = new ArrayList
This is implemented by making var a reserved, magic
type name rather than a language keyword. Developers can still in theory use var as the name of a variable, method, or package.
Note An important side effect of using var appropriately is that the domain of your code is once more front and center (as opposed to the type information). But with great power comes great responsibility! Make sure that you name your variables carefully to help future readers of your code.
On the other hand, code that previously used var as the name of a type will have to be recompiled. However, virtually all Java developers follow the convention that type names should start with a capital letter, so the number of instances of preexisting types called var should be vanishing small. This means that it is entirely legal to write code like that shown in the next listing.
Listing 1.1 Bad code
package var; public class Var { private static Var var = null; public static Var var() { return var; } public static void var(Var var) { Var.var = var; } }
And then call it like this:
var var = var(); if (var == null) { var(new Var()); }
However, just because something is legal, does not mean it is sensible. Writing code like the previous listing is not going to make you any friends and should not pass code reviews!
The intention of var is to reduce verbosity in Java code and to be familiar to programmers coming to Java from other languages. It does not introduce dynamic typing, and all Java variables continue to have static types at all times—you just don’t need to write them down explicitly in all cases.
Type inference in Java is local, and in the case of var, the algorithm examines only the declaration of the local variable. This means it cannot be used for fields, method arguments, or return types. The compiler applies a form of constraint solving to determine whether any type exists that could satisfy all the requirements of the code as written.
Note var is implemented solely in the source code compiler (javac) and has no runtime or performance effect whatsoever.
For example, in the declaration of lengthFn in the previous code sample, the constraint solver can deduce that the type of the method parameter s must be compatible with String which is explicitly provided as the type of the parameter to Function. In Java, of course, the string type is final, so the compiler can conclude that the type of s is exactly String.
For the compiler to be able to infer types, enough information must be provided by the programmer to allow the constraint equations to be solved. For example, code like this
var fn = s -> s.length();
does not have enough type information for the compiler to deduce the type of fn, and so it will not compile. One important case of this is
var n = null;
which cannot be resolved by the compiler because the null value can be assigned to a variable of any reference type, so there is no information about what types n could conceivably be. We say that the type constraint equations that the inferencer needs to solve are underdetermined
in this case—a mathematical term that connects the number of equations to be solved with the number of variables.
You could imagine a scheme of type inference that goes beyond just the initial declaration of the local variable and examines more code to make inference decisions, like this:
var n = null; String.format(n);
A more complex inference algorithm (or a human) might be able to conclude that the type of n is actually String, because the format() method takes a string as the first argument.
This might seem appealing, but, as with everything else in software, it represents a trade-off. More complexity means longer compilation times and a wider variety of ways in which the inference can fail. This, in turn, means that the programmer must develop a more complicated intuition to use nonlocal type inference correctly.
Other languages may choose to make different trade-offs, but Java is clear: only the declaration is used to infer types. Local variable type inference is intended to be a beneficial technique to reduce boilerplate text and verbosity. However, it should be used only where necessary to make the code clearer, not as a blunt instrument to be used whenever possible (the Golden Hammer
antipattern).
Some quick guidelines for when to use LVTI follow:
In simple initializers, if the right-hand side is a call to a constructor or static factory method
If removing the explicit type deletes repeated or redundant information
If variables have names that already indicate their types
If the scope and usage of the local variable is short and simple
A complete set of applicable rules of thumb is provided by Stuart Marks, one of the core developers of the Java language, in his style guides for LVTI usage at http://mng.bz/RvPK.
To conclude this section, let’s look at another, more advanced, usage of var—the so-called nondenotable types. These are types that are legal in Java, but they cannot appear as the type of a variable. Instead, they must be inferred as the type of the expression that is being assigned. Let’s look at a simple example using the jshell interactive environment, which arrived in Java 9:
jshell> var duck = new Object() { ...> void quack() { ...> System.out.println(Quack!
); ...> } ...> } duck ==> $0@5910e440 jshell> duck.quack(); Quack!
The variable duck has an unusual type—it is effectively Object but extended with a method called quack(). Although the object may quack like a duck, its type lacks a name, so we can’t use the type as either a method parameter or return type.
With LVTI, we can use it as the inferred type of a local variable. This allows us to use the type within a method. Of course, the type can’t be used outside of this tight local scope, so the overall utility of this language feature is limited. It’s more of a curiosity than anything else.
Despite these limitations, this does represent a glimpse at Java’s take on a feature that is present in some other languages—sometimes referred to as structural typing in statically typed languages and duck typing in dynamically typed languages (particularly Python).
1.4 Changing the language and the platform
We think it’s essential to explain the why
of language change as well as the what.
During the development of new versions of Java, much interest around new language features often exists, but the community doesn’t always understand how much work is required to get changes fully engineered and ready for prime time.
You may also have noticed that in a mature runtime such as Java, language features tend to evolve