MOBI BOOT CAMP CORP. logoLearning Buddy
  • SIGN IN
  • Introduction
  • Warm up
  • 1: Collections
  • 2: More OOP
  • 3: Exceptions, Threads & File
  • 3: Other Concepts
    • Nested Classes
    • Modern and Enhanced Features
    • Generics
    • Slides
  • 4: Graphical User Interface (GUI)

Modern and Enhanced Java Features

With each release since Java 8, the language has introduced powerful features that make code more concise, readable, and robust. This section covers some of the most important enhancements you should know.


1. Lambda Expressions (Java 8+)

A lambda expression is a short, anonymous function that can be treated as a value. It allows you to write more concise code, especially when working with collections or implementing functional interfaces.

Syntax: (parameters) -> { body }

  • If the body is a single expression, curly braces are not needed: (a, b) -> a + b
  • If there is one parameter, parentheses are not needed: name -> System.out.println(name)

Example: forEach on a List

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

public class LambdaExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");

        // Using forEach with a lambda to print each name in lowercase
        names.forEach(name -> System.out.println(name.toLowerCase()));
    }
}

This is much more expressive than a traditional for loop.


2. Functional Interfaces (Java 8+)

A functional interface is an interface that contains exactly one abstract method. They are the target type for lambda expressions. The @FunctionalInterface annotation is used to ensure this contract at compile time.

Example: Creating a custom Functional Interface

@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

public class Calculator {
    public static void main(String args[]) {
        // We can assign a lambda to our functional interface
        MathOperation addition = (a, b) -> a + b;
        MathOperation subtraction = (a, b) -> a - b;

        System.out.println("10 + 5 = " + addition.operate(10, 5));    // Output: 15
        System.out.println("10 - 5 = " + subtraction.operate(10, 5)); // Output: 5
    }
}

Java provides many built-in functional interfaces in the java.util.function package, such as Predicate, Function, and Consumer.


3. The Stream API (Java 8+)

A Stream is a sequence of elements from a source (like a Collection) that supports powerful aggregate operations. Streams don't store data; they process it through a pipeline of operations.

Stream Map Reduce Diagram

Common Pipeline: source -> intermediate_operation() -> terminal_operation()

filter() - Filtering a Collection

The filter operation returns a new stream containing only the elements that match a given condition (a Predicate).

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
	public static void main(String[] args) {
		List<String> names = List.of("Alice", "Bob", "Anna", "Charlie");
		
		// what we learned up to this point
		List<String> aList = new ArrayList<String>();
		for (String string : names) {
			if(string.startsWith("A")) {
				aList.add(string);
			}
		}
		
		// new way to implement the same functionality
		List<String> namesStartingWithA = names.stream()
		    .filter(name -> name.startsWith("A"))
		    .collect(Collectors.toList()); // Collects the results into a new List

		System.out.println(namesStartingWithA); // Output: [Alice, Anna]
	}
}

map() - Transforming Data

The map operation transforms each element in the stream into another object. You can run this examples in jshell without the class and main methods.

import java.util.List;
import java.util.stream.Collectors;

List<String> names = List.of("Alice", "Bob", "Charlie");

List<Integer> nameLengths = names.stream()
    .map(name -> name.length()) // or String::length
    .collect(Collectors.toList());

System.out.println(nameLengths); // Output: [5, 3, 7]

reduce() - Aggregating Data

The reduce operation combines all elements of a stream into a single result. You can run this examples in jshell without the class and main methods.

import java.util.List;

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// 0 is the starting value.
// (sum, number) -> sum + number is the accumulator.
int sum = numbers.stream()
    .reduce(0, (currentSum, number) -> currentSum + number);

System.out.println(sum); // Output: 15

4. The Optional Object (Java 8+)

Optional<T> is a container object that may or may not contain a non-null value. Its purpose is to provide a clear and explicit way to handle null values, helping to prevent NullPointerException.

Key Methods:

  • Optional.of(value): Creates an Optional. Throws NullPointerException if value is null.
  • Optional.ofNullable(value): Creates an Optional. Allows the value to be null.
  • isPresent(): Returns true if a value is present.
  • get(): Returns the value if present, otherwise throws NoSuchElementException.
  • orElse(defaultValue): Returns the value if present, otherwise returns a default value.
  • ifPresent(consumer): Executes the given code block only if a value is present.

Example:

import java.util.Optional;

public class OptionalExample {
    public void printName(String name) {
        Optional<String> nameOpt = Optional.ofNullable(name);

        // Using orElse to provide a default value
        String displayName = nameOpt.orElse("Guest");
        System.out.println("Hello, " + displayName);

        // Using ifPresent to perform an action
        nameOpt.ifPresent(n -> System.out.println("Your name has " + n.length() + " letters."));
    }

    public static void main(String[] args) {
        OptionalExample example = new OptionalExample();
        // Calling the method:
        example.printName("John");   // Output: Hello, John \n Your name has 4 letters.
        example.printName(null);     // Output: Hello, Guest
    }
}

5. Modern Date and Time API (Java 8+)

The java.time package provides a powerful and immutable API for handling dates and times, replacing the old Date and Calendar classes.

  • LocalDate: Represents a date (year, month, day).
  • LocalTime: Represents a time (hour, minute, second).
  • LocalDateTime: Represents both date and time.
  • DateTimeFormatter: For parsing and formatting dates.

Example: Parsing and Formatting

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class DateExample {
    public static void main(String[] args) {
        // 1. Parsing a string into a date object
        String dateString = "2025-10-16";
        DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate myDate = LocalDate.parse(dateString, inputFormatter);

        System.out.println("Year: " + myDate.getYear());          // 2025
        System.out.println("Day of Week: " + myDate.getDayOfWeek()); // THURSDAY

        // 2. Formatting a date object into a string
        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy");
        String formattedDate = myDate.format(outputFormatter);
        System.out.println("Formatted Date: " + formattedDate); // Oct 16, 2025
    }
}

6. Interactive Shell (jshell) (Java 9+)

Java 9 introduced an interactive shell (REPL) called jshell. It's a great tool for testing simple statements, expressions, and APIs without needing to write a full class.

To start it, just type jshell in your command line.

$ jshell
|  Welcome to JShell -- Version 17.0.1
|  For an introduction type: /help intro

jshell> int x = 10;
x ==> 10

jshell> int y = 20;
y ==> 20

jshell> System.out.println("The sum is " + (x + y));
The sum is 30

jshell> /exit
|  Goodbye

7. var Keyword (Type Inference) (Java 10+)

The var keyword can be used to declare local variables. The compiler infers the type from the value on the right-hand side.

  • Reduces verbosity, especially with complex generic types.
  • Can only be used for local variables (inside methods).
// Before Java 10
String message = "Hello, World!";
Map<String, List<Integer>> myMap = new HashMap<String, List<Integer>>();

// With 'var' (Java 10+)
var message2 = "Hello, World!"; // Inferred as String
var myMap2 = new HashMap<String, List<Integer>>(); // Inferred as HashMap<...>

// 'var' is great for loops
for (var entry : myMap2.entrySet()) {
    // entry is inferred as Map.Entry<String, List<Integer>>
    System.out.println(entry.getKey());
}

8. Enhanced switch Expressions (Java 12+)

The new switch can be used as an expression that returns a value.

  • Uses -> (arrow) syntax, which prevents accidental fall-through.
  • More concise and less error-prone.
  • Can handle multiple cases per line.
// Traditional switch statement
int day = 3;
String dayName;
switch (day) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        dayName = "Weekday";
        break;
    case 6:
    case 7:
        dayName = "Weekend";
        break;
    default:
        dayName = "Invalid day";
        break;
}

// Enhanced switch expression
String dayName2 = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Invalid day";
}; // Note the semicolon
Privacy Policy | Terms & Conditions