How to implement lazy loading in Java

How to implement lazy loading in Java

Lazy loading is a common optimization technique that delays the creation or initialization of an object until it is first used. This technology can help us reduce the waste of resources and improve the operating efficiency of the program.

In Scala, we can use the keyword lazy to define lazy variables and implement lazy loading (lazy loading). But in Java, we need to use other techniques to achieve lazy loading. In this article, we will describe how to use the Supplier interface in Java and the double-checked locking pattern to implement lazy loading and guarantee only one initialization.

Use the Supplier interface to implement lazy loading

The Supplier interface in Java is a functional interface for providing objects of type T. We can implement lazy loading by passing a lambda expression to an instance of the Supplier interface.

The following is a sample code for lazy loading using the Supplier interface:

javaCopy code
import java.util.function.Supplier;

publicclassLazy<T> {
    private final Supplier<T> supplier;
    private T value;

    public Lazy(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public T get() {
        if (value == null) {
            value = supplier. get();
        }
        return value;
    }
}
Copy Code

In the above code, we define a generic class Lazy and pass in a Supplier object in the constructor. In the get() method, we use the value variable to cache the object of type T, and call the supplier.get() method to get the object of type T when needed. Since the value variable will only be initialized once, it is guaranteed that the value variable will only be initialized when needed.

Here is sample code using the Lazy class:

javaCopy code
publicclassLazyDemo {
    publicstatic void main(String[] args) {
        Lazy<String> lazyString = new Lazy<>(() -> {
            System.out.println("Initializing lazy string...");
            return"Hello, World!";
        });

        System.out.println(lazyString.get());
        System.out.println(lazyString.get());
        System.out.println(lazyString.get());
    }
}

In the above code, we create a Lazy object and pass in a lambda expression to provide an object of type String. In the main() method, we call the lazyString.get() method multiple times and print the return value. Since the value variable will only be initialized once, “Initializing lazy string…” will only be output when the lazyString.get() method is called for the first time, and will not be output for subsequent calls.

Lazy loading with double-checked locking

The double-checked locking pattern is a common technique used to implement lazy loading. It utilizes synchronized blocks and the volatile keyword to ensure thread safety and lazy loading.

The following is lazy loading using the double-checked locking pattern

The first step is to create a Java class and declare a generic type to store the lazy calculated value. In our example, we will use the generic type T so that we can use the Lazy class to store any type of value.

javaCopy code
public class Lazy<T> {
    private final Supplier<T> supplier;
    private volatile T result;

    public Lazy(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public T get() {
        T value = result;
        if (value == null) {
            synchronized (this) {
                value = result;
                if (value == null) {
                    value = supplier. get(); result = value;
                }
            }
        }
        return value;
    }
}

In the above code, we declare a member variable supplier of private Supplier type, which passes the function to calculate the value as a parameter. We also declare a volatile member variable result, which is used to store the calculation result and ensure correct use in a multi-threaded environment.

The second step is to implement the logic of lazy loading. In our Lazy class, we implement a get() method that returns the result of the computation. In the get() method, we use a double-check locking mechanism to ensure the correctness of lazy loading. On the first call to the get() method, we check if the result variable is empty. If empty, we use a synchronized code block to avoid multiple threads computing values at the same time. In the synchronized code block, we check again that the result variable is not empty, to ensure that another thread has not calculated a value while locking. If it is empty, we call the supplier.get() method to calculate the value and store the result in the result variable. After the calculation is complete, we return the value to the caller.

The third step is to use the singleton pattern to ensure that it is only initialized once. To ensure that it is only initialized once, we declare the result variable as volatile and use a double-checked locking mechanism. In the process of computing a value, if another thread has already computed a value, the previously computed result is returned.

Step four, test that our Lazy class works as expected. In the test, we will create a class called TestLazy and declare a variable of type Lazy, then pass an anonymous function to the constructor of the Lazy class to calculate a value. We will use the value of this variable to test the correctness of lazy loading and singleton pattern.

to calculate a value. We will use the value of this variable to test the correctness of lazy loading and singleton pattern.

javaCopy code
publicclassTestLazy {
    publicstatic void main(String[] args) {
        Lazy<Integer> lazyValue = new Lazy<>(() -> {
            int result = 100 + 200;
            System.out.println("Calculating value...");
            return result;
        });

        System.out.println("Before calling get()");
        // first call int value1 = lazyValue.get();
        System.out.println("After calling get()");
        // second call int value2 = lazyValue.get();
        System.out.println("After calling get() again");

        System.out.println("value1: " + value1);
        System.out.println("value2: " + value2);

        // Check if it is the same object
        System.out.println("Is same instance: " + (lazyValue == lazyValue));
    }
}

After running this test class, the output we expect to see is:

vbnetCopy code
Before calling get()
Calculating value...
After calling get()
After calling get() again
value1:300value2:300Is same instance: trueCopy code

The output shows that the first time the get() method is called, the function that computes the value is called and the value is computed. In the second call to the get() method, we don’t see the “Calculating value…” output, which proves that lazy loading is correct. In addition, we also checked whether the values obtained twice are equal, and whether the objects are the same instance, which proves the correctness of the singleton pattern.

Finally, we now have a Lazy class that implements lazy loading, using the Supplier interface to implement lazy loading and the singleton pattern, allowing us to easily delay the calculation of values while avoiding the problem of initializing variables multiple times.

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Java skill treeHomepageOverview 108563 people are studying systematically