String json = "{\n"
+ "\t\"name\": \"" + name + "\",\n"
+ "\t\"year\": \"" + bday.getYear() + "\"\n"
+ "}";
slow
old
verbose
cumbersome
no fun!
None of that is true.
But none of it is (entirely) false, either.
A more nuanced view
(on some issues):
strings lack expressiveness
the type system and encapsulation
can get in the way
dealing with data is clunky
getting started isn’t easy
Project Amber:
Right-sizing ceremony.
(Things that are important but cumbersome
should be made less cumbersome.)
Profile:
project / wiki / mailing list
launched March 2017
led by Brian Goetz and Gavin Bierman
Goal is not:
code golf
feature overload
Instead, identify situations where:
Java lacks expressiveness
strengths turn into weaknesses
tensions and tradeoffs occur
And resolve those for greater good!
Amber is not the only project doing that!
Valhalla — design vs performance
Loom — simplicity vs scalability
Panama — flexibility vs safety
Leyden — dynamism vs performance
Getting back to Project Amber:
strings
type safety & encapsulation
dealing with data
starting (with) Java
Slides at slides.nipafx.dev.
How Project Amber
smartly enhances
one of Java’s most basic types.
String json = "{\n"
+ "\t\"name\": \"" + name + "\",\n"
+ "\t\"year\": \"" + bday.getYear() + "\"\n"
+ "}";
What we’re expressing:
we need a String
β
π
across multiple lines π«
with indentation π«£
and quotation marks π’
and embedded variables π
String literals lack expressiveness:
no concept of multi-line-ness
no concept of interpolation/processing
no concept of rawness
Project Amber is working on that.
(But without throwing it all into one pot.)
Java 15 finalized text blocks (JEP 378):
multiline string literals
understand incidental vs essential indentation
require less escaping
String duke = """
{
"name": "Duke",
"year": "1992"
}
""";
We can now "enable" multi-line-ness.
But embedding variables is still cumbersome.
With String::formatted
:
String json = """
{
"name": "%s",
"year": "%d"
}
""".formatted(name, bday.getYear());
But:
So, to be clear: there will be no string
template feature, even with
--enable-preview
, in JDK 23.
Gavin Bierman, April 5th
πππ
proposed for Java 12 (JEP 326)
withdrawn due to complexity
would be convenient in some form
String regex = "\\[\\d{2,4}\\]"; // π€π€π€
// made-up syntax!
String rawRegex = !"\[\d{2,4}\]" // π€
Maybe, in the future, we can "enable" rawness. π€
(But no plans at the moment.)
Java’s strings are:
essential to development
not expressive
Project Amber introduces new features that:
make strings more expressive
learned from other languages
can be combined as needed
How Project Amber tackles the tension
explicitness/safety vs succinctness
for less verbosity and new features.
Java is…
every field, variable, method argument
and return has a type
all this is known at compile time
For our types, fields, methods:
decouple API from internal state
hide internals (behind accessors)
pick lowest feasible visibility
We rely on them to:
model domains
guarantee invariants
modularize problems
catch errors early
understand code
But, they can be:
verbose
redundant
Underlying tension:
explicitness/safety vs succinctness.
Predicate<Person> isOld = person -> person.age() >= 30;
didn’t allow anything new!
focus on what’s essential
(behavior and semantics)
removed lots of verbosity
(and bits of explicitness)
Found a (situationally) better balance.
β More "code as data".
Aims to do more of that:
give up small amounts of benefits
in suitable, specific situations
for new semantics and features
for much more succinctness
Java 10 introduced var
(JEP 286):
var audienceMember = new Person("John Doe");
focus on what’s essential
(variable name and expression)
removes verbosity & redundancy
(and bits of explicitness)
Finds a better balance (if used wisely).
β More readable variables.
Java 16 introduced records (JEP 395):
record Person(String name, LocalDate birthday) { }
Transparent carriers for immutable data.
compiler understands internals
couple API to internals
reduce verbosity a lot
Defines a new point of balance.
β More "data as data".
Java 21 introduced record patterns (JEP 440):
if (obj instanceof Person(var name, var bday)) {
// use `name` and `bday`
}
switch (obj) {
case Person(var name, var bday) -> {
// use `name` and `bday`
}
// ...
}
var person = fetchPerson();
var unnamed = new Person("", person.birthday());
JEP 468 proposes derived record creation:
var person = fetchPerson();
var unnamed = person with { name = ""; };
var person = fetchPerson();
var name = person.name();
var bday = person.birthday();
// use `name` and `bday`
In the future (no JEP, but it’s coming):
// speculative syntax
Person(var name, var bday) = fetchPerson();
// use `name` and `bday`
Type safety and encapsulation:
are bedrocks of Java
but aren’t free
Project Amber introduces new features that:
lower the cost
make them shine brighter
How Project Amber introduces
a new programming paradigm
to handle data as data.
Essentials when dealing with data
(JSON, SQL result, events, β¦):
represent data with simple, immutable types
model alternatives explicitly
make illegal states unrepresentable
represent polymorphic behavior with functions
(de)construct data easily and quickly
Starting with a seed URL:
identifies kind of page
follows links from GitHub pages (β 1.)
public static Page loadPageTree(/*...*/) {
// ...
}
What does Page
look like?
"Represent data with simple immutable types." β
Records:
public record ErrorPage(
URI url, Exception ex) { }
public record ExternalPage(
URI url, String content) { }
public record GitHubIssuePage(
URI url, String content,
Set<Page> links, int issueNumber) { }
public record GitHubPrPage(
URI url, String content,
Set<Page> links, int prNumber) { }
"Model alternatives explicitly." β
Use sealed types to limit inheritance.
Java 17 introduced sealed types (JEP 409):
public sealed interface Page
permits ErrorPage, SuccessfulPage {
// ...
}
Only ErrorPage
and SuccessfulPage
can implement/extend Page
.
β interface MyPage extends Page
doesn’t compile
public sealed interface Page
permits ErrorPage, SuccessfulPage {
URI url();
}
public sealed interface SuccessfulPage
extends Page permits ExternalPage, GitHubPage {
String content();
}
public sealed interface GitHubPage
extends SuccessfulPage
permits GitHubIssuePage, GitHubPrPage {
Set<Page> links();
default Stream<Page> subtree() { ... }
}
"Make illegal states unrepresentable." β
Combine:
sealed types
records
data validation
(during construction)
(Also makes code more self-explanatory.)
"Represent polymorphic behavior with functions." β
(Static) methods that have data as input:
public static String createPageName(Page page) {
// ...
}
"Polymorphic"β
Switch over input type:
public static String createPageName(Page page) {
return switch (page) {
case ErrorPage err
-> "π₯ ERROR: " + err.url().getHost();
case ExternalPage ext
-> "π€ EXTERNAL: " + ext.url().getHost();
case GitHubIssuePage issue
-> "π ISSUE #" + issue.issueNumber();
case GitHubPrPage pr
-> "π PR #" + pr.prNumber();
// ...
};
}
To keep code maintainable:
switch over sealed types
enumerate all possible types
avoid default
branch
switch (page) {
case ErrorPage err -> // ...
case ExternalPage ext -> // ...
case GitHubIssuePage issue -> // ...
case GitHubPrPage pr -> // ...
// no default branch!
}
β Compile error when new type is added.
"Deconstruct data easily and quickly" β
Use deconstruction patterns:
public static String createPageName(Page page) {
return switch (page) {
case ErrorPage(var url, var ex)
-> "π₯ ERROR: " + url.getHost();
case GitHubIssuePage(
var url, var content, var links,
int issueNumber)
-> "π ISSUE #" + issueNumber;
// ...
};
}
Java 22 finalizes unnamed patterns (JEP 456).
Use _
to ignore components:
public static String createPageName(Page page) {
return switch (page) {
case ErrorPage(var url, _)
-> "π₯ ERROR: " + url.getHost();
case GitHubIssuePage(_, _, _, int issueNumber)
-> "π ISSUE #" + issueNumber;
// ...
};
}
β Focus on what’s essential.
Use _
to define default behavior:
public static String createPageEmoji(Page page) {
return switch (page) {
case GitHubIssuePage issue -> "π";
case GitHubPrPage pr -> "π";
case ErrorPage _, ExternalPage _ -> "n.a.";
};
}
β Default behavior without default
branch.
Use Java’s strong typing to model data as data:
use types to model data, particularly:
data as data with records
alternatives with sealed types
use (static) methods to model behavior, particularly:
exhaustive switch
without default
pattern matching to destructure polymorphic data
but it’s similar (data + functions)
first priority is data, not functions
use OOP to modularize large systems
use DOP to model small, data-focused (sub)systems
More on data-oriented programming:
seminal article by Brian Goetz on InfoQ
GitHub crawler on github.com/nipafx/loom-lab
intro in Inside Java Newscast #29
deeper tutorial in JEP Cafe #14
practical example in Inside Java Newscast #33
Object-oriented programming:
is the beating heart of Java develoment π
but isn’t the best fit in all situations
Project Amber introduces new features that:
unlock data-oriented programming
make functional programming more feasible
How Project Amber
paves the on-ramp
for new (Java) developers.
We all know Java, IDEs, build tools, etc.
do we all?
what about your kids?
what about students?
what about the frontend dev?
what about ML/AI folks?
Java needs to be approachable!
Java needs an on-ramp for new (Java) developers!
To write and run a simple Java program, you need:
a JDK
an editor (IDE?)
javac
(build tool? IDE?)
java
(IDE?)
some Java code
Minimal Java code:
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
visibility
classes & methods
static vs instance
returns & parameters
statements & arguments
That’s a lot of tools and concepts!
Java is great for large-scale development:
detailed toolchain
refined programming model
This make it less approachable.
Let’s change that!
Java 9 added jshell
:
all you need:
tools: JDK, jshell
concepts: statements & arguments
but:
not great for beginners (IMO)
no progression
More is needed.
Java 11 added single-file execution (JEP 330):
java Prog.java
removed: javac
but: no progression
Much better for beginners,
but just a section of an on-ramp.
Expand single-file execution in two directions:
ease progression: run multiple files with java
simplify code: reduce required Java concepts
Say you have a folder:
MyFirstJava
ββ Prog.java
ββ Helper.java
ββ Lib
ββ library.jar
Run with:
java -cp 'Lib/*' Prog.java
Added in Java 22 (JEP 458).
Natural progression:
start with main()
need arguments? β add String[] args
need to organize code? β add methods
need shared state? β add fields
need more functionality? β explore JDK APIs
even more? β explore simple libraries
need more structure? β split into multiple files
even more β use visibility & packages
Doesn’t even have to be that order!
Java’s strengths for large-scale development
make it less approachable:
detailed toolchain
refined programming model
Project Amber introduces new features that:
make it easier to start
allow gradual progression
entice the future dev generation
Up to Java 21:
var
text blocks
records
type patterns
sealed types
pattern matching in switch
record patterns
In Java 21:
unnamed patterns (preview)
string templates (preview)
simpler main
(preview)
In Java 22:
unnamed patterns
multi-file launcher
string templates (preview)
simpler main
(preview)
And beyond:
deconstruction assignments
with
expression
AND MORE:
relaxed this()
/super()
(JEP 447; preview in 22)
primitive types in patterns (JEP 455; preview in 23)
concise method bodies (JEP draft)
serialization revamp (design notes)
The solution factory
to Java’s problems!