KEMBAR78
The Art of Java Type Patterns | PPTX
The Art of Java
Type Patterns
Simon Ritter, Deputy CTO | Azul
2
Modern Java
• The six-month release cadence for Java has been really good
• Lots of new features added much faster than we've ever seen before
• Significant language changes are initially developed under project Amber
o "... explore and incubate smaller, productivity-oriented Java language features..."
o Most features go through at least two rounds of preview
• Many of the new features work both separately and combined with others
3
Pattern Matching In Java
• java.util.regex
• This is not what we're here to talk about
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
boolean b = Pattern.matches("a*b", "aaaaab");
4
Pattern Matching Fundamentals
• A well used technique, been in use since the 1960s (used in Haskell, AWK, etc.)
match predicate pattern variables
Determines whether the pattern
matches a target
A pattern
Conditionally extracted if the
pattern matches the target
5
Pattern Types
• Constant
o Match on a constant (already in use in a switch statement)
• Type
o Match on a type
• Deconstruction
o Match and extract
• var
o Uses type inference to map to a type pattern (effecively matches anything)
• Any (_)
o Matches anything but binds to nothing (an unused pattern variable). See JEP 302
6
Before we get into to Pattern Matching
7
Switch Expressions
• Switch construct was a statement
o No concept of generating a result that could be assigned
• Rather clunky syntax
o Every case statement needs to be separated
o Must remember break (default is to fall through)
o Scope of local variables is not intuitive
8
Switch Statement
int numberOfLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numberOfLetters = 6;
break;
case TUESDAY:
numberOfLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numberOfLetters = 8;
break;
case WEDNESDAY:
numberOfLetters = 9;
break;
default:
throw new IllegalStateException("Huh?: " + day); };
9
Switch Expression (JDK 12)
• Switch expressions must be complete (exhaustive)
o We'll come back to this later
int numberOfLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Huh?: " + day);
};
10
Algebraic Data Types in Java
11
Simple Java Data Class
class Point {
private final double x;
private final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double x() {
return x;
}
public double y() {
return y;
}
}
12
Records (JDK 14)
record Point(double x, double y) { }
record Anything<T>(T t) { } // Generic Record
public record Circle(double radius) {
private static final double PI = 3.142; // Static instance fields are allowed
public double area() {
return PI * radius * radius;
}
}
13
Records Additional Details
• The base class of all records is java.lang.Record
o Records cannot sub-class (but may implement interfaces)
• Object methods equals(), hashCode() and toString() can be overridden
• Records are implicitly final (although you may add the modifier)
• Records do not follow the Java bean pattern
o x() not getX() in Point example
o record Point(getX, getY) // If you must
14
Java Inheritance
• A class (or interface) in Java can be sub-classed by any class
o Unless it is marked as final
Shape
Triangle Square Pentagon
15
Sealed Classes (JEP 360)
public sealed class Shape permits Triangle, Square, Pentagon { ... }
Shape
Triangle Square Pentagon Circle
X
16
Sealed Classes (JEP 360)
• All sub-classes must have inheritance capabilities explicitly specified
// Restrict sub-classes to defined set
public sealed class Triangle permits Equilateral, Isosoles extends Shape { ... }
// Prevent any further sub-classing
public final class Square extends Shape { ... }
// Allow any classes to sub-class this one (open)
public non-sealed class Pentagon extends Shape { ... }
17
Current Pattern Matching in Java
18
Using The instanceof Operator
if (obj instanceof String) {
String s = (String)obj;
System.out.println(s.length());
}
We must always perform an
explicit cast with an assignment
19
Pattern Matching For instanceof (JDK 14)
if (obj instanceof String s)
System.out.println(s.length());
else
// Use of s not allowed here
if (obj instanceof String s && s.length() > 0)
System.out.println(s.length());
// Compiler error
if (obj instanceof String s || s.length() > 0)
System.out.println(s.length());
20
Pattern Matching For instanceof
public void doSomething(Object o) {
if (!(o instanceof String s))
return;
System.out.println(s.length()); // Scope of s valid
// Several hundred lines of code
System.out.println(s.length()); // Still in scope
}
22
Flow Scoping For Binding Variables
• Scope of local variable runs from its declaration until the end of the block in which it is declared
o Locals are subject to definite assigment
• Binding variables are also subject to definite assignment
o The scope of a binding variable is the set of places in the program where it would be definitely assigned
o This is flow scoping
• However, scope is not the same as local variables
if (o instanceof Integer num) { ... }
else if (o instanceof Float num) { ... }
else if (o instanceof Long num) { ... }
Need flow scoping to be able
to reuse num as variable name
23
Pattern Matching For instanceof Puzzler
• Will this work?
Object s = new Object();
if (s instanceof String s)
System.out.println("String of length " + s.length());
else
System.out.println("No string");
25
Pattern Matching For switch
• Switch is limited on what types you can use (Integral values, Strings, enumerations)
• Expanded to allow type patterns to be matched
void typeTester(Object o) {
switch (o) {
case null -> System.out.println("Null type");
case String s -> System.out.println("String: " + s);
case Color c -> System.out.println("Color with RGB: " + c.getRGB());
case int[] ia -> System.out.println("Array of ints, length" + ia.length);
default -> System.out.println(o.toString());
}
}
26
Pattern Matching For switch
• Null is special (and complicated)
• case null can be used in all switch statements and expressions
o If not included, it will be added by compiler at start (throwing NullPointerException)
void typeTester(Object o) {
switch (o) {
case String s -> System.out.println("String: " + s);
case Color c -> System.out.println("Color with RGB: " + c.getRGB());
case int[] ia -> System.out.println("Array of ints, length" + ia.length);
default -> System.out.println("Bad input!");
}
}
27
Pattern Matching For switch
• Null is special (and complicated)
• case null can be used in all switch statements and expressions
o If not included, it will be added by compiler at start (throwing NullPointerException)
void typeTester(Object o) {
switch (o) {
case null -> throw new NullPointerException(); // Added by compiler
case String s -> System.out.println("String: " + s);
case Color c -> System.out.println("Color with RGB: " + c.getRGB());
case int[] ia -> System.out.println("Array of ints, length" + ia.length);
default -> System.out.println("Bad input!");
}
}
28
Pattern Matching For switch
• Null is special (and complicated)
• case null can be used in all switch statements and expressions
o If not included, it will be added by compiler at start (throwing NullPointerException)
void typeTester(Object o) {
switch (o) {
case String s -> System.out.println("String: " + s);
case Color c -> System.out.println("Color with RGB: " + c.getRGB());
case int[] ia -> System.out.println("Array of ints, length" + ia.length);
null, default -> System.out.println("Bad input!"); // No NullPointerException
}
}
29
Pattern Matching for switch (Completeness)
• Pattern switch statements (and all switch expressions) must be exhaustive
o All possible values must be handled
void typeTester(Object o) {
switch (o) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer with value " + i.getInteger());
}
}
void typeTester(Object o) {
switch (o) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer with value " + i.getInteger());
default -> System.out.println("Some other type");
}
}
30
Pattern Matching for switch (Completeness)
void typeTester(Shape shape) { // Using previous sealed class example
switch (shape) {
case Triangle t -> System.out.println("It's a triangle");
case Square s -> System.out.println("It's a square");
case Pentagon p -> System.out.println("It's a pentagon");
case Shape s -> System.out.println("It's a shape");
}
}
31
Guarded Patterns
void shapeTester(Shape shape) { // Using previous sealed class example
switch (shape) {
case Triangle t && t.area() > 25 -> System.out.println("It's a big triangle");
case Triangle t -> System.out.println("It's a small triangle");
case Square s -> System.out.println("It's a square");
case Pentagon p -> System.out.println("It's a pentagon");
case Shape s -> System.out.println("It's a shape");
}
}
GuardedPattern:
PrimaryPattern && ConditionalAndExpression
32
Pattern Dominance
• Less specific cases must not hide more specific cases
void typeTester(Shape shape) {
switch (shape) {
case Shape s -> System.out.println("It's a shape");
case Triangle t -> System.out.println("It's a triangle");
case Square s -> System.out.println("It's a square");
case Pentagon p -> System.out.println("It's a pentagon");
}
}
Shape will always match first
Triangle, Square, Pentagon cases
are unreachable
33
Pattern Dominance
void shapeTester(Shape shape) {
switch (shape) {
case Triangle t -> System.out.println("It's a small triangle");
case Triangle t && t.area() > 25 -> System.out.println("It's a big triangle");
case Square s -> System.out.println("It's a square");
case Pentagon p -> System.out.println("It's a pentagon");
case Shape s -> System.out.println("It's a shape");
}
}
Again, Triangle will match before
Triangle with a guard
34
Pattern Matching in Future Java
35
Pattern Matching instanceof And Records
• What we can do now
• This is good but we can do better
record Point(double x, double y) { }
public void pythagoras(Object o) {
if (o instanceof Point p) {
double x = p.x();
double y = p.y();
System.out.println("Hypotonuse = " + Math.sqrt((x*x) + (y*y));
}
}
36
Pattern Matching For Records (JEP 405)
• Use of a record pattern (which is a deconstruction pattern)
• Since a Record is just a special form of class, this will work with normal classes as well
public void pythagoras(Object o) {
if (o instanceof Point(double x, double y))
System.out.println("Hypotonuse = " + Math.sqrt((x*x) + (y*y));
}
37
Patterns Are Composable
record Point(double x, double y) {};
enum Colour { RED, GREEN, BLUE };
record ColourPoint(Point p, Colour c) {};
record ColourRectangle(ColourPoint topLeft,
ColourPoint bottomRight) implements Shape { ... };
public void printColour(Shape s) {
if (s instanceof ColourRectangle(ColourPoint topleft, ColourPoint bottomRight))
System.out.println(topLeft.c());
}
But this is a record as well
38
Patterns Are Composable
public void printColour(Shape s) {
if (s instanceof ColourRectangle(ColourPoint(Point p, Colour c), ColourPoint br))
System.out.println(c);
}
public void printTopLeftX(Shape s) {
if (s instanceof
ColourRectangle(ColourPoint(Point(double x, double y), Colour c), ColourPoint br)
System.out.println(x);
}
Yet another record
39
Patterns And Local Variable Type Inference
• Use var, introduced in JDK 10
• An example of the any pattern matching type
public void printTopLeftX(Shape s) {
if (s instanceof
ColourRectangle(ColourPoint(Point(var x, var y), var c), var br)
System.out.println(x);
}
40
Using The Any Pattern Match
• Not yet part of any proposed JEP
• Related to JEP 302, Lambda Leftovers
• This is not a proposed change, at this time (i.e. I'm speculating this as a feature)
public void printTopLeftX(Shape s) {
if (s instanceof
ColourRectangle(ColourPoint(Point(var x, _), _), _)
System.out.println(x);
}
We have no interest in these
variables so let's ignore them
41
Pattern Matching For Arrays
• Why not use a decompose pattern for arrays?
• This was part of JEP 405 but has been dropped for now
static void printFirstTwoStrings(Object o) {
if (o instanceof String[] sa && sa.length >= 2) {
String s1 = sa[0];
String s2 = sa[1];
System.out.println(s1 + s2);
}
}
static void printFirstTwoStrings(Object o) {
if (o instanceof String[] { String s1, String s2, ... })
System.out.println(s1 + s2);
}
Summary
43
Conclusions
• Pattern matching is a powerful set of language constructs
• Simplifies certain tasks
o Less boilerplate code
o More declarative
• Also has the potential for better optimisation of code and enhanced performance
• Some features already included
• More to come
• Yet more may be added later
44
Azul Zulu Builds of OpenJDK
• Enhanced build of OpenJDK source code
o Fully TCK tested
o JDK 7, 8, 11, 13, 15 and 17
o JDK 6 available for commercial customers
• Wide platform support:
o Intel 64-bit Windows, Mac, Linux
o Intel 32-bit Windows and Linux
• Free community edition, optional commercial support (Azul Platform Core)
Thank you!
@speakjava

The Art of Java Type Patterns

  • 1.
    The Art ofJava Type Patterns Simon Ritter, Deputy CTO | Azul
  • 2.
    2 Modern Java • Thesix-month release cadence for Java has been really good • Lots of new features added much faster than we've ever seen before • Significant language changes are initially developed under project Amber o "... explore and incubate smaller, productivity-oriented Java language features..." o Most features go through at least two rounds of preview • Many of the new features work both separately and combined with others
  • 3.
    3 Pattern Matching InJava • java.util.regex • This is not what we're here to talk about Pattern p = Pattern.compile("a*b"); Matcher m = p.matcher("aaaaab"); boolean b = m.matches(); boolean b = Pattern.matches("a*b", "aaaaab");
  • 4.
    4 Pattern Matching Fundamentals •A well used technique, been in use since the 1960s (used in Haskell, AWK, etc.) match predicate pattern variables Determines whether the pattern matches a target A pattern Conditionally extracted if the pattern matches the target
  • 5.
    5 Pattern Types • Constant oMatch on a constant (already in use in a switch statement) • Type o Match on a type • Deconstruction o Match and extract • var o Uses type inference to map to a type pattern (effecively matches anything) • Any (_) o Matches anything but binds to nothing (an unused pattern variable). See JEP 302
  • 6.
    6 Before we getinto to Pattern Matching
  • 7.
    7 Switch Expressions • Switchconstruct was a statement o No concept of generating a result that could be assigned • Rather clunky syntax o Every case statement needs to be separated o Must remember break (default is to fall through) o Scope of local variables is not intuitive
  • 8.
    8 Switch Statement int numberOfLetters; switch(day) { case MONDAY: case FRIDAY: case SUNDAY: numberOfLetters = 6; break; case TUESDAY: numberOfLetters = 7; break; case THURSDAY: case SATURDAY: numberOfLetters = 8; break; case WEDNESDAY: numberOfLetters = 9; break; default: throw new IllegalStateException("Huh?: " + day); };
  • 9.
    9 Switch Expression (JDK12) • Switch expressions must be complete (exhaustive) o We'll come back to this later int numberOfLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException("Huh?: " + day); };
  • 10.
  • 11.
    11 Simple Java DataClass class Point { private final double x; private final double y; public Point(double x, double y) { this.x = x; this.y = y; } public double x() { return x; } public double y() { return y; } }
  • 12.
    12 Records (JDK 14) recordPoint(double x, double y) { } record Anything<T>(T t) { } // Generic Record public record Circle(double radius) { private static final double PI = 3.142; // Static instance fields are allowed public double area() { return PI * radius * radius; } }
  • 13.
    13 Records Additional Details •The base class of all records is java.lang.Record o Records cannot sub-class (but may implement interfaces) • Object methods equals(), hashCode() and toString() can be overridden • Records are implicitly final (although you may add the modifier) • Records do not follow the Java bean pattern o x() not getX() in Point example o record Point(getX, getY) // If you must
  • 14.
    14 Java Inheritance • Aclass (or interface) in Java can be sub-classed by any class o Unless it is marked as final Shape Triangle Square Pentagon
  • 15.
    15 Sealed Classes (JEP360) public sealed class Shape permits Triangle, Square, Pentagon { ... } Shape Triangle Square Pentagon Circle X
  • 16.
    16 Sealed Classes (JEP360) • All sub-classes must have inheritance capabilities explicitly specified // Restrict sub-classes to defined set public sealed class Triangle permits Equilateral, Isosoles extends Shape { ... } // Prevent any further sub-classing public final class Square extends Shape { ... } // Allow any classes to sub-class this one (open) public non-sealed class Pentagon extends Shape { ... }
  • 17.
  • 18.
    18 Using The instanceofOperator if (obj instanceof String) { String s = (String)obj; System.out.println(s.length()); } We must always perform an explicit cast with an assignment
  • 19.
    19 Pattern Matching Forinstanceof (JDK 14) if (obj instanceof String s) System.out.println(s.length()); else // Use of s not allowed here if (obj instanceof String s && s.length() > 0) System.out.println(s.length()); // Compiler error if (obj instanceof String s || s.length() > 0) System.out.println(s.length());
  • 20.
    20 Pattern Matching Forinstanceof public void doSomething(Object o) { if (!(o instanceof String s)) return; System.out.println(s.length()); // Scope of s valid // Several hundred lines of code System.out.println(s.length()); // Still in scope }
  • 21.
    22 Flow Scoping ForBinding Variables • Scope of local variable runs from its declaration until the end of the block in which it is declared o Locals are subject to definite assigment • Binding variables are also subject to definite assignment o The scope of a binding variable is the set of places in the program where it would be definitely assigned o This is flow scoping • However, scope is not the same as local variables if (o instanceof Integer num) { ... } else if (o instanceof Float num) { ... } else if (o instanceof Long num) { ... } Need flow scoping to be able to reuse num as variable name
  • 22.
    23 Pattern Matching Forinstanceof Puzzler • Will this work? Object s = new Object(); if (s instanceof String s) System.out.println("String of length " + s.length()); else System.out.println("No string");
  • 23.
    25 Pattern Matching Forswitch • Switch is limited on what types you can use (Integral values, Strings, enumerations) • Expanded to allow type patterns to be matched void typeTester(Object o) { switch (o) { case null -> System.out.println("Null type"); case String s -> System.out.println("String: " + s); case Color c -> System.out.println("Color with RGB: " + c.getRGB()); case int[] ia -> System.out.println("Array of ints, length" + ia.length); default -> System.out.println(o.toString()); } }
  • 24.
    26 Pattern Matching Forswitch • Null is special (and complicated) • case null can be used in all switch statements and expressions o If not included, it will be added by compiler at start (throwing NullPointerException) void typeTester(Object o) { switch (o) { case String s -> System.out.println("String: " + s); case Color c -> System.out.println("Color with RGB: " + c.getRGB()); case int[] ia -> System.out.println("Array of ints, length" + ia.length); default -> System.out.println("Bad input!"); } }
  • 25.
    27 Pattern Matching Forswitch • Null is special (and complicated) • case null can be used in all switch statements and expressions o If not included, it will be added by compiler at start (throwing NullPointerException) void typeTester(Object o) { switch (o) { case null -> throw new NullPointerException(); // Added by compiler case String s -> System.out.println("String: " + s); case Color c -> System.out.println("Color with RGB: " + c.getRGB()); case int[] ia -> System.out.println("Array of ints, length" + ia.length); default -> System.out.println("Bad input!"); } }
  • 26.
    28 Pattern Matching Forswitch • Null is special (and complicated) • case null can be used in all switch statements and expressions o If not included, it will be added by compiler at start (throwing NullPointerException) void typeTester(Object o) { switch (o) { case String s -> System.out.println("String: " + s); case Color c -> System.out.println("Color with RGB: " + c.getRGB()); case int[] ia -> System.out.println("Array of ints, length" + ia.length); null, default -> System.out.println("Bad input!"); // No NullPointerException } }
  • 27.
    29 Pattern Matching forswitch (Completeness) • Pattern switch statements (and all switch expressions) must be exhaustive o All possible values must be handled void typeTester(Object o) { switch (o) { case String s -> System.out.println("String: " + s); case Integer i -> System.out.println("Integer with value " + i.getInteger()); } } void typeTester(Object o) { switch (o) { case String s -> System.out.println("String: " + s); case Integer i -> System.out.println("Integer with value " + i.getInteger()); default -> System.out.println("Some other type"); } }
  • 28.
    30 Pattern Matching forswitch (Completeness) void typeTester(Shape shape) { // Using previous sealed class example switch (shape) { case Triangle t -> System.out.println("It's a triangle"); case Square s -> System.out.println("It's a square"); case Pentagon p -> System.out.println("It's a pentagon"); case Shape s -> System.out.println("It's a shape"); } }
  • 29.
    31 Guarded Patterns void shapeTester(Shapeshape) { // Using previous sealed class example switch (shape) { case Triangle t && t.area() > 25 -> System.out.println("It's a big triangle"); case Triangle t -> System.out.println("It's a small triangle"); case Square s -> System.out.println("It's a square"); case Pentagon p -> System.out.println("It's a pentagon"); case Shape s -> System.out.println("It's a shape"); } } GuardedPattern: PrimaryPattern && ConditionalAndExpression
  • 30.
    32 Pattern Dominance • Lessspecific cases must not hide more specific cases void typeTester(Shape shape) { switch (shape) { case Shape s -> System.out.println("It's a shape"); case Triangle t -> System.out.println("It's a triangle"); case Square s -> System.out.println("It's a square"); case Pentagon p -> System.out.println("It's a pentagon"); } } Shape will always match first Triangle, Square, Pentagon cases are unreachable
  • 31.
    33 Pattern Dominance void shapeTester(Shapeshape) { switch (shape) { case Triangle t -> System.out.println("It's a small triangle"); case Triangle t && t.area() > 25 -> System.out.println("It's a big triangle"); case Square s -> System.out.println("It's a square"); case Pentagon p -> System.out.println("It's a pentagon"); case Shape s -> System.out.println("It's a shape"); } } Again, Triangle will match before Triangle with a guard
  • 32.
  • 33.
    35 Pattern Matching instanceofAnd Records • What we can do now • This is good but we can do better record Point(double x, double y) { } public void pythagoras(Object o) { if (o instanceof Point p) { double x = p.x(); double y = p.y(); System.out.println("Hypotonuse = " + Math.sqrt((x*x) + (y*y)); } }
  • 34.
    36 Pattern Matching ForRecords (JEP 405) • Use of a record pattern (which is a deconstruction pattern) • Since a Record is just a special form of class, this will work with normal classes as well public void pythagoras(Object o) { if (o instanceof Point(double x, double y)) System.out.println("Hypotonuse = " + Math.sqrt((x*x) + (y*y)); }
  • 35.
    37 Patterns Are Composable recordPoint(double x, double y) {}; enum Colour { RED, GREEN, BLUE }; record ColourPoint(Point p, Colour c) {}; record ColourRectangle(ColourPoint topLeft, ColourPoint bottomRight) implements Shape { ... }; public void printColour(Shape s) { if (s instanceof ColourRectangle(ColourPoint topleft, ColourPoint bottomRight)) System.out.println(topLeft.c()); } But this is a record as well
  • 36.
    38 Patterns Are Composable publicvoid printColour(Shape s) { if (s instanceof ColourRectangle(ColourPoint(Point p, Colour c), ColourPoint br)) System.out.println(c); } public void printTopLeftX(Shape s) { if (s instanceof ColourRectangle(ColourPoint(Point(double x, double y), Colour c), ColourPoint br) System.out.println(x); } Yet another record
  • 37.
    39 Patterns And LocalVariable Type Inference • Use var, introduced in JDK 10 • An example of the any pattern matching type public void printTopLeftX(Shape s) { if (s instanceof ColourRectangle(ColourPoint(Point(var x, var y), var c), var br) System.out.println(x); }
  • 38.
    40 Using The AnyPattern Match • Not yet part of any proposed JEP • Related to JEP 302, Lambda Leftovers • This is not a proposed change, at this time (i.e. I'm speculating this as a feature) public void printTopLeftX(Shape s) { if (s instanceof ColourRectangle(ColourPoint(Point(var x, _), _), _) System.out.println(x); } We have no interest in these variables so let's ignore them
  • 39.
    41 Pattern Matching ForArrays • Why not use a decompose pattern for arrays? • This was part of JEP 405 but has been dropped for now static void printFirstTwoStrings(Object o) { if (o instanceof String[] sa && sa.length >= 2) { String s1 = sa[0]; String s2 = sa[1]; System.out.println(s1 + s2); } } static void printFirstTwoStrings(Object o) { if (o instanceof String[] { String s1, String s2, ... }) System.out.println(s1 + s2); }
  • 40.
  • 41.
    43 Conclusions • Pattern matchingis a powerful set of language constructs • Simplifies certain tasks o Less boilerplate code o More declarative • Also has the potential for better optimisation of code and enhanced performance • Some features already included • More to come • Yet more may be added later
  • 42.
    44 Azul Zulu Buildsof OpenJDK • Enhanced build of OpenJDK source code o Fully TCK tested o JDK 7, 8, 11, 13, 15 and 17 o JDK 6 available for commercial customers • Wide platform support: o Intel 64-bit Windows, Mac, Linux o Intel 32-bit Windows and Linux • Free community edition, optional commercial support (Azul Platform Core)
  • 43.