How to use switch expressions in Java 17

In Java 17, switch expressions provide a more concise and streamlined way to handle conditional logic. This feature was introduced in Java 12 as a preview and made a standard feature in Java 14. Java 17, being a Long-Term Support version, includes this feature as well.

Let me guide you through how to use them.


What Are Switch Expressions?

Switch expressions allow you to:

  1. Return values directly from a switch block (as an expression).
  2. Use concise syntax with the arrow -> syntax.
  3. Prevent fall-through by removing the need for explicit break statements.
  4. Handle multiple case labels compactly.

Switch Expression Syntax

Basic Syntax

switch (expression) {
    case value1 -> result1;
    case value2 -> result2;
    default -> defaultResult;
}
  1. Use -> for expression forms.
  2. A default case is mandatory unless all possible values are handled.
  3. The switch expression evaluates to a single value, which can be assigned to a variable.

Examples

1. Assigning a Value with Switch Expression

package org.kodejava.basic;

public class SwitchExpressionExample {
    public static void main(String[] args) {
        String day = "MONDAY";

        int dayNumber = switch (day) {
            case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> 1;
            case "SATURDAY", "SUNDAY" -> 2;
            default -> throw new IllegalArgumentException("Invalid day: " + day);
        };

        System.out.println("Day Group: " + dayNumber);
    }
}
  • Explanation:
    • Multiple case labels like "MONDAY", "TUESDAY" are handled via commas.
    • Default throws an exception if the input doesn’t match any case.

2. Block Syntax with yield

For cases where a more complex computation is needed, you can use a code block and yield to return a value.

package org.kodejava.basic;

public class SwitchExpressionWithYieldExample {
    public static void main(String[] args) {
        String grade = "B";

        String message = switch (grade) {
            case "A" -> "Excellent!";
            case "B" -> {
                int score = 85;
                yield "Good job! Your score is " + score;
            }
            case "C" -> "Passed.";
            default -> {
                yield "Invalid grade.";
            }
        };

        System.out.println(message);
    }
}
  • Explanation:
    • Use {} to enclose a block, and yield to specify the value to return.

3. Enhanced Switch with Enums

Switch expressions work great with enums, promoting type safety and readability.

package org.kodejava.basic;

public class SwitchWithEnums {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[] args) {
        Day today = Day.FRIDAY;

        String dayType = switch (today) {
            case SATURDAY, SUNDAY -> "Weekend";
            case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
        };

        System.out.println("Today is a " + dayType);
    }
}
  • Explanation:
    • Using enums eliminates the need for default because all cases are covered.

4. Using Switch in a Method

You can use switch expressions for cleaner and more concise methods.

package org.kodejava.basic;

public class SwitchInMethodExample {
    public static void main(String[] args) {
        System.out.println(getSeason(3)); // Output: Spring
    }

    static String getSeason(int month) {
        return switch (month) {
            case 12, 1, 2 -> "Winter";
            case 3, 4, 5 -> "Spring";
            case 6, 7, 8 -> "Summer";
            case 9, 10, 11 -> "Autumn";
            default -> throw new IllegalArgumentException("Invalid month: " + month);
        };
    }
}
  • Explanation:
    • No break is needed, as the return value is implicit in switch expressions.

Key Features to Remember

  1. No need for break statements.
  2. Use -> for one-liner cases.
  3. Use yield to return values from block-style cases.
  4. Works well with var for type inference.

Switch expressions simplify many patterns while keeping your code readable and concise!

A basic understanding of Java Classes and Interfaces

Java classes and interfaces are essential building blocks in Java programming. Here’s a simple explanation of both:


Java Classes

A class in Java is a blueprint or template used to create objects (instances). It contains:

  • Fields (Instance Variables): To store the state of objects.
  • Methods: To define behaviors or functionalities of the objects.
  • Constructors: To initialize objects.

Key Characteristics of a Class:

  1. It can extend (inherit from) another class (single inheritance).
  2. It can implement multiple interfaces.
  3. Commonly used for defining real-world entities with their properties and behaviors.

Example of a Class:

public class Animal {
    // Fields
    private String name;
    private int age;

    // Constructor
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Method
    public void speak() {
        System.out.println(name + " says hello!");
    }

    // Getter
    public String getName() {
        return name;
    }
}

How to use a class:

public class Main {
    public static void main(String[] args) {
        Animal dog = new Animal("Buddy", 3);
        dog.speak();  // Output: Buddy says hello!
    }
}

Java Interfaces

An interface in Java is a contract that defines a set of methods that a class must implement. Interfaces do not provide implementation but only the method declarations (method signatures).

Key Characteristics of Interfaces:

  1. A class that implements an interface must provide concrete implementations for all of its methods.
  2. A class can implement multiple interfaces (unlike inheritance where a class can extend only one class).
  3. Interfaces in Java 8+ can have:
    • Default Methods: Methods with a default implementation.
    • Static Methods: Methods that belong to the interface and can be invoked without an instance.

Example of an Interface:

public interface AnimalBehavior {
    void eat();  // Abstract method
    void sleep();
}

Implementing an Interface:

public class Dog implements AnimalBehavior {
    @Override
    public void eat() {
        System.out.println("The dog is eating.");
    }

    @Override
    public void sleep() {
        System.out.println("The dog is sleeping.");
    }
}

How to use the class with interface:

public class Main {
    public static void main(String[] args) {
        AnimalBehavior myDog = new Dog();
        myDog.eat();     // Output: The dog is eating.
        myDog.sleep();   // Output: The dog is sleeping.
    }
}

Differences Between Classes and Interfaces

Feature Class Interface
Purpose Blueprint for creating objects. Contract defining behavior (method signatures).
Inheritance Supports single inheritance. Can be implemented by multiple classes.
Access Modifiers Methods can have different access modifiers. Methods are public by default (abstract).
Implementation Contains method implementations. No method implementations (except default/static in Java 8+).
Usage Used for defining states and behaviors. Provides abstraction and enforces contract.

Summary:

  • Use classes to define what something is and its behavior.
  • Use interfaces to define what something can do (define a contract for behavior).

How to create cleaner code with type inference in Java 10

Type inference was introduced in Java 10 with the new var keyword, enabling developers to declare local variables without explicitly specifying their type. This feature can help create cleaner, more concise code by reducing boilerplate, though it should be used judiciously to maintain code readability.

Here’s a guide on how to use type inference effectively and write cleaner code in Java 10 and later:


1. Use var for Local Variables

The var keyword allows you to declare local variables without explicitly stating their type. The compiler infers the type based on the expression assigned to the variable. Here’s how it works:

Example:

var message = "Hello, World!"; // Compiler infers this as String
var count = 42;                // Compiler infers this as int
var list = new ArrayList<String>(); // Compiler infers this as ArrayList<String>

System.out.println(message);  // Hello, World!
System.out.println(count);    // 42

Benefits:

  • Eliminates redundancy. For instance:
List<String> list = new ArrayList<>();

becomes:

var list = new ArrayList<String>();

2. Use var in Loops

In for-each loops and traditional for-loops, var can simplify the code:

Example:

var numbers = List.of(1, 2, 3, 4, 5);
for (var num : numbers) {
    System.out.println(num); // Iterates through the numbers
}

Benefits:

  • Avoids unnecessary type declarations while maintaining readability.

3. Use var with Streams and Lambdas

var integrates well with Java Streams and Lambda expressions to reduce verbosity:

Example:

var numbers = List.of(1, 2, 3, 4, 5);
var result = numbers.stream()
                    .filter(n -> n % 2 == 0)
                    .map(n -> n * 2)
                    .toList();

System.out.println(result); // [4, 8]

When working with complex streams, var can make code shorter and easier to follow.


4. Restrictions on var

While var is versatile, there are some limitations and rules:

  • Only for Local Variables: var can only be used for local variables, loop variables, and indexes, not for class fields, method parameters, or return types.
  • Compiler Must Infer Type: You must assign a value to a var. For example, the following won’t work:
var uninitialized; // Error: cannot use 'var' without initializer
  • Anonymous Classes: Avoid overuse with anonymous classes to maintain clarity.

5. Maintain Readability

While var can simplify code, readability should always be a priority. Overusing var can obscure the code’s intent, especially when dealing with complex types:

Example of Overuse:

var map = new HashMap<List<String>, Set<Integer>>(); // Hard to understand

In such cases, it’s better to use explicit types.


6. Good Practices

  • Use var for Obvious Types:
var name = "John Doe"; // Obviously String
  • Avoid var for Ambiguous Types:
// Original:
var data = performOperation(); // What is the return type?
// Better:
List<String> data = performOperation();
  • Avoid Excessive Chaining:

    Using var with complex chains can make debugging harder. Be explicit when needed.


7. Refactoring Example

Here’s how you can refactor code for better clarity using var:

Before Refactoring:

ArrayList<String> names = new ArrayList<>();
HashMap<String, Integer> nameAgeMap = new HashMap<>();

After Refactoring:

var names = new ArrayList<String>();
var nameAgeMap = new HashMap<String, Integer>();

This is concise without sacrificing clarity.


Conclusion

Type inference with var in Java 10 improves code conciseness and readability when used appropriately. To ensure cleaner code:

  • Use var for obvious and readable scenarios.
  • Avoid using var when the inferred type is unclear or ambiguous.
  • Focus on balancing conciseness with the need for maintainable and self-explanatory code.

How to Use Java 17 Text Blocks for Multiline Strings

Java 17 introduced text blocks to simplify the use of multiline strings, making it much easier to include and manage multiline text in your Java applications. Text blocks were actually introduced in Java 15 but were further refined and are fully supported in Java 17.

What Are Text Blocks?

A text block is a multiline string literal declared with triple double-quotes ("""). It preserves the format of the text, including newlines and whitespace, making it ideal for creating strings like XML, JSON, HTML, SQL queries, or large blocks of text.


Syntax and Usage

Here’s the basic syntax:

String multilineString = """
        Line 1
        Line 2
        Line 3
        """;

Key Features of Text Blocks:

  1. Multiline Strings: Text blocks support strings spanning multiple lines.
  2. Automatic Line Breaks: No need to write \n at the end of each line.
  3. Automatic Handling of Whitespace: Leading whitespace can be trimmed automatically.
  4. Readable for Formats: Excellent for embedding JSON, SQL, XML, or other text-based formats.

Example Usages

1. JSON or XML Example

String json = """
        {
            "name": "John Doe",
            "age": 30,
            "city": "New York"
        }
        """;

System.out.println(json);

2. SQL Query Example

String sql = """
        SELECT *
        FROM users
        WHERE age > 18
          AND city = 'New York';
        """;

System.out.println(sql);

3. Embedding an HTML Template

String html = """
        <html>
            <body>
                <h1>Hello, World!</h1>
                <p>This is an example of a text block.</p>
            </body>
        </html>
        """;

System.out.println(html);

Notes on Formatting and Indentation

  1. Indentation Control: Java automatically determines the minimum level of indentation for the text block and removes it by default.

    For example:

    String indentedText = """
              This text block
              is indented uniformly.
              """;
    

    Outputs:

    This text block
    is indented uniformly.
    

    Notice that the leading spaces are omitted while retaining the structure.

  2. Custom Alignment: To maintain a consistent indentation in your block while coding, Java aligns the text block based on the whitespace before the ending triple quotes.


Escape Characters in Text Blocks

Text blocks still support escape sequences just like regular strings:

  • \n: Newline
  • \t: Tab
  • \": Double quote if needed inside the block
  • \\: Backslash

Example:

String special = """
        She said, \"Hello!\"
        This includes some escape sequences: \\n \\t
        """;

System.out.println(special);

Summary

Text blocks are a powerful feature, making it easier to embed multiline strings. They reduce the need for concatenation and enhance readability. Whether you’re working with configurations, templates, or queries, they provide a neat and concise way to manage strings in Java applications.

Best Practices

  • Use text blocks instead of concatenated strings for multiline text.
  • Rely on proper indentation to make the code more readable.
  • Test the output when using text blocks with external sources like JSON, SQL, or XML to ensure correctness.

Happy coding! 😊

How to Install and Set Up Java 17 on Your System

To install and set up Java 17 on your system, follow the steps below. The process may vary slightly depending on your operating system.


On Windows

  1. Download Java 17
  2. Install Java 17
    • Run the .msi installer file and follow the setup instructions.
    • Install Java in the default directory or specify a custom directory (e.g., C:\Program Files\Java\jdk-17).
  3. Set Environment Variables
    • Open the Start menu, search for “Environment Variables,” and click on “Edit the system environment variables.”
    • In the System Properties window, click on the “Environment Variables” button.
    • Under “System Variables,” find the Path variable and click Edit.
    • Add the path to the bin directory of your Java installation (e.g., C:\Program Files\Java\jdk-17\bin).
    • Click OK on all windows to save your changes.
    • Optionally, set a JAVA_HOME variable:
      • Click New under “System Variables.”
      • Name the variable JAVA_HOME and set its value to the path of your Java installation (e.g., C:\Program Files\Java\jdk-17).
  4. Verify Installation
    • Open a Command Prompt and run:
    java -version
    
    • If installed properly, it will display the Java 17 version.

On macOS

  1. Download Java 17
    • Visit the Oracle JDK or OpenJDK website, and download the .dmg installer for macOS.
  2. Install Java 17
    • Open the .dmg file and follow the installation instructions.
    • Java will be installed, usually in /Library/Java/JavaVirtualMachines/.
  3. Set Environment Variables (Optional)
    • Open a terminal and edit the ~/.zshrc (for zsh users) or ~/.bash_profile (for bash users) file using a text editor.
    • Add the following lines to set the JAVA_HOME variable:
    export JAVA_HOME=$(/usr/libexec/java_home -v 17)
    export PATH=$JAVA_HOME/bin:$PATH
    
    • Save and close the file, then reload the shell configuration:
    source ~/.zshrc
    
    • Note: The /usr/libexec/java_home command automatically detects installed Java versions.
  4. Verify Installation
    • Run the following in Terminal:
    java -version
    
    • It should show the Java 17 version.

On Linux

  1. Install OpenJDK 17
    • Use your package manager to install OpenJDK 17:
      • For Debian/Ubuntu-based systems:
      sudo apt update
      sudo apt install openjdk-17-jdk
      
      • For Red Hat/CentOS/Fedora-based systems:
      sudo dnf install java-17-openjdk-devel
      
  2. Set Default Java Version
    • If multiple Java versions are installed, you can set Java 17 as the default:
    sudo update-alternatives --config java
    
    • Select the path for Java 17 from the list.
  3. Set Environment Variables
    • Edit the ~/.bashrc or ~/.zshrc file and add:
    export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
    export PATH=$JAVA_HOME/bin:$PATH
    
    • Save the file and reload it:
    source ~/.bashrc
    
  4. Verify Installation
    • Run the following command:
    java -version
    
    • It should display details about Java 17.

Optional: Verify Java Compiler

To ensure the javac compiler is working:

javac -version

That’s it! Now Java 17 is installed and ready to use.