Extremely Serious

Month: October 2025

Mastering Java Method and Constructor References: Concepts and Practical Examples

Java method references, introduced in Java 8, offer a succinct and expressive way to refer to existing methods or constructors using the :: operator. They serve as a powerful alternative to verbose lambda expressions, helping developers write clearer and more maintainable code in functional programming contexts. This article covers the four types of method references.

Types of Java Method References

Java supports four main types of method references, grouped by the kind of method they refer to:

  1. Reference to a Constructor
    References a constructor to create new objects or arrays.
    Syntax: ClassName::new
    Examples:

    List people = names.stream().map(Person::new).toList();

    Create new instances with constructor reference for each element in the stream.

    Additionally, constructor references can be used for arrays:

    import java.util.function.IntFunction;
    
    IntFunction arrayCreator = String[]::new;
    String[] myArray = arrayCreator.apply(5);
    System.out.println("Array length: " + myArray.length);  // Prints 5

    This is especially useful in streams to collect into arrays:

    String[] namesArray = names.stream().toArray(String[]::new);
  2. Reference to a Static Method
    This refers to a static method in a class.
    Syntax: ClassName::staticMethodName
    Example:

    Arrays.sort(array, Integer::compare);
  3. Reference to an Instance Method of a Particular Object (Bound Method Reference)
    This is a bound method reference, tied to a specific, existing object instance. The instance is fixed when the reference is created.
    Syntax: instance::instanceMethodName
    Example:

    List names = List.of("Alice", "Bob");
    names.forEach(System.out::println);  // System.out is a fixed object

    Here, System.out::println is bound to the particular System.out object.

  4. Reference to an Instance Method of an Arbitrary Object of a Particular Type (Unbound Method Reference)
    This is an unbound method reference where the instance is supplied dynamically when the method is called.
    Syntax: ClassName::instanceMethodName
    Important Rule:
    The first parameter of the functional interface method corresponds to the instance on which the referenced instance method will be invoked. That is, the instance to call the method on is passed as the first argument, and any remaining parameters map directly to the method parameters.
    Example:

    List team = Arrays.asList("Dan", "Josh", "Cora");
    team.sort(String::compareToIgnoreCase);

    In this example, when the comparator functional interface’s compare method is called with two arguments (a, b), it is equivalent to calling a.compareToIgnoreCase(b) on the first parameter instance.

Summary

  • Java method references simplify code by allowing concise references to methods and constructors.
  • The first type—constructor references—express object and array instantiation clearly.
  • The second type is referencing static methods.
  • The third type—instance method reference of a particular object—is a bound method reference, fixed on a single object instance.
  • The fourth type—instance method reference of an arbitrary object of a particular type—is an unbound method reference, where the instance is provided at call time.
  • Constructor references are especially handy for arrays like String[].
  • System.out::println is a classic example of a bound method reference.

Locks and Semaphores in Java: A Comprehensive Guide to Concurrency Control

Locks and semaphores are foundational synchronization mechanisms in Java, designed to control access to shared resources in concurrent programming. Proper use of these constructs ensures thread safety, prevents data corruption, and manages resource contention efficiently.

What is a Lock in Java?

A lock provides exclusive access to a shared resource by allowing only one thread at a time to execute a critical section of code. The simplest form in Java is the intrinsic lock obtained by the synchronized keyword, which guards methods or blocks. For more flexibility, Java’s java.util.concurrent.locks package offers classes like ReentrantLock that provide advanced features such as interruptible lock acquisition, timed waits, and fairness policies.

Using locks ensures that when multiple threads try to modify shared data, one thread gains exclusive control while others wait, thus preventing race conditions.

Example of a Lock (ReentrantLock):

import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();  // acquire lock
        try {
            count++;  // critical section
        } finally {
            lock.unlock();  // release lock
        }
    }

    public int getCount() {
        return count;
    }
}

What is a Semaphore in Java?

A semaphore controls access based on a set number of permits, allowing a fixed number of threads to access a resource concurrently. Threads must acquire a permit before entering the critical section and release it afterward. If no permits are available, threads block until a permit becomes free. This model suits scenarios like connection pools or task throttling, where parallel access is limited rather than exclusive.

Example of a Semaphore:

import java.util.concurrent.Semaphore;

public class WorkerPool {
    private final Semaphore semaphore;

    public WorkerPool(int maxConcurrent) {
        this.semaphore = new Semaphore(maxConcurrent);
    }

    public void performTask() throws InterruptedException {
        semaphore.acquire();  // acquire permit
        try {
            // critical section
        } finally {
            semaphore.release();  // release permit
        }
    }
}

Comparing Locks and Semaphores

Aspect Lock Semaphore
Concurrency Single thread access (exclusive) Multiple threads up to a limit (concurrent)
Use case Mutual exclusion in critical sections Limit concurrent resource usage
API examples synchronized, ReentrantLock Semaphore
Complexity Simpler, single ownership More flexible, requires permit management

Best Practices for Using Locks and Semaphores

  • Always release locks or semaphore permits in a finally block to avoid deadlocks.
  • Use locks for strict mutual exclusion when only one thread should execute at a time.
  • Use semaphores when allowing multiple threads limited concurrent access.
  • Keep the critical section as short as possible to reduce contention.
  • Avoid acquiring multiple locks or permits in inconsistent order to prevent deadlocks.

Mastering locks and semaphores is key to writing thread-safe Java applications that perform optimally in concurrent environments. By choosing the right synchronization mechanism, developers can effectively balance safety and parallelism to build scalable, reliable systems.