Review of Java Features From 11 to 17
From version 11 to 17, Java has introduced several major features, including local variable type inference, text blocks, records, and sealed classes. In this blog post, we will introduce each of these features.
New methods of String class (Java SE 11)
Java SE 11 introduced several new methods to the String class. Here are some of the most notable ones:
isBlank()
: This method returnstrue
if the string is empty or contains only white space, andfalse
otherwise.strip()
: This method returns a copy of the string with all leading and trailing white space removed.stripLeading()
: This method returns a copy of the string with all leading white space removed.stripTrailing()
: This method returns a copy of the string with all trailing white space removed.repeat(int count)
: This method returns a new string composed of the original string repeatedcount
times.
These new methods can be useful for performing common string operations more easily and efficiently. Here’s an example:
public class NewString {
public static void main(String[] args) {
// isBlank() method
System.out.println(" ".isBlank()); // true
String string = " omernaci ";
System.out.println(string.isBlank()); // false
String string1 = "";
System.out.println(string1.isBlank()); // true
// repeat(int count) method
String repeatedString = "omer".repeat(2);
System.out.println(repeatedString); // prints omeromer
// strip(), stripLeading() and stripTrailing() methods
String str = " JD ";
System.out.print("Start");
System.out.print(str.strip());
System.out.println("End");
System.out.print("Start");
System.out.print(str.stripLeading());
System.out.println("End");
System.out.print("Start");
System.out.print(str.stripTrailing());
System.out.println("End");
}
}
Switch expressions (Java SE 14, JEP-361)
Switch expressions are a new feature introduced in Java SE 14 that allow you to write more concise and expressive switch statements. In traditional switch statements, you could only use constants or enumerated values as cases, and you had to use a break
statement to avoid falling through to subsequent cases. With switch expressions, you can use any data type as a case, and you can use a yield
statement to specify the value that the expression should return.
public class SwitchExpressions {
public static void main(String[] args) {
var month = 4;
oldStyleSwitchExpression(month);
newStyleSwitchExpression(month);
}
private static void newStyleSwitchExpression(int month) {
String season = switch (month) {
case 12, 1, 2 -> "Winter";
case 3, 4, 5 -> "Spring";
case 6, 7, 8 -> "Summer";
case 9, 10, 11 -> "Autumn";
default -> "Invalid Month";
};
System.out.println("April is in the " + season + ".");
}
private static void oldStyleSwitchExpression(int month) {
String season;
switch (month) {
case 12:
case 1:
case 2:
season = "Winter";
break;
case 3:
case 4:
case 5:
season = "Spring";
break;
case 6:
case 7:
case 8:
season = "Summer";
break;
case 9:
case 10:
case 11:
season = "Autumn";
break;
default:
season = "Invalid Month";
}
System.out.println("April is in the " + season + ".");
}
}
In this example, we use a switch expression to assign a month number to the month
variable. We use the ->
operator to separate each case from its value, and we use the default
keyword to specify the default value if no case matches.
Text Blocks (Java SE 15, JEP-378)
Text blocks are a new feature introduced in Java SE 15, as part of JEP-378, that allow you to write multi-line strings and blocks of text without needing to escape special characters or use concatenation.
In Java SE 15 and later versions, you can create a text block by enclosing a string of text in triple quotes ("""
). Here's an example:
public class TextBlocks {
public static void main(String[] args) {
// Using a literal string
String literalString = "omernaci";
// Using a text block
String textBlockString = """
omernaci""";
System.out.println(literalString.equals(textBlockString)); // true
// ERROR
/* String name = """Omer Soydemir"""; */
// ERROR
/* String colors = """red
green
blue
"""; */
// Multiline example
String oldMultiline = "In the sad moonlight, \n" +
"she clasped him by the neck, \n" +
"and laid her face upon his breast. ";
System.out.println(oldMultiline);
String newMultiline = """
In the sad moonlight,
she clasped him by the neck,
and laid her face upon his breast.
""";
System.out.println(newMultiline);
// JSON object with text blocks
System.out.println(getResultAsJson());
// SQL blocks
System.out.println(getSQLQuery());
}
private static String getSQLQuery() {
return """
SELECT *
FROM customers
WHERE first_name = 'Joe'
ORDER BY last_name ASC;
""";
}
private static String getResultAsJson() {
return """
{
"id": 1235,
"title": "Example Product",
"description": "A product which is nothing like apple",
"price": 549
}
""";
}
}
Pattern matching for instance of (Java SE 16, JEP-394)
Pattern matching for instanceof
is a feature introduced in Java 16 that allows you to use pattern matching syntax to extract a castable variable from an object and perform an action based on its type.
Here’s an example that demonstrates how to use pattern matching for instanceof
:
public class InstanceOfSample {
public static void main(String[] args) {
Object obj1 = "omernaci";
Object obj2 = 42;
Object obj3 = new Object();
processObject(obj1); // Output: Input is a string: Hello
processObject(obj2); // Output: Input is an integer: 42
processObject(obj3); // Output: Input is of unknown type
}
public static void processObject(Object obj) {
if (obj instanceof String str) {
System.out.println("Input is a string: " + str);
} else if (obj instanceof Integer i) {
System.out.println("Input is an integer: " + i);
} else {
System.out.println("Input is of unknown type");
}
}
}
In this example, the processObject()
method takes an Object
parameter obj
and uses pattern matching for instanceof
to extract a variable of type String
or Integer
if obj
is an instance of either of those types.
If obj
is an instance of String
, the pattern variable str
is created and assigned the value of obj
, and the code block within the if
statement is executed. Similarly, if obj
is an instance of Integer
, the pattern variable i
is created and assigned the value of obj
, and the code block within the second if
statement is executed. If obj
is not an instance of either String
or Integer
, the else
block is executed.
Using pattern matching for instanceof
allows you to extract a castable variable from an object and perform an action based on its type, without having to cast the object to a specific type manually. This can make your code more concise and readable, especially when dealing with complex object hierarchies.
Records (Java SE 16, JEP-395)
Records are a new feature introduced in Java 16 that provide a concise way to declare simple classes that are primarily used to store data. A record is similar to a regular Java class, but with some key differences.
When you declare a record in Java, you get a compact class that provides default implementations of common methods like constructors, equals()
, hashCode()
, and toString()
. These methods are generated based on the record's state description, which is defined by its record components.
Here’s an example of a simple record definition in Java:
public class RecordSample {
public static class Employee {
private Long id;
private String department;
private BigDecimal salary;
public Employee(Long id, String department, BigDecimal salary) {
this.id = id;
this.department = department;
this.salary = salary;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public BigDecimal getSalary() {
return salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
}
public record EmployeeRecord(Long id, String department, BigDecimal salary) {
}
public static void main(String[] args) {
Employee employee = new Employee(1L, "Data Science", BigDecimal.ZERO);
EmployeeRecord employeeRecord = new EmployeeRecord(2L, "IT", BigDecimal.ZERO);
Arrays.stream(EmployeeRecord.class.getRecordComponents()).forEach(System.out::println);
}
}
Sealed classes (Java SE 17, JEP-409)
The sealed
keyword in Java is used to declare a class or an interface as sealed, which means that it restricts the hierarchy of inheritance for its subclasses or implementations.
When a class or an interface is declared as sealed, you can specify a finite set of permitted subclasses or implementations using the permits
keyword. Only those subclasses or implementations explicitly listed after permits
can extend or implement the sealed class or interface.
Here’s an example of a sealed class in Java:
public sealed class Shape permits Circle, Rectangle {
// common properties and methods of shapes
}
final class Circle extends Shape {
// properties and methods specific to circles
}
final class Rectangle extends Shape {
// properties and methods specific to rectangles
}
In this example, the Shape
class is declared as sealed
using the permits
keyword, which means that only the Circle
and Rectangle
classes are allowed to extend it. Both Circle
and Rectangle
are declared as final
, which means they cannot be further subclassed.
By using sealed classes, you can restrict the hierarchy of inheritance for your classes, ensuring that only specific subclasses can be created to represent different types of shapes. This can help prevent the creation of unexpected subclasses that could violate the design constraints of your code.
Github repo : https://github.com/omernaci/medium-samples/tree/master/features