life is too short for a diary




Review Modern Java in Action

Tags: books

Author
Written by: Tushar Sharma
  This post is in progress. It will be updated continuously


What is OOP?

OOP (Object-Oriented Programming) is a paradigm where everything is treated as an object — encapsulating state (fields) and behavior (methods) together.

Stream processing

A stream is a sequence of data items that are conceptually produced one at a time.

Parallelism vs Concurrency

Parallelism vs Concurrency. Parallelism is executing the code across multiple GPU cores. However Concurrency is running various code accross single core. You want to use paralleism for CPU bound work. Concurrency for I/O bound work or slow task.

Functional Programming in Java

Functional programming focuses on:

  1. Avoiding shared mutable state
  2. Using higher-order functions — passing functions as parameters or returning them

Functional vs Imperative Styles

Method refernce

Method references are a shorthand for lambda expressions that simply call a method. They make your code cleaner and easier to read.

File[] hiddenFiles = new File(".").listFiles(file -> file.isHidden());

You can use a method reference:

File[] hiddenFiles = new File(".").listFiles(File::isHidden);

Some practice problems

Double a list using stream

import java.util.*;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

	    numbers
		  .stream()
		  .map(x -> x * x)
      .collect(Collectors.toList())
      .forEach(System.out::println);
    }
}

Here collect is a terminal operation. It is eager—it triggers the processing of the stream and gathers the results. map is lazy, meaning it doesn’t process elements until a terminal operation (like collect) is called.

First class vs Second class citizens

In programming language theory, the concept of first-class citizens (or first-class objects) refers to entities that can be used freely in all standard ways that other values can be used in a language.

  1. Being passed as an argument to a function

  2. Being returned from a function

  3. Being assigned to a variable

  4. Being stored in data structures

Functions Are Not First-Class Citizens (Before Java 8)

  1. You couldn't pass a method directly as an argument to another method.

  2. You couldn't assign a method to a variable.

  3. You couldn't return a method from another method.

Functions are now first class citizens

Starting with Java 8 , functions became first-class citizens thanks to the introduction of:

  1. Lambda expressions

  2. Method references

  3. Functional interfaces

  4. The java.util.function package

// Assigning a lambda to a variable
Function<Integer, Integer> square = x -> x * x;

// Invoke it
int result = square.apply(5);

// Passing a function as an argument
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
numbers.forEach(n -> System.out.println(n));

// Returning a function from a method
public Function<Integer, Integer> getMultiplier(int factor) {
    return x -> x * factor;
}

Classes Are Also Not First-Class Citizens

  1. Pass a class definition directly as a parameter

  2. Return a class definition from a method (though you can return a Class<?> reference, which is metadata, not the class body itself)

public static void printClassName(Class<?> clazz) {
    System.out.println("Class name: " + clazz.getName());
}

printClassName(String.class); // Passing metadata, not the class itself

Method Reference

A method reference allows you to refer to a method without executing it.

Before Java 8, if you wanted to list only the hidden files in a directory, you might write:

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
    public boolean accept(File file) {
        return file.isHidden();
    }
});

Using Method Reference

File[] hiddenFiles = new File(".").listFiles(File::isHidden);

Here, File::isHidden is a method reference to the isHidden() method of the File class. It’s equivalent to this lambda expression:

file -> file.isHidden()

Passing code as an example

Lets you add two filters like

public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();

    for (Apple apple : inventory) {
        if (GREEN.equals(apple.getColor())) {
            result.add(apple);
        }
    }

    return result;
}

public static List<Apple> filterHeavyApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();

    for (Apple apple : inventory) {
        if (apple.getWeight() > 150) {
            result.add(apple);
        }
    }

    return result;
}

Refactor this like where a method is passes a predicate parameter named p.

public static boolean isGreenApple(Apple apple) {
    return Green.equals(apple.getColor());
}

public static boolean isHeavyApple(Apple apple) {
    return apple.getWeight() > 150;
}


import java.util.function.Predicate;

static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();

    for (Apple apple : inventory) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }

    return result;
}

To test this you can call

filterApples(inventory, Apple::isGreenApple);

// or

filterApples(inventory, Apple::isHeavyApple);

Predicate in Java

In Java, the Predicate<T> interface is part of the java.util.function package and plays a central role in functional-style programming.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Basic Usage

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        // Define a predicate using a lambda
        Predicate<String> isLongerThan5 = s -> s.length() > 5;

        // Use the test() method
        System.out.println(isLongerThan5.test("hello"));     // false
        System.out.println(isLongerThan5.test("longstring")); // true
    }
}

Filtering Collections

List<String> words = List.of("apple", "bat", "carrot", "dog", "elephant");

List<String> longWords = words.stream()
                               .filter(s -> s.length() > 5)
                               .toList();

Predicate also provides default methods for composing conditions:

Predicate<String> startsWithA = s -> s.startsWith("a");
Predicate<String> endsWithZ = s -> s.endsWith("z");

// Combine with .and(), .or(), .negate()
Predicate<String> combined = startsWithA.and(endsWithZ);

System.out.println(combined.test("applez")); // true
System.out.println(combined.test("apple"));  // false

Anonymous Inner classs


public class MeaningOfThis {

    public final int value = 4;

    public void doIt() {
        int value = 6;
        Runnable r = new Runnable() {
            public final int value = 5;
            public void run() {
                int value = 10;
                System.out.println(this.value);
            }
        };

        r.run();
    }
    public static void main(String[] args) {
        MeaningOfThis m = new MeaningOfThis();
        m.doIt();
    }
}

First, MeaningOfThis has a variable value with value 4. Next, the doIt method declares a local value with value 6, shadowing the field. Then, an inner class is defined with its own value set to 5. Inside its run method, another local value is declared with value 10. However, this.value refers to the inner class's field, so the output is 5.

Before Java 5, a common way to execute code in a separate thread was to implement Runnable and pass it to a Thread object:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running in a thread");
    }
};

Thread t = new Thread(r);
t.start(); // This actually starts a new thread

r.run(), which does not start a new thread — it just calls the method directly on the current thread.

Java 5 introduced the java.util.concurrent package, including ExecutorService, which abstracts thread creation and management. You submit tasks to a thread pool, and you can optionally get a result via a Future.

import java.util.concurrent.*;

public class Example {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // Runnable example (no result)
        executor.submit(() -> System.out.println("Running in a thread pool"));

        // Callable example (returns a result)
        Future<String> future = executor.submit(() -> {
            Thread.sleep(500);
            return "Task completed!";
        });

        System.out.println(future.get()); // Wait and get the result

        executor.shutdown();
    }
}

Java 8 (2014) introduced CompletableFuture in java.util.concurrent.

It implements both Future and CompletionStage.

Adds non-blocking, chainable async programming

CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println);

Lambda expression

it's the anonymous functions that can be passed around. So there are first class citizens in java. It has three parts

  1. Lambda parameters

  2. Arrow

  3. Lambda body

(String s ) -> s.length() // takes one parameter of tyep string and returns an int
(Apple a) -> a.getWeight() > 2 // return a boolean (implied)
(int x, int y) -> {
    return 2;
}

() -> 42 //takes no parameters and returns an int

Functional Interface

Interface that only has exactly one abstract method. e.g. predicate, runnable, comparator.


comments powered by Disqus