References:
- "Java Language Updates Release 21" @ https://docs.oracle.com/en/java/javase/21/language/index.html - describes the updated language features from JDK 9 to JDK 21.
Preview Features
Many of the new features started as preview features, and are standardized in later releases. To compile and run a program with preview features for JDK 21, you need to include the --enable-preview -source 21
flags. For example,
// To compile with preview feature for JDK 21 javac --enable-preview -source 21 HelloPreview.java // To run javac --enable-preview HelloPreview
Summary of Language Change from JDK 9
- JDK 9 (Release Notes):
- JSR 376: Java Platform Module System
- JSR 334: Small Enhancements to the Java Programming Language
JEP 213: Milling Project Coin- try-with-resources for
final
and effectivelyfinal
variables @SafeVarargs
allowed onprivate
instance methods- Diamond operator <> in anonymous inner classes
- Underscore (_) character not a legal name
- Support for
private
interface
methods
- try-with-resources for
- JDK 10 (Release Notes):
- JEP 286: Local-Variable Type Inference with
var
- JEP 286: Local-Variable Type Inference with
- JDK 11 (Release Notes):
- JEP 323: Local-Variable Syntax for Lambda Parameters with
var
- JEP 323: Local-Variable Syntax for Lambda Parameters with
- JDK 12 (Release Notes):
- JEP 325: Switch Expressions (Preview)
- JDK 13 (Release Notes):
- JDK 14 (Release Notes):
- JDK 15 (Release Notes):
- JDK 16 (Release Notes):
- JDK 17 (Release Notes):
- JDK 18 (Release Notes):
- JEP 420: Pattern Matching for switch (Second Preview)
- JDK 19 (Release Notes):
- JDK 20 (Release Notes):
- JDK 21 (Release Notes):
- JDK 22:
- JEP 459: String Templates (Second Preview)
- more
(JDK 7,12-14) "switch
" Enhancements
- (JDK 7) Switch with String selector
- (JDK 12) JEP 325: Switch Expressions (Preview)
- (JDK 13) JEP 354: Switch Expressions (Second Preview)
- (JDK 14) JEP 361: Switch Expressions (Standard)
- (JDK 17) JEP 406: Pattern Matching for switch (Preview) [TODO]
- (JDK 18) JEP 420: Pattern Matching for switch (Second Preview) [TODO]
- (JDK 19) JEP 427: Pattern Matching for switch (Third Preview) [TODO]
- (JDK 20) JEP 433: Pattern Matching for switch (Fourth Preview) [TODO]
- (JDK 21) JEP 441: Pattern Matching for switch (Standard) [TODO]
(JDK 7) switch
with String
selector
Prior to JDK 7, a switch
works on integral primitive (byte
, short
, int
, char
; but NOT long
). It also works with enumerated types (Enum
) and primitive wrapper classes (Character
, Byte
, Short
, and Integer
).
From JDK 7, you can use a String
object as the selector. For example,
public class J7SwitchOnStringTest {
public static void main(String[] args) {
String day = "SAT";
switch (day) { // switch on String selector (JDK 7)
case "MON": case "TUE": case "WED": case "THU":
System.out.println("Working Day");
break;
case "FRI":
System.out.println("Thank God It's Friday");
break;
case "SAT": case "SUN":
System.out.println("Gone Fishing");
break;
default:
System.out.println("Invalid");
break; // recommended good defensive code
}
}
}
Notes:
String
's.equals()
method, which is case-sensitive, is used in comparison.switch
vs. nested-if
: An if-then-else statement can test expressions based on ranges of values or conditions (e.g.,num!=-1
,x+y<5
), whereas aswitch
statement tests expressions based only on a single integer, enumerated value, orString
object.- You probably should use an
Enum
, which is safer, in this example.
"switch
on String
" is handy in handling options specified in command-line arguments, which are String
s. For example,
/**
* This program accepts the following command-line options:
* -c|--create : create
* -v|--verbose : verbose
* -d|--debug : debug
* More than one options can be specified in any order.
*/
public class J7SwitchArgsTest {
public static void main(String[] args) {
boolean create = false;
boolean verbose = false;
boolean debug = false;
for (String arg : args) {
switch (arg) { // Switch on String selector (JDK 7)
case "-c": case "--create":
create = true;
break;
case "-v": case "--verbose":
verbose = true;
break;
case "-d": case "--debug":
debug = true;
break;
default:
throw new IllegalArgumentException("invalid option " + arg);
}
}
System.out.println("create: " + create);
System.out.println("verbose: " + verbose);
System.out.println("debug: " + debug);
}
}
(JDK 12, 13, 14) "switch
" expressions
"switch
expressions" started in JDK 12 (JEP 325) as a preview feature and continued to JDK 13 (JEP354) for second review. It is standardized in JDK 14 (JEP 361).
The original switch
statement (follows the C/C++) has several irregularities, such as:
- the default control flow behavior between switch labels (i.e., fall through without a
break
statement) switch
works only as a statement, not as an expression (i.e.,switch
does not return a value)- the default scoping in
switch
block (the whole block is treated as one scope)
New Arrow Labels "case L ->
" and New "yield
" Statement
- The original
switch
's label has the form of "case L:
" which fall through withoutbreak
. JDK 14 introduced arrow labels, in the form of "case L ->
", which does not fall thru to the next case andhence break
is not needed. switch
in arrow-label form can take an expression that evaluates to a value.- JDK 14 also introduced a new
yield
statement to produce a value.
For example:
enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
public class J14SwitchExprYieldTest {
public static void main(String[] args) {
Day day = Day.SUNDAY; // switch on String selector (From JDK 7)
int numLetters;
// Before JDK 14, switch is a statement without evaluated value
switch (day) {
case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break;
case TUESDAY: numLetters = 7; break;
case THURSDAY: case SATURDAY: numLetters = 8; break;
case WEDNESDAY: numLetters = 9; break;
default: // defensive code for enum
numLetters = 0;
throw new IllegalArgumentException("invalid day " + day);
}
System.out.println("1. Number of letters: " + numLetters);
// JDK 14 supports switch expression that evaluates to a value
numLetters = // Assign the new switch expression to a variable
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6; // single-line expression
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> { // braces {} needed for block in 'case -> L', local scope
System.out.println("invalid day " + day);
yield 0; // use "yield" to return a value in a block
}
};
System.out.println("2. Number of letters: " + numLetters);
// switch expression can also use the traditional "case L:" with yield
numLetters = switch (day) {
case MONDAY: case FRIDAY: case SUNDAY: yield 6;
case TUESDAY: yield 7;
case THURSDAY: case SATURDAY: yield 8;
case WEDNESDAY: yield 9;
default: // braces { } NOT needed in 'case: L'
System.out.println("invalid day " + day);
yield 0; // use "yield" to return a value in a block
};
System.out.println("3. Number of letters: " + numLetters);
}
}
Notes:
- Multiple labels are separated by commas in arrow labels "
case L ->
". - Body blocks must be enclosed in braces, with its scope.
(JDK 17,18,19,20,21) Pattern Matching for switch
[TODO]
(JDK 7) Binary Integer Literals with Prefix "0b" and Underscore (_
) in Numeric Literals
From JDK 7, you can express literal values in binary with prefix '0b' (zero-b) (or '0B') for integral types, similar to C/C++. Prior to JDK 7, you can only use octal literal values with prefix '0' (zero) or hexadecimal literal values with prefix '0x' (zero-x) or '0X'.
From JDK 7, you are also permitted to use underscore (_
) to break the digits to improve the readability but you must start and end with a digit. Underscores are allowed in fractional part for floating point numbers too. For examples,
int anInt1 = 0b01010000101000101101000010100010; // binary literal prefix with '0b' or '0B' int anInt2 = 0b0101_0000_1010_0010_1101_0000_1010_0010; // break digits with underscore for readability int anInt3 = 2_123_456; // break the digits with underscore byte aByte = (byte)0b0110_1101; // '0b' for int(32-bit), need to cast to byte(8-bit)/short(16-bit) short aShort = (short)0b0111_0101_0000_0101; long aLong = 0b1000_0101_0001_0110_1000_0101_0000_1010_0010_1101_0100_0101_1010_0001_0100_0101L; // long(64-bit) with suffix 'L' double aDouble = 3.1415_9265; // You can also use underscore on fractional part for readability float aFloat = 3.14_15_92_65f;
(JDK 7) Catching Multiple Exception
Types and Re-throwing Exception
s with Improved Type Checking
Prior to JDK 7, you need two catch
blocks to catch two exception types even though both perform identical task. For example,
try { ...... } catch(ClassNotFoundException ex) { ex.printStackTrace(); } catch(SQLException ex) { ex.printStackTrace(); // same actions }
From JDK 7, you could use one single catch
block to handle more than one exception types, with the exception types separated by a vertical bar (|
). For example,
try {
......
} catch(ClassNotFoundException|SQLException ex) {
ex.printStackTrace();
}
(JDK 7,9) Automatic Resource Management in try
-with-resources Statement
(Pre-JDK 7) try-catch-finally
Prior to JDK 7, we have the try-catch-finally
clause to handle exceptions. For example,
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
// Copy from one file to another file line by line.
// Pre-JDK 7 requires you to close the resources using a finally block.
public class PreJ7FileCopy {
public static void main(String[] args) {
BufferedReader src = null;
BufferedWriter dest = null;
try {
src = new BufferedReader(new FileReader("in.txt"));
dest = new BufferedWriter(new FileWriter("out.txt"));
String line;
while ((line = src.readLine()) != null) {
System.out.println(line);
dest.write(line);
dest.newLine(); // write a newline
}
} catch (IOException ex) {
ex.printStackTrace();
} finally { // always close the streams
try {
if (src != null)
src.close(); // close() throw IOException
if (dest != null)
dest.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
(JDK 7) New try
-with-resources Statement in the form of "try(resources)
"
JDK 7 introduces a try
-with-resources statement, which ensures that each of the resources in try(resources)
is properly closed at the end of the statement. This results in much cleaner codes.
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
// Copy from one file to another file line by line.
// JDK 7 has a try-with-resources statement, which ensures that
// each resource opened in try(resources) is closed at the end of the statement.
public class J7FileCopy {
public static void main(String[] args) {
try (BufferedReader src = new BufferedReader(new FileReader("in.txt"));
BufferedWriter dest = new BufferedWriter(new FileWriter("out.txt"))) { // JDK 7
String line;
while ((line = src.readLine()) != null) {
System.out.println(line);
dest.write(line);
dest.newLine();
}
} catch (IOException ex) {
ex.printStackTrace();
}
// src and dest automatically close.
// No need for finally to explicitly close the resources.
}
}
(JDK 9) try
-with-resource on final
or "effectively final
" variables
From JDK 9, if you already have a resource as a final
or "effectively final
" variable, you can use that variable in a try(resources)
clause without declaring a new variable. An "effectively final
" variable is one whose value is never changed after it is initialized. For example,
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
// Copy from one file to another file line by line.
// Java 7 has a try-with-resources statement, which ensures that
// each resource opened in try(resources) is closed at the end of the statement.
public class J9FileCopy {
public static void main(String[] args) throws IOException {
BufferedReader src = new BufferedReader(new FileReader("in.txt"));
BufferedWriter dest = new BufferedWriter(new FileWriter("out.txt"));
// before JDK 9
// try (BufferedReader src1 = src; BufferedWriter dest1 = dest) { // local variables only
try (src; dest) { // final or effectively final variables permitted from JDK 9
String line;
while ((line = src.readLine()) != null) {
System.out.println(line);
dest.write(line);
dest.newLine();
}
} catch (IOException ex) {
ex.printStackTrace();
}
// src and dest automatically close.
// No need for finally to explicitly close the resources.
}
}
(JDK 7,9) Improved Type Inference for Generic Instance Creation with the Diamond Operator <>
(JDK 7) New Diamond Operator <>
For example,
import java.util.*;
public class J7GenericTest {
public static void main(String[] args) {
// Pre-JDK 7
List<String> lst1 = new ArrayList<String>();
// JDK 7 supports limited type inference for generic instance creation with
// diamond operator <>
List<String> lst2 = new ArrayList<>();
lst1.add("Mon");
lst1.add("Tue");
lst2.add("Wed");
lst2.add("Thu");
for (String item : lst1)
System.out.println(item);
// Mon
// Tue
for (String item : lst2)
System.out.println(item);
// Wed
// Thu
List<String> lst3 = List.of("Fri", "Sat"); // JDK 9
System.out.println(lst3);
// [Fri, Sat]
System.out.println(Arrays.toString(lst3.toArray()));
// [Fri, Sat]
lst3.forEach(System.out::println); // JDK 8
// Fri
// Sat
}
}
(JDK 9) Diamond operator is allowed in anonymous inner classes
From JDK 9, as long as the inferred type is denotable, you can use the diamond operator <> when you create an anonymous inner class.
For example, [TODO]
(JDK 7,9) Simplified varargs
Method Declaration with @SafeVarargs
Annotation
(JDK 7) New @SafeVarargs
From JDK 7, you have the option of using @SafeVarargs
annotation to suppress the warning you get when compiling a method with a non-reifiable varargs parameter. This annotation should be used when the method ensures that only elements of the same type as the varargs parameter are stored in the varargs array.
Example [TODO]
(JDK 9) @SafeVargs
annotation is allowed on private
instance methods
In JDK 7, the @SafeVarargs
annotation can be applied only to methods that cannot be overridden. These include static
methods, final
instance methods. From JDK 9, @SafeVargs
annotation is allowed on private
instance methods.
(JDK 8,9) New methods in interface
Prior to JDK 8, only public
abstract
methods and public
static
final
variables are allowed inside an interface
.
(JDK 8) public
default
method and public
static
method
JDK 8 enhances interface
by introducing these new methods:
public
default
method (JDK 8)public
static
method (JDK 8)
For examples,
public interface MyJ8Interface {
// prior to JDK 8
String NAME = "peter"; // public static final
void sayHello(); // public abstract
// JDK 8 public default method - may be overridden
default void sayHi() { // public
System.out.print("(public default method) ");
System.out.println("hello, " + NAME);
}
// JDK 8 public static method - must invoke via interface name
static void sayGoodDay() { // public
System.out.print("(public static method) ");
System.out.println("good day, " + NAME);
}
}
public class MyJ8InterfaceImpl1 implements MyJ8Interface {
public void sayHello() {
System.out.print("(public abstract implementation) ");
System.out.println("hello, world");
}
public static void main(String[] args) {
MyJ8InterfaceImpl1 obj1 = new MyJ8InterfaceImpl1();
obj1.sayHello(); // run public abstract implementation
obj1.sayHi(); // run public default
MyJ8Interface.sayGoodDay(); // run public static via interface name
}
}
(public abstract implementation) hello, world (public default method) hello, peter (public static method) good day, peter
(JDK 9) private
method and private
static
method
JDK 9 introduces these new methods:
private
(instance) methodsprivate
static
methods: can be called by otherpublic|private
static
methods within the sameinterface
.
They are used as helper methods within the interface
to remove redundant codes.
See "Enhanced Interface, Lambda Expressions, Streams and Functional Programming" for more details.
(JDK 8) Lambda Expression
JDK 8 introduces lambda expressions, which provides a shorthand notation for creating an instance of an anonymous inner class implementing a functional interface (single-abstract-method interface). The new syntax is as follows:
(arg1, arg2,...) -> abstract-method-body;
// Return an instance implementing the functional interface
For examples,
[TODO]
This is a major update to support functional programming - started in JDK8 and continue into next few releases. See "Enhanced Interface, Lambda Expressions, Streams and Functional Programming".
(JDK 9) Java Module System
See "Java Module System".
(JDK 9) jshell
JDK 9 introduces a new tool called jshell
to support REPL (Read-Evaluate-Print-Loop). It is used to execute and test any Java constructs like class, interface, enum, object, statements etc.
This is NOT a language feature.
(JDK 10,11) var
- Type Inference
- (JDK 10) JEP 286: Local-Variable Type Inference with var
- (JDK 11) JEP 323: Local-Variable Syntax for Lambda Parameters with var
(JDK 10) Type Inference (var
) for Local-Variables with Initializers (JEP 286)
Prior to JDK 10, all local variable declarations required an explicit (manifest) type on the left-hand side. JDK 10 introduces a new keyword var
to extend type inference for declarations of local variables with initializers (JEP 286), while maintaining Java's commitment to static type safety.
For examples,
public class J10VarTest {
public static void main(String[] args) {
// inferred type from initialized value
var anInt = 88;
var aDouble = 55.66;
var msg = "hello";
// print type
System.out.println(msg.getClass().getName()); // for object
System.out.println(((Object)anInt).getClass().getName()); // for primitive (wrapper)
System.out.println(((Object)aDouble).getClass().getName());
}
}
java.lang.String java.lang.Integer java.lang.Double
"There is a certain amount of controversy over this feature. Some welcome the concision it enables; others fear that it deprives readers of important type information, impairing readability. And both groups are right. It can make code more readable by eliminating redundant information, and it can also make code less readable by eliding useful information. Another group worries that it will be overused, resulting in more bad Java code being written. This is also true, but it’s also likely to result in more good Java code being written. Like all features, it must be used with judgment. There’s no blanket rule for when it should and shouldn’t be used."
(JDK 11) Type Inference (var
) for Local-Variable for Lambda Parameters (JEP 323)
In JDK 11, var
is allowed when declaring the formal parameters of implicitly typed lambda expressions.
Read "Local Variable Type Inference Style Guidelines" @ https://openjdk.org/projects/amber/guides/lvti-style-guide.
(JDK 11) Launch Single-File Source-Code Programs (JEP 330)
Single-file programs, where the whole program is kept in a single source file, are common in learning Java.
From JDK 11, you can run a single-file program directly from the source-code with the java launcher, without explicitly compiling the source (via javac
).
For example, you can launch the Hello.java
directly as follows:
java Hello.java
Notes:
- This is applicable to single-file source only.
- No external
.class
file will be generated. - It compiles the source in the memory, and executes directly from the memory.
- This feature is introduced for beginners to learn Java, and for professionals to test a Java feature.
- The filename and classname need not be the same.
This is NOT a language feature.
(JDK 13,14,15) Text Blocks
- (JDK 13) JEP 355: Text Blocks (Preview)
- (JDK 14) JEP 368: Text Blocks (Second Preview)
- (JDK 15) JEP 378: Text Blocks (Standard)
Read "Programmer's Guide To Text Blocks" @ https://docs.oracle.com/en/java/javase/14/text-blocks/index.html.
(JDK 13) JEP 355: Text Blocks (Preview)
In earlier releases of the JDK, embedding multi-line code snippets required a tangled mess of explicit new line, string concatenations, and delimiters. JDK 13 proposes to support multi-line string literal (or text block) (JEP 355) that eliminates most of these obstructions, and automatically formats the multi-line string in a predictable way.
A multi-line text block is delimited by a pair of triple double quotes, i.e., """ ... """
, which may span over multiple lines. The object produced from a text block is a java.lang.String
with the same characteristics as a traditional double quoted string.
For example,
public class J15TextBlock {
public static void main(String[] args) {
String htmlStr = """
<html>
<head>
<title>Hello</title>
</head>
<body>
<p>"Hello, world!"</p>
</body>
</html>
"""; // A multi-line text block delimited by """......"""
// Leading incidental whitespaces and trailing whitespaces are stripped
System.out.println(htmlStr); // automatically formatted (see output below)
// variable htmlStr is the same as:
htmlStr =
"<html>\n" +
" <head>\n" +
" <title>Hello</title>\n" +
" </head>\n" +
" <body>\n" +
" <p>\"Hello, world!\"</p>\n" + // need escape sequence for "
" </body>\n" +
"</html>\n";
}
}
<html> <head> <title>Hello</title> </head> <body> <p>"Hello, world!"</p> </body> </html>
Notes:
- Text block must begin with
"""
on the first line (by itself only). The content starts from the next line as in the above example.String s1 = """testing"""; // error: no single line text block String s2 = """testing 1 testing 2 """; // error: the opening """ must be on its own line String s3 = """ testing 1 testing 2 """; // OK String s4 = "testing 1\n" + "testing 2\n"; // s3 is the same as s4 System.out.println(s3.equals(s4)); //true String s5 = """ testing 1 testing 2"""; // OK, no trailing newline String s6 = "testing 1\n" + "testing 2"; // s5 is the same as s6 System.out.println(s5.equals(s6)); //true System.out.println(s5.equals(s4)); //false
- Text block is automatically formatted by removing incidental leading whitespaces. The entire contents of the text block is shifted to the left until the line with the least leading white space has no leading white space. A text block can opt out of incidental white space stripping by positioning the closing delimiter in the first character position of a source line, e.g.,
String s7 = """ testing 1 testing 2 """; // testing 1 // testing 2 String s8 = """ testing 1 testing 2 """; // All whitespaces preserved // testing 1 // testing 2 String s9 = """ testing 1 testing 2"""; // no trailing newline //testing 1 //testing 2
- Trailing whitespaces are stripped.
- There is no need to use escape sequence for double-quote inside a text block.
- For most multi-line strings, place the opening delimiter at the right end of the previous line, and place the closing delimiter on its own line, at the left margin of the text block. The contents shall start at 1-2 indents, like the variable
html
in the first example.
(JDK 14) JEP 368: New Escape Sequences (Preview)
JDK 14 adds two new escape sequences:
\<newline>
: a backslash (\) at the end of the line suppresses the newline, i.e., continue the next line.\s
: to indicate a single space, can be used to create trailing whitespaces.
For example,
String s10 = """ testing 1 \ testing 2 \ """; //testing 1 testing 2 String s11 = """ testing 1 \s= testing 2 \s= """; // with trailing whitespaces //testing 1 = //testing 2 =
(JDK 14,15,16) Pattern Matching for instanceof
- JEP 305: Pattern Matching for
instanceof
(Preview) - JEP 375: Pattern Matching for
instanceof
(Second Preview) - JEP 394: Pattern Matching for
instanceof
(Standard)
(JDK 14) JEP 305: Pattern Matching for instanceof
(Preview)
JDK 14 has introduced pattern matching for instanceof
with the aim of eliminating boilerplate code.
For example, before this feature, we wrote:
if (obj instanceof String) { String str = (String) obj; int len = str.length(); // ... }
This is a very common pattern in Java. Whenever we check if a variable is a certain type, we almost always follow it with a cast to that type.
Now, we can write:
if (obj instanceof String str) { int len = str.length(); // ... }
In future releases, Java is going to come up with pattern matching for other constructs such as a switch.
JDK 15, 16 [MORE]
(JDK 14,15,16) Record
JDK 14 introduces record (JEP 359) as a preview feature, with a second review in JDK 15 (JEP 384), and standardized in JDK 16 (JEP 395).
(JDK 14) JEP 359: Records (Preview)
A common complaint of Java is there is too much boilerplate codes. Records were introduced to reduce boilerplate code in immutable data model. For example, a data model for a User
with an id
and name
can be simply defined as:
public record User(int id, String name) { }
A new keyword "record
" is introduced. This simple declaration will automatically add a canonical constructor with matching parameters, getters, equals()
, hashCode()
and toString()
methods. Record is a restricted form of class, like enum
. Records are immutable.
import java.util.Objects;
// Before "record"
public class User {
private final int id; // immutable
private final String name;
public User(int id, String name) { // constructor
this.id = id;
this.name = name;
}
public int getId() { // getters
return this.id;
}
public String getName() {
return this.name;
}
@Override public boolean equals(Object obj) {
if (!(obj instanceof User)) return false;
var other = (User)obj;
return other.id == this.id && other.name.equals(this.name);
}
@Override public int hashCode() {
return Objects.hash(id, name);
}
}
The above class can be defined in one line with record:
public record User(int id, String name) {}
public class J16RecordUserTest {
public static void main(String[] args) {
User user = new User(123, "Tan Ah Teck"); // canonical constructor
System.out.println(user.id()); // getters
System.out.println(user.name());
System.out.println(user); // toString()
}
}
You are allow to override the default implementation of the implicitly-defined methods, for example,
public record Student(int id, String name) {
public Student(int id, String name) { // canonical constructor
if (id < 0)
throw new IllegalArgumentException("id=" + id + " < 0");
this.id = id;
this.name = name;
}
@Override
public int id() {
return 0;
}
@Override
public String toString() {
return "This is a Student";
}
public static void main(String[] args) { // test driver
var s1 = new Student(0, "Peter");
System.out.println(s1);
//This is a Student
var s2 = new Student(-9, "Paul");
//Exception in thread "main" java.lang.IllegalArgumentException: id=-9 < 0
}
}
You can also add more methods to record, for example,
public record Rectangle (double length, double width) {
public double area() {
return length * width;
}
public static void main(String[] args) { // test driver
var r1 = new Rectangle(2.2, 1.1);
System.out.println(r1);
//Rectangle[length=2.2, width=1.1]
System.out.println(r1.area());
//2.4200000000000004
}
}
Take note that records do have some restrictions, e.g., the fields are always final
, they cannot be declared abstract
, and they cannot use native methods.
Note that the introduction of classes in the java.lang
package is rare but necessary from time to time, such as Enum
in JDK 5, Module
in JDK 9, and Record
in JDK 14 (preview).
Constructor for the record class
A record without any constructor is automatically given a canonical constructor that assigns all the private
fields to the corresponding arguments of the new expression which instantiated the record. (In contrast, a class without any constructor is automatically given a default constructor that does nothing.)
Compact Canonical Constructor
You can override the constructor in the usual way. There is also a compact way, for example,
public record Time(int hour, int minute, int second) {
// Compact form to override the canonical constructor to do validation
public Time { // The parameters (int hour, int minute, int second) are declared implicitly
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59)
throw new IllegalArgumentException(String.format("%02d:%02d:%02d", hour, minute, second));
// At the end of the constructor, implicitly run:
// this.hour = hour; this.minute = minute; this.second = second;
}
public static void main(String[] args) {
var t1 = new Time(23, 59, 59);
System.out.println(t1);
//Time[hour=23, minute=59, second=59]
var t2 = new Time(23, 60, 59);
//Exception in thread "main" java.lang.IllegalArgumentException: 23:60:59
System.out.println(t2);
}
}
JDK 15, 16 [MORE]
(JDK 21,22) String Templates
Embedded Expressions \{expr}
String templates complement Java's existing string literals and text blocks by coupling literal text with embedded expressions and template processors to produce specialized results. The embedded expression is in the form of \{expr}
.
The STR
String Template Processor
For example,
//import static java.lang.StringTemplate.STR; // not needed, imported automatically String name = "Peter"; String greeting = STR."Hello \{name}"; System.out.println(greeting); String title = "My Hello"; String message = "hello, world"; String html = STR.""" <html>- <head> <title>\{title}</title> </head> <body> <p>"\{message}"</p> </body> </html> """; System.out.println(html);
STR (String Template Processor) performs string interpolation by replacing each embedded expression in the template with the (stringified) value of that expression. The result of evaluating a template expression which uses STR is a String
.
The embedded expressions are evaluated and the results are converted to String
. For example,
int x = 8, y = 9; String result = STR."\{2*x} + \{3*y} = \{2*x + 3*y}"; System.out.println(result); //16 + 27 = 43 System.out.println(STR."\{x++}, \{x++}, \{x++}"); // evaluate from left to right //8, 9, 10 System.out.println(STR."A random number: \{Math.random()}"); // can invoke method System.out.println(STR."\{x} is \{(x % 2 == 0 ? "even" : "odd")}"); // escape not needed for double-quote //11 is odd
The FMT
Format Template Processor
FMT is another template processor. FMT is like STR in that it performs interpolation, but it also interprets format specifiers which appear to the left of embedded expressions. For example,
import static java.util.FormatProcessor.FMT; int hour = 23; int minute = 1; int second = 5; String time = FMT."The time is %02d\{hour}:%02d\{minute}:%02d\{second}"; System.out.println(time); //The time is 23:01:05
String Template is a preview feature in JDK 21. You need to compile and run the program with "--enable-preview -source 21
" flags.
JDK 22 [MORE]