At first it seemed to be just a small enhancement: the addition of “Pattern Matching for instanceof” (JEP 305) in Java 14. No more unnecessary casting after an instanceof, that ought to save us a few seconds a day! However, upon further investigation you’ll quickly discover that pattern matching is not just an enhancement, but rather a vital puzzle piece in the grander scheme of things.
Why were switch expressions added to Java, for example? To make them support pattern matching in a later release! And why did Java 14 bring us records and will Java 15 contain sealed types? Because they could work really well with pattern matching in a later release! These new concepts are the foundation upon which advanced pattern matching features will be built.
So attend this session to get all caught up! You’ll hear about type patterns, deconstruction patterns, nested patterns and even how pattern matching could improve serialization in the future. Live coding included, of course!
13. Pattern matching
Pattern matching
Pattern matching
Pattern matching
Pattern matching
Allows the conditional extraction of
components from objects to be
expressed more concisely and safely.
https://www.pexels.com/photo/person-holding-white-chalk-625219/
14. Declaring 'in the
Declaring 'in the
middle'
middle'
if (product instanceof Guitar lesPaul) {
// use lesPaul
}
1
2
3
15. Scoping
Scoping
'Regular' local variable ('block scoping')
'Regular' local variable ('block scoping')
The block in which it is declared.
void playTunedGuitar() {
Guitar lesPaul = new Guitar("Les Paul");
if (!lesPaul.isInTune()) {
Guitar fenderStrat = new Guitar("Fender Stratocaster");
fenderStrat.play();
}
}
1
2
3
4
5
6
7
8
16. Scoping
Scoping
'Regular' local variable ('block scoping')
'Regular' local variable ('block scoping')
The block in which it is declared.
void playTunedGuitar() {
Guitar lesPaul = new Guitar("Les Paul");
if (!lesPaul.isInTune()) {
Guitar fenderStrat = new Guitar("Fender Stratocaster");
fenderStrat.play();
// fenderStrat is in scope
}
// fenderStrat is not in scope
}
1
2
3
4
5
6
7
8
9
10
17. Scoping
Scoping
Pattern binding variable ('flow scoping')
Pattern binding variable ('flow scoping')
The set of places where it would definitely be
assigned.
if (product instanceof Guitar lesPaul) {
// can use lesPaul here
} else {
// can't use lesPaul here
}
1
2
3
4
5
18. Scoping
Scoping
Pattern binding variable ('flow scoping')
Pattern binding variable ('flow scoping')
The set of places where it would definitely be
assigned.
boolean isTunedGuitar(Object product) {
if (!(product instanceof Guitar lesPaul)) {
return false;
}
// This code is only reachable if 'product' is
// a Guitar, so 'lesPaul' is in scope.
return lesPaul.isInTune();
}
1
2
3
4
5
6
7
8
9
19.
20. Scoping
Scoping
Pattern binding variable ('flow scoping')
Pattern binding variable ('flow scoping')
The set of places where it would definitely be
assigned.
void test(Effect effect) {
if (effect instanceof Reverb stockEffect)
stockEffect.setRoomSize(25);
else if (effect instanceof Delay stockEffect)
stockEffect.setTimeInMs(200);
}
1
2
3
4
5
6
32. Switch expression
Switch expression
case Delay de -> String.format("Delay active of %d ms.",
case Reverb re -> String.format("Reverb active of type %s
String apply(Effect effect) {
1
return switch(effect) {
2
3
4
default -> String.format("Unknown effect active: %s
5
};
6
}
7
33. Switch expression
Switch expression
case Overdrive ov -> String.format("Overdrive active with ga
case Tremolo tr -> String.format("Tremolo active with depth
case Tuner tu -> String.format("Tuner active with pitch
String apply(Effect effect) {
1
return switch(effect) {
2
case Delay de -> String.format("Delay active of %d ms.",
3
case Reverb re -> String.format("Reverb active of type %s
4
5
6
7
default -> String.format("Unknown effect active: %
8
};
9
}
10
34. Switch expression
Switch expression
case EffectLoop el -> el.getEffects().stream().map(this::apply
String apply(Effect effect) {
1
return switch(effect) {
2
case Delay de -> String.format("Delay active of %d ms.",
3
case Reverb re -> String.format("Reverb active of type %s
4
case Overdrive ov -> String.format("Overdrive active with ga
5
case Tremolo tr -> String.format("Tremolo active with depth
6
case Tuner tu -> String.format("Tuner active with pitch
7
8
default -> String.format("Unknown effect active: %
9
};
10
}
11
35. Switch expression
Switch expression
String apply(Effect effect) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.",
case Reverb re -> String.format("Reverb active of type %s
case Overdrive ov -> String.format("Overdrive active with ga
case Tremolo tr -> String.format("Tremolo active with depth
case Tuner tu -> String.format("Tuner active with pitch
case EffectLoop el -> el.getEffects().stream().map(this::apply
default -> String.format("Unknown effect active: %
};
}
1
2
3
4
5
6
7
8
9
10
11
36.
37. Sensible operations to the
Sensible operations to the
effect loop
effect loop
apply()
setVolume(int volume)
contains(Effect... effect)
38. Nonsensical operations to the
Nonsensical operations to the
effect loop
effect loop
isTunerActive()
isDelayTimeEqualToReverbRoomSize()
isToneSuitableToPlayPrideInTheNameOfLov
39. Visitor pattern
Visitor pattern
interface EffectVisitor<T> {
T visit(Tuner effect);
T visit(Delay effect);
T visit(Reverb effect);
// ...
}
class IsTunerActiveVisitor implements EffectVisitor<Boolean> {
public Boolean visit(Tuner effect) {
return !effect.isInTune();
}
public Boolean visit(Delay effect) { return false; }
public Boolean visit(Reverb effect) { return false; }
// ...
}
40. Switch expression
Switch expression
String apply(Effect effect) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.",
case Reverb re -> String.format("Reverb active of type %s
case Overdrive ov -> String.format("Overdrive active with ga
case Tremolo tr -> String.format("Tremolo active with depth
case Tuner tu -> String.format("Tuner active with pitch
case EffectLoop el -> el.getEffects().stream().map(this::apply
default -> String.format("Unknown effect active: %
};
}
1
2
3
4
5
6
7
8
9
10
11
41. Switch expression
Switch expression
static String apply(Effect effect) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.",
case Reverb re -> String.format("Reverb active of type %s
case Overdrive ov -> String.format("Overdrive active with ga
case Tremolo tr -> String.format("Tremolo active with depth
case Tuner tu -> String.format("Tuner active with pitch
case EffectLoop el -> el.getEffects().stream().map(Effect::app
default -> String.format("Unknown effect active: %
};
}
1
2
3
4
5
6
7
8
9
10
11
42. Benefits
Benefits
No need for the Visitor pattern or a common
supertype
A single expression instead of many assignments
Less error-prone (in adding cases)
More concise
Safer - the compiler can check for missing cases
43. What happens if
What happens if effect
effect is
is
null
null?
?
static String apply(Effect effect) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.",
case Reverb re -> String.format("Reverb active of type %s
case Overdrive ov -> String.format("Overdrive active with ga
case Tremolo tr -> String.format("Tremolo active with depth
case Tuner tu -> String.format("Tuner active with pitch
case EffectLoop el -> el.getEffects().stream().map(Effect::app
default -> String.format("Unknown effect active: %
};
}
1
2
3
4
5
6
7
8
9
10
11
44. What happens if
What happens if effect
effect is
is
null
null?
?
return switch(effect) { // throws NullPointerException!
static String apply(Effect effect) {
1
2
case Delay de -> String.format("Delay active of %d ms.",
3
case Reverb re -> String.format("Reverb active of type %s
4
case Overdrive ov -> String.format("Overdrive active with ga
5
case Tremolo tr -> String.format("Tremolo active with depth
6
case Tuner tu -> String.format("Tuner active with pitch
7
case EffectLoop el -> el.getEffects().stream().map(Effect::app
8
default -> String.format("Unknown effect active: %
9
};
10
}
11
45. Solution #1: defensive testing
Solution #1: defensive testing
if (effect == null) {
return "Malfunctioning effect active.";
}
static String apply(Effect effect) {
1
2
3
4
5
return switch(effect) {
6
case Delay de -> String.format("Delay active of %d ms.",
7
case Reverb re -> String.format("Reverb active of type %s
8
case Overdrive ov -> String.format("Overdrive active with ga
9
case Tremolo tr -> String.format("Tremolo active with depth
10
case Tuner tu -> String.format("Tuner active with pitch
11
case EffectLoop el -> el.getEffects().stream().map(Effect::app
12
default -> String.format("Unknown effect active: %
13
};
14
}
15
46. Solution #2: integrate null
Solution #2: integrate null
check in switch
check in switch
case null -> return "Malfunctioning effect active.";
static String apply(Effect effect) {
1
return switch(effect) {
2
3
case Delay de -> String.format("Delay active of %d ms.",
4
case Reverb re -> String.format("Reverb active of type %s
5
case Overdrive ov -> String.format("Overdrive active with ga
6
case Tremolo tr -> String.format("Tremolo active with depth
7
case Tuner tu -> String.format("Tuner active with pitch
8
case EffectLoop el -> el.getEffects().stream().map(Effect::app
9
default -> String.format("Unknown effect active: %
10
};
11
}
12
47. Combining case labels
Combining case labels
case null, default -> String.format("Unknown or malfunctionin
static String apply(Effect effect) {
1
return switch(effect) {
2
case Delay de -> String.format("Delay active of %d ms.",
3
case Reverb re -> String.format("Reverb active of type %s
4
case Overdrive ov -> String.format("Overdrive active with ga
5
case Tremolo tr -> String.format("Tremolo active with depth
6
case Tuner tu -> String.format("Tuner active with pitch
7
case EffectLoop el -> el.getEffects().stream().map(Effect::app
8
9
};
10
}
11
48. Guarded patterns
Guarded patterns
String apply(Effect effect, Guitar guitar) {
return switch(effect) {
// (...)
case Tremolo tr-> String.format("Tremolo active with depth %d
case Tuner tu -> String.format("Tuner active with pitch %d. Mu
case EffectLoop el -> el.getEffects().stream().map(this::apply
default -> String.format("Unknown effect active: %s.", effect)
};
}
1
2
3
4
5
6
7
8
9
https://openjdk.java.net/jeps/406
49. Guarded patterns
Guarded patterns
case Tuner tu && !tu.isInTune(guitar) -> String.format("Guitar
String apply(Effect effect, Guitar guitar) {
1
return switch(effect) {
2
// (...)
3
case Tremolo tr-> String.format("Tremolo active with depth %d
4
5
case EffectLoop el -> el.getEffects().stream().map(this::apply
6
default -> String.format("Unknown effect active: %s.", effect)
7
};
8
}
9
55. Deconstruction patterns
Deconstruction patterns
static String apply(Effect effect) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.",
case Reverb re -> String.format("Reverb active of type %s
case Overdrive ov -> String.format("Overdrive active with ga
case Tremolo tr -> String.format("Tremolo active with depth
case Tuner tu -> String.format("Tuner active with pitch
case EffectLoop el -> el.getEffects().stream().map(Effect::app
default -> String.format("Unknown effect active: %
};
}
1
2
3
4
5
6
7
8
9
10
11
56. Deconstruction patterns
Deconstruction patterns
case Overdrive(int gain) -> String.format("Overdrive active w
static String apply(Effect effect) {
1
return switch(effect) {
2
case Delay de -> String.format("Delay active of %d ms.",
3
case Reverb re -> String.format("Reverb active of type %s
4
5
case Tremolo tr -> String.format("Tremolo active with depth
6
case Tuner tu -> String.format("Tuner active with pitch
7
case EffectLoop el -> el.getEffects().stream().map(Effect::app
8
default -> String.format("Unknown effect active: %
9
};
10
}
11
58. Pattern definition
Pattern definition
public pattern Overdrive(int gain) {
gain = this.gain;
}
public class Overdrive implements Effect {
1
private final int gain;
2
3
public Overdrive(int gain) {
4
this.gain = gain;
5
}
6
7
8
9
10
}
11
59. Deconstruction patterns
Deconstruction patterns
case Overdrive(int gain) -> String.format("Overdrive active w
static String apply(Effect effect) {
1
return switch(effect) {
2
case Delay de -> String.format("Delay active of %d ms.",
3
case Reverb re -> String.format("Reverb active of type %s
4
5
case Tremolo tr -> String.format("Tremolo active with depth
6
case Tuner tu -> String.format("Tuner active with pitch
7
case EffectLoop el -> el.getEffects().stream().map(Effect::app
8
default -> String.format("Unknown effect active: %
9
};
10
}
11
60. Deconstruction patterns
Deconstruction patterns
static String apply(Effect effect) {
return switch(effect) {
case Delay(int timeInMs) -> String.format("Delay active of %d
case Reverb(String name, int roomSize) -> String.format("Reve
case Overdrive(int gain) -> String.format("Overdrive active w
case Tremolo(int depth, int rate) -> String.format("Tremolo ac
case Tuner(int pitchInHz) -> String.format("Tuner active with
case EffectLoop(Set<Effect> effects) -> effects.stream().map(E
default -> String.format("Unknown effect active: %s.", effect
};
}
1
2
3
4
5
6
7
8
9
10
11
64. Var and any patterns
Var and any patterns
// Pre-Java 10
Guitar telecaster = new Guitar("Fender Telecaster Baritone Blacktop", G
// Java 10
var telecaster = new Guitar("Fender Telecaster Baritone Blacktop", Gui
1
2
3
4
5
https://openjdk.java.net/jeps/286
65. Var and any patterns
Var and any patterns
static boolean isDelayTimeEqualToReverbRoomSize(EffectLoop effectLoop)
if (effectLoop instanceof EffectLoop(Delay(int timeInMs), Reverb(St
return timeInMs == roomSize;
}
return false;
}
1
2
3
4
5
6
66. Var and any patterns
Var and any patterns
if (effectLoop instanceof EffectLoop(Delay(var timeInMs), Reverb(v
static boolean isDelayTimeEqualToReverbRoomSize(EffectLoop effectLoop)
1
2
return timeInMs == roomSize;
3
}
4
return false;
5
}
6
68. Var and any patterns
Var and any patterns
static boolean isDelayTimeEqualToReverbRoomSize(EffectLoop effectLoop)
if (effectLoop instanceof EffectLoop(Delay(var timeInMs), Reverb(_
return timeInMs == roomSize;
}
return false;
}
1
2
3
4
5
6
69. Optimization
Optimization
static String apply(Effect effect) {
return switch(effect) {
case Delay(int timeInMs) -> String.format("Delay active of %d
case Reverb(String name, int roomSize) -> String.format("Reve
case Overdrive(int gain) -> String.format("Overdrive active w
case Tremolo(int depth, int rate) -> String.format("Tremolo ac
case Tuner(int pitchInHz) -> String.format("Tuner active with
case EffectLoop(var effects) -> effects.stream().map(Effect::
default -> String.format("Unknown effect active: %s.", effect
};
}
1
2
3
4
5
6
7
8
9
10
11
72. Benefits
Benefits
Better encapsulation
a case branch only receives data that it actually references.
More elegant logic
by using pattern composition
Optimization
through the use of any patterns
74. Pattern Matching Plays
Pattern Matching Plays
Nice With
Nice With
Sealed Types
Sealed Types
Sealed Types
Sealed Types
Sealed Types
and Records
and Records
and Records
and Records
and Records
https://pxhere.com/en/photo/752901
75. Completeness on pattern switch
Completeness on pattern switch
expressions
expressions
static String apply(Object object) {
return switch(object) {
case Effect e -> e.apply();
case String s -> String.format("Tried to apply a String: %s",
case Number n -> String.format("Tried to apply a Number: %d",
};
}
1
2
3
4
5
6
7
76. Completeness on pattern switch
Completeness on pattern switch
expressions
expressions
return switch(object) { // Won't compile - incomplete!
static String apply(Object object) {
1
2
case Effect e -> e.apply();
3
case String s -> String.format("Tried to apply a String: %s",
4
case Number n -> String.format("Tried to apply a Number: %d",
5
};
6
}
7
77. Completeness on pattern switch
Completeness on pattern switch
expressions
expressions
default -> String.format("Tried to apply an Object: %s",
static String apply(Object object) {
1
return switch(object) {
2
case Effect e -> e.apply();
3
case String s -> String.format("Tried to apply a String: %s",
4
case Number n -> String.format("Tried to apply a Number: %d",
5
6
};
7
}
8
79. Sealed types yield
Sealed types yield
completeness
completeness
static String apply(Effect effect) {
return switch(effect) {
case Delay(int timeInMs) -> String.format("Delay active of %d
case Reverb(String name, int roomSize) -> String.format("Reve
case Overdrive(int gain) -> String.format("Overdrive active w
case Tremolo(int depth, int rate) -> String.format("Tremolo ac
case Tuner(int pitchInHz) -> String.format("Tuner active with
case EffectLoop(Tuner(int pitchInHz), _) -> String.format("The
case EffectLoop(var effects) -> effects.stream().map(Effect::
default -> String.format("Unknown effect active: %s.", effect
};
}
1
2
3
4
5
6
7
8
9
10
11
12
80. Sealed types yield
Sealed types yield
completeness
completeness
static String apply(Effect effect) {
return switch(effect) {
case Delay(int timeInMs) -> String.format("Delay active of %d
case Reverb(String name, int roomSize) -> String.format("Reve
case Overdrive(int gain) -> String.format("Overdrive active w
case Tremolo(int depth, int rate) -> String.format("Tremolo ac
case Tuner(int pitchInHz) -> String.format("Tuner active with
case EffectLoop(Tuner(int pitchInHz), _) -> String.format("The
case EffectLoop(var effects) -> effects.stream().map(Effect::
};
}
1
2
3
4
5
6
7
8
9
10
11
https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html
82. Records
Records
Input:
Input:
Commit to the class being a transparent carrier for
its data.
Output:
Output:
constructors
accessor methods
equals()-implementation
hashCode()-implementation
toString()-implementation
deconstruction pattern
83. Record patterns
Record patterns
record Amplifier(String name, EffectLoop stockEffects, EffectLoop auxE
record EffectLoop(Delay delay, Reverb reverb) { }
1
2
static String switchOn(Amplifier amplifier) {
return switch(effectLoop) {
// ...
case Amplifier(var name, EffectLoop(Delay(int timeInMs), Reverb
// ...
}
}
1
2
3
4
5
6
7
84. Array patterns
Array patterns
record EffectLoop(String name, int volume, Effect... effects) { }
1
static String apply(EffectLoop effectLoop) {}
return switch(effectLoop) {
case EffectLoop(var name, var volume) -> " Effect loop contain
case EffectLoop(var name, var volume, var effect) -> "Effect lo
case EffectLoop(var name, var volume, var effect, ...) -> "Eff
case EffectLoop(var name, var volume, var effect1, var effect2
case EffectLoop(var name, var volume, var effect1, var effect2
}
}
1
2
3
4
5
6
7
8
9
85. Feature Status
Feature Status
Sealed Types
Sealed Types
Java version Feature status JEP
15 Preview
16 Second preview
17 Final
JEP 360
JEP 397
JEP 409
88. A Better
A Better
A Better
A Better
A Better
Serialization
Serialization
Serialization
Serialization
Serialization
?
?
?
?
?
89. Here be dragons!
Here be dragons!
Here be dragons!
Here be dragons!
Here be dragons!
We can't be sure at all that the
following features will appear in Java
as depicted.
They can change a lot in
the meantime.
https://www.pexels.com/photo/dragon-festival-during-nighttime-6068535/
91. Serialization
Serialization
very important feature
but many people hate its current implementation
Drawbacks
Drawbacks
it undermines the accessibility model
serialization logic is not 'readable code'
it bypasses constructors and data validation
92. Serialization
Serialization
public class EffectLoop implements Effect {
private String name;
private Set<Effect> effects;
public EffectLoop(String name) {
this.name = name;
this.effects = new HashSet<>();
}
}
1
2
3
4
5
6
7
8
9
93. Serialization
Serialization
public pattern EffectLoop(String name, Effect[] effects) {
name = this.name;
effects = this.effects.toArray();
}
public class EffectLoop implements Effect {
1
private String name;
2
private Set<Effect> effects;
3
4
public EffectLoop(String name) {
5
this.name = name;
6
this.effects = new HashSet<>();
7
}
8
9
10
11
12
13
}
14
94. Serialization
Serialization
public static Effectloop deserialize(String name, Effect[] effect
EffectLoop effectLoop = new EffectLoop(name);
for (Effect effect : effects) {
this.effects.add(effect);
}
return effectLoop;
public class EffectLoop implements Effect {
1
private String name;
2
private Set<Effect> effects;
3
4
public EffectLoop(String name) {
5
this.name = name;
6
this.effects = new HashSet<>();
7
}
8
9
10
11
12
13
14
15
}
16
95. Serialization
Serialization
public class EffectLoop implements Effect {
private String name;
private Set<Effect> effects;
public EffectLoop(String name) {
this.name = name;
this.effects = new HashSet<>();
}
@Deserializer
public static EffectLoop deserialize(String name, Effect[] effect
EffectLoop effectLoop = new EffectLoop(name);
for (Effect effect : effects) {
this.effects.add(effect);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
98. Here be super dragons!
Here be super dragons!
Here be super dragons!
Here be super dragons!
Here be super dragons!
We can't be sure that the following
features will appear in Java as
depicted, if at all.
Proceed with caution!
https://www.pexels.com/photo/dragon-festival-during-nighttime-6068535/
99. Pattern bind statements
Pattern bind statements
var reverb = new Reverb("ChamberReverb", 2);
__let Reverb(String name, int roomSize) = reverb;
// do something with name & roomSize
1
2
3
4
5
https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html
100. Pattern bind statements
Pattern bind statements
else throw new IllegalArgumentException("not a Reverb!");
var reverb = new Reverb("ChamberReverb", 2);
1
2
__let Reverb(String name, int roomSize) = reverb;
3
4
5
// do something with name & roomSize
6
101. Other ideas
Other ideas
Deconstruction patterns for all classes
Enhanced array patterns
String[] { [8] -> var eighthElement, [9] -> var ninthElement}
AND patterns
Patterns in catch clauses
Collection patterns
https://mail.openjdk.java.net/pipermail/amber-spec-experts/2021-January/002758.html
103. Pattern matching...
Pattern matching...
is a rich feature arc that will play out over several
versions.
allows us to use type patterns in instanceof.
improves switch expressions.
makes destructuring objects as easy (and more
similar to) constructing them.
holds the potential to simplify and streamline much
of the code we write today.