top of page
hand-businesswoman-touching-hand-artificial-intelligence-meaning-technology-connection-go-

Java Language Updates: Java 17

Java 17 comes with various new features that are inclined to improve developer productivity and program efficiency. In this article, we will explore some of the most essential developer’s friendly Java 17 features.


Record Class

Records provide a concise way to define simple classes primarily intended for encapsulating data.

Once we create a Record class, Java internally defines final variables and their getter methods in addition to the class-level methods such as toString, hashCode, and equals.



public record StudentRecord(String name, String email, int id){    }

// Initialize the record.
StudentRecord student = new StudentRecord ("Meenu", "m.juneja@gmail.com", 25);
 // get the properties
 System.out.println(student.email());
 System.out.println(student.toString());

Instead of getEmail() that we use traditionally, we just use email() while calling Record methods.


We cannot set the value of a property of Record once initialized. All the variables are final. This means Records are immutable.


A record class is implicitly final, so you cannot explicitly extend a record class.

All Records implicitly extends the Record class, therefore can’t extend any other class. (No multiple inheritance in Java allowed)

You can declare a record class that implements one or more interfaces, for example let’s assume SchoolRules is an Interface.

public interface SchoolRules {    //interface methods}
public record StudentRecord(String name, String email, int id) implements SchoolRules {}

Record Constructor

Record declares a default constructor with all parameters.

 public StudentRecord(String name, String email, int id) {
        this.name = name;
        this.email = email;
        this.id = id;
    }

We can write custom logic in constructors.

public StudentRecord (String name, String email, int id) {
        this.name = name;
        this.email = email;
        this.id = id;
        if (id < 1) {
            throw new IllegalArgumentException("Student Id can not be less than 1");
        }
    }

The above canonical constructor with custom logic can be re-written in compact form as:

public StudentRecord {
        if (id < 1) {
            throw new IllegalArgumentException("Student Id can not be less than 1");
        }
    }

You can declare static fields, static initializers, and static methods in a record class, and they behave as they would in a normal class, for example:

public record GoldenRatioRectangle(double length, double width) {
    // Static field
    static double goldenRatio;
    // Static initializer
    static {
        goldenRatio = (1 + Math.sqrt(5)) / 2;
    }
    // Static method
    public static GoldenRatioRectangle createGoldenRectangle(double width) {
        return new GoldenRatioRectangle(width, width * goldenRatio);
    }
}
public class GoldenRatioRectangleClass {
    public static void main(String[] arg) {
        System.out.println(GoldenRatioRectangle.createGoldenRectangle(2.0));
    }
}

Pattern Matching for Switch Expressions and Statements

Generally, the switch statement selects one of many code blocks to be executed, depending upon value of switch expression.

Switch Expressions

Like all expressions, switch expressions evaluate to a single value and can be used in statements. They may contain "case L ->" labels that eliminate the need for break statements to prevent fall through.

You can use a yield statement to specify the value of a switch expression. Yield statement returns the value and finishes the switch execution.


public class YieldTest {
    public static void main(String[] args) {
        System.out.println(getDayType("Monday"));
        System.out.println(getDayType("Saturday"));
        System.out.println(getDayType(""));
    }

    public static String getDayType(String day) {
        return switch (day) {
            // we can use block statements to return a value using yield after
            // executing other statements
            case "Monday", "Tuesday", "Wednesday","Thursday", "Friday" -> {
                System.out.println("In Weekdays");
                yield "Weekday";
            }
            case "Saturday", "Sunday" -> {
                System.out.println("In Weekends");
                yield "Weekend";
            }
            default -> throw new IllegalStateException("Invalid day: " + day);
        };
    }


}

Output:

In Weekdays

Weekday

In Weekends

Weekend

Exception in thread "main" java.lang.IllegalStateException: Invalid day: 


In earlier releases, the selector expression must evaluate to a number, string or Enum constant, and case labels must be constants.

However, in Java 17 release, the selector expression can be of any type, and case labels can have patterns. 

Switch statement or expression can test whether its selector expression matches a pattern.

These changes aim to simplify the syntax, improve readability and provide developers with more flexibility in handling complex scenarios.

We can use pattern matching in the switch statement to directly return the day of the week’s name based on the input integer. This reduces the need for temporary variables and results in more concise code.

public String getDayOfWeekName(int dayOfWeek) {    
	return switch (dayOfWeek) {        
		case 1 -> "Sunday";        
		case 2 -> "Monday";        
		case 3 -> "Tuesday";        
		case 4 -> "Wednesday";        
		case 5 -> "Thursday";        
		case 6 -> "Friday";        
		case 7 -> "Saturday";        
		default -> "Unknown";    
	};
}

Following code calculates the perimeter of certain shapes from Pattern Matching of instanceOf:


interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
public class InstanceOFPatternMatching {
    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        return switch (shape) {
            case Rectangle r -> 2 * r.length() + 2 * r.width();
            case Circle c    -> 2 * c.radius() * Math.PI;
            default          -> throw new IllegalArgumentException("Unrecognized shape");
        };
    }

}

Below example shows the summary of types that are allowed in a case label:

public enum DayOfWeek {    
	SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}

public record StudentRecord(String name, String email, int id){}

public class Employee {

    String dept;
    String name;

    public void setDept(String dept) {
        this.dept = dept;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDept(){
        return dept;
    }
}


public class TypeOfObjAllowed {
    public String typeOfObj(Object obj) {
        return switch (obj) {
            case null -> "NULL value";
            case String str -> "It's a String";
            case DayOfWeek d -> "Enum Type";
            case StudentRecord s -> "Records Type";
            case int[] ar -> "Array Type";
            case Employee e when e.getName().equals("joy") -> "Conditional Statement";
            default -> "Unknown";
        };
    }

}

}

Text Blocks

Text block is an improvement on formatting String variables. We can write a String that spans through several lines as regular text.

We used special syntax for opening and closing quotes: """.

There are some rules that we need to abide by when using a text block. We need to make sure that we put a new line after our opening quotes, or our compiler will throw an error.

A text block can be used in place of a string literal to improve the readability and clarity of the code. This primarily occurs when a string literal is used to represent a multi-line string. In this case there is considerable clutter from quotation marks, newline escapes, and concatenation operators:

Using text blocks removes much of the clutter:


public class TestTextBlocks {
    public static void main(String[] args) {
        // ORIGINAL
        String message = "'The time has come,' the Walrus said,\n" +
                "'To talk of many things:\n" +
                "Of shoes -- and ships -- and sealing-wax --\n" +
                "Of cabbages -- and kings --\n" +
                "And why the sea is boiling hot --\n" +
                "And whether pigs have wings.'\n";

        // BETTER
        String newMessage = """
                'The time has come,' the Walrus said,
                'To talk of many things:
                Of shoes -- and ships -- and sealing-wax --
                Of cabbages -- and kings --
                And why the sea is boiling hot --
                And whether pigs have wings.'
                """;


    }
}
 }}

Local Variable Type Inference

You can declare local variables with non-null initializers with the var identifier, which can help you write code that’s easier to read.

var is a reserved type name, not a keyword, which means that existing code that uses var as a variable, method, or package name is not affected. However, code that uses var as a class or interface name is affected and the class or interface needs to be renamed.


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LocalVarTypeInference {
    public static void main(String[] args) {
        var list = new ArrayList<String>();    // infers ArrayList<String>
        var stream = list.stream();            // infers Stream<String>



        List<String> myList = Arrays.asList("a", "b", "c");
        for (var element : myList) {
            // infers String
            System.out.println(element);
        }

    }
}

Sealed Classes

Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. By sealing a class, you can specify which classes are permitted to extend it and prevent any other arbitrary class from doing so.

To seal a class, add the sealed modifier to its declaration. Then, after any extends and implements clauses, add the permits clause. This clause specifies the classes that may extend the sealed class.


public sealed class Engine permits Car, Motorcyle, Truck {}

public final class Car extends Engine {    public int noOfSeats;}

public sealed class Motorcyle extends Engine permits Scooter {
    public String color;
}

Motorcyle has a further subclass, Scooter:

public final class Scooter extends Motorcyle {
    //relevant code
}

Truck is a non-sealed class.


public non-sealed class Truck extends Engine {
    public double height;
}

All permitted sub classes must have exactly one of the following modifiers to describe how it continues the sealing initiated by its superclass:

  • final: Cannot be extended further

  • sealed: Can only be extended by its permitted subclasses

  • non-sealed: Can be extended by unknown subclasses; a sealed class cannot prevent its permitted subclasses from doing this

9 views0 comments

Commentaires

Noté 0 étoile sur 5.
Pas encore de note

Ajouter une note
bottom of page