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:
Smaller, productivity-oriented
Java language features
Profile:
project / wiki / mailing list
launched March 2017
led by Brian Goetz and Gavin Bierman
Goal is not:
syntax sugar
minor improvements
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 — optimization vs specification
Getting back to Project Amber:
strings
type safety & encapsulation
dealing with data
starting (with) Java
(Pay attention, this will be on the test!)
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 fixing 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());
Java 21 may preview string templates (JEP 430):
allow embedding expressions
understand need for explicit processing
(for validation, escaping, etc.)
String json = STR."""
{
"name": "\{name}",
"year": "\{bday.getYear()}"
}
""";
We’d be able to "enable" variable embedding.
Template expression:
String json = STR."""
{
"name": "\{name}",
"year": "\{bday.getYear()}"
}
""";
Ingredients:
template with embedded expressions: StringTemplate
template processor (e.g. STR
): StringTemplate
β String
*
Often, strings are just exchange format, e.g.:
start with: String
+ values
validate / sanitize (i.e. parse)
dumb down to: String
π€
parse to: JSONObject
, Statement
, β¦
Why the detour?
Top interface:
public interface Processor<RESULT, EX> {
RESULT process(StringTemplate s) throws EX;
}
RESULT
can be of any type!
// validates & escapes JSON
JSONObject doc = JSON."""
{
"name": "\{name}",
"year": "\{bday.getYear()}"
}
""";
// prevents SQL injections
Statement query = SQL."""
SELECT * FROM Person p
WHERE p.name = '\{name}'
""";
proposed for Java 12 (JEP 326)
withdrawn due to complexity
still needed (e.g. for regex)
String regex = "\\[\\d{2,4}\\]"; // π€π€π€
// made-up syntax!
String rawRegex = !"\[\d{2,4}\]" // π€
We hope to be able to "enable" rawness. π€
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 verbosity
for more succinctness 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:
pick lowest feasible visibility
hide internals (behind accessors)
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 verbosity.
Predicate<Person> isOld = person -> person.age() >= 30;
didn’t allow anything new!
focus on what’s essential
(behavior and semantics)
removed losts 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) { }
[T]ransparent carriers for immutable data.
opt out of encapsulation
allow compiler to understand internals
reduce verbosity a lot
Defines a new point of balance.
β More and simpler types.
Probably in Java 21 (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 name = person.name();
var bday = person.birtday();
// 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`
var person = fetchPerson();
var unnamed = new Person("", person.birthday());
Maybe in the future (design document from Aug 2020):
// highly speculative syntax
var person = fetchPerson();
var unnamed = person with {
name = "";
};
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
(since Java 17; 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 polymoprhic 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;
// ...
};
}
With unnamed patterns (JEP 443),
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 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
yesterday’s talk on youtube.com/@DevoxxForever
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:
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:
simplyify code: reduce required Java concepts
ease progression: run multiple files with java
Remove requirement of:
String[] args
parameter
main
being static
main
or the class being public
the class itself
// all the code in Prog.java
void main() {
System.out.println("Hello, World!")
}
Say you have a folder:
MyFirstJava
ββ Prog.java
ββ Helper.java
ββ Lib
ββ library.jar
Run with:
java -cp 'Lib/*' Prog.java
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
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
Apply what you learned!
Up to Java 21 previews:
var
text blocks
records
type/record patterns
And beyond:
deconstruction assignments
string templates
simpler main
Is doing all that
AND MORE:
relaxed this()
/super()
(JEP draft)
concise method bodies (JEP draft)
serialization revamp (design notes)