Introduction

The Suppliers class in Google Guava library provides useful methods for working with a Supplier. In this post, let us see the Suppliers utility class in Google Guava.

Java Supplier and Google Guava Supplier

In Java, a Supplier is a functional interface which supplies some result.

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

Calling Supplier#get could return the same value each time or could return different values.

Google Guava also has a Supplier interface which extends the Java Supplier interface. Hence, we can use Google Guava’s Supplier where a Java Supplier (java.util.function.Supplier) is used.

Supplier<String> javaSupplier = () -> "java-supplier";
com.google.common.base.Supplier<String> googleGuavaSuppler = () -> "google-guava-supplier";

System.out.println(javaSupplier.get()); //java-supplier
System.out.println(googleGuavaSuppler.get()); //google-guava-supplier

To use a Java supplier as a Google Guava supplier we have to use a lambda or a method reference as shown below.

Supplier<String> javaSupplier2 = googleGuavaSuppler;
com.google.common.base.Supplier<String> googleGuavaSuppler2 = () -> javaSupplier.get();
//or
com.google.common.base.Supplier<String> googleGuavaSuppler2 = javaSupplier::get;

Suppliers ofInstance

The ofInstance method takes a reference value and returns a supplier that always supplies the passed value. We can use it as,

Supplier<String> supplier = Suppliers.ofInstance("value");
System.out.println(supplier.get()); //value
System.out.println(supplier.get()); //value

Normally, we would have written it as a lambda expression () -> “value”.

Composing a Supplier with a Function

Using the compose method, we can compose a Supplier with a Function. The Supplier and Function arguments are Google Guava’s Supplier and Function, respectively.

Note: Like the Google Guava’s Supplier, the Function extends the java.util.function.Function.

It returns a new supplier which composes the passed function and supplier. First, it calls the passed supplier and gets the value. Then, it calls the function with that value and gets the result. The returned supplier will return that result (the one returned by the function).

It does this only when we call the returned supplier’s get method.

The method contract is:

public static <F, T> Supplier<T> compose(Function<? super F, T> function, Supplier<F> supplier)
Supplier<Integer> integerSupplier = () -> 5;
Function<Integer, String> intToString = String::valueOf; //i -> String.valueOf(i);

Supplier<String> stringSupplier = Suppliers.compose(intToString, integerSupplier);

System.out.println(stringSupplier.get()); //5

We have a Supplier of Integer which always returns the value 5. We have a Google Guava function which converts the passed Integer to a String. Next, we compose these two using Suppliers#compose method. When we call the get() method on the returned supplier, it

  1. Calls the integerSupplier and gets 5 back.
  2. Calls the intToString function and gets “5” as the result.

Returns the result as string value “5”.

Compose doesn’t cache the result

The compose method does not cache the result as the supplier could return different values each time. We can see this by adding print statements in the supplier and function passed.

Supplier<Integer> integerSupplier = () -> {
    System.out.println("Integer supplier called");
    return 5;
};
Function<Integer, String> intToString = i -> {
    System.out.println("Integer to String function called");
    return String.valueOf(i);
};

Supplier<String> stringSupplier = Suppliers.compose(intToString, integerSupplier);

System.out.println(stringSupplier.get());
System.out.println(stringSupplier.get());
System.out.println(stringSupplier.get());

After we compose the supplier with the function, we call the resultant supplier 3 times. It prints,

Integer supplier called
Integer to String function called
5
Integer supplier called
Integer to String function called
5
Integer supplier called
Integer to String function called
5

As we can see, both the supplier and the function were called each time.

Passing Function accepting a supertype

The Function parameter is of type Function<? super F, T> and hence the function can take a type which is a super type of the type the Supplier returns. In the example shown below, we use a Function<Number, String> as an Integer is a Number.

Supplier<Integer> integerSupplier = () -> 5;
Function<Number, String> numToString = String::valueOf;

Supplier<String> stringSupplier = Suppliers.compose(numToString, integerSupplier);

System.out.println(stringSupplier.get()); //5

Supplier returning different values

As another example, let us create a supplier which would return different values each time we call it. For this purpose, let us use an AtomicInteger.

AtomicInteger atomicInteger = new AtomicInteger(0);

Supplier<Integer> continuousValueSupplier = atomicInteger::getAndIncrement; //() -> atomicInteger.getAndIncrement();
Function<Number, String> intToString = val -> "The value is " + val;

stringSupplier = Suppliers.compose(intToString, continuousValueSupplier);

System.out.println(stringSupplier.get());
System.out.println(stringSupplier.get());
System.out.println(stringSupplier.get());

The supplier will return values starting with 0 and in increments of 1 (0, 1, 2, 3 etc.,). The function converts the integer to a string by appending the value to the string “The value is”. Composing these, we get the result,

The value is 0
The value is 1
The value is 2

Supplier memoize

The word Memoization refers to the technique of storing or cache the results of expensive operations so that we can return the stored/cached result for the subsequent calls for the same inputs.

Here the memoize() method takes a supplier and returns a new supplier which can cache the supplier value. The first time we call the returned supplier, it calls the underlying supplier, gets the value and caches it before returning it. Thus on subsequent calls, it just returns the cached value. This method is very useful if the logic of computing the value in the supplier is compute heavy and the value can be cached (i.e., it would return the same value each time we call it).

Supplier<String> supplier = () -> {
    System.out.println("Underlying supplier called");
    return "value";
};
Supplier<String> memoizedSupplier = Suppliers.memoize(supplier);
System.out.println(memoizedSupplier.get());
System.out.println(memoizedSupplier.get());
System.out.println(memoizedSupplier.get());

Prints,

Underlying supplier called
value
value
value

The above supplier returns a hard-coded value. We memoize it and call get() on the memoized supplier. We can see that it invokes the underlying supplier only the first time.

When the supplier throws an exception

What if the supplier we pass throws an exception?

In that case, the memoizing supplier will rethrow (propagate) the exception back to the caller and nothing would be cached. The next time, we call the memoizing supplier, since no cached value is available, it will call the underlying supplier. It does this till it gets a valid data back.

Let us create a supplier which will throw a RuntimeException for the first 5 times we call it and returns a valid result the 6th time.

AtomicInteger atomicInteger = new AtomicInteger(0);
Supplier<String> exceptionThrowingSupplier = () -> {
    System.out.println("Underlying supplier called");
    if (atomicInteger.getAndIncrement() < 5) {
        throw new RuntimeException("Value not available yet");
    }
    return "value";
};

Memoizing this,

Supplier<String> memoizedSupplier = Suppliers.memoize(exceptionThrowingSupplier);

We have a helper method to call the get() method on a passed supplier, handling any exceptions thrown.

private static void getValue(Supplier<String> supplier) {
    try {
        System.out.println(supplier.get());
    } catch (RuntimeException exception) {
        System.out.println("Got an exception: " + exception.getMessage());
    }
}

Let us use this to call the memoized supplier several times.

getValue(memoizedSupplier);
getValue(memoizedSupplier);
getValue(memoizedSupplier);
getValue(memoizedSupplier);
getValue(memoizedSupplier);
// returns value from here onwards
getValue(memoizedSupplier); 
getValue(memoizedSupplier);
getValue(memoizedSupplier);

For the first five calls, we get back an exception, as shown below. We can see that all the five calls were delegated to the underlying supplier.

Underlying supplier called
Got an exception: Value not available yet
Underlying supplier called
Got an exception: Value not available yet
Underlying supplier called
Got an exception: Value not available yet
Underlying supplier called
Got an exception: Value not available yet
Underlying supplier called
Got an exception: Value not available yet

When the sixth call is made, it calls the underlying supplier. But this time, it returns a valid result and hence is cached before returning the result. Any subsequence calls will return this cached response and it won’t call the underlying delegate.

We can see that the underlying supplier was called only once (during 6th call) from the below output.

Underlying supplier called
value
value
value

Memoize with expiration

There is an overloaded memoize method which takes an expiration input (duration and its unit). Within this duration limit, it will keep returning the cached data. After the duration has elapsed, the cached result will be removed. Any subsequent calls to get() will call the underlying delegate supplier to fetch the new data and it caches this and returns it.

Using the same supplier as before, but when creating the memoized supplier, we are passing the expiration time as 2 seconds.

Supplier<String> supplier = () -> {
    System.out.println("Underlying supplier called");
    return "value";
};
Supplier<String> memoizedSupplier = Suppliers.memoizeWithExpiration(supplier, 2, TimeUnit.SECONDS);

Then we are making three calls to the memoized supplier. Out of these, only the first call will invoke the underlying supplier. Next, we sleep for 2 seconds after which we call get() on the memoized supplier three more times.

When the thread was sleeping, the specified timeout of the memoized supplier is reached and the cached data would no longer be valid. Hence, it will call the underlying supplier again when we call the memoized supplier after coming out of the sleep. Again, the subsequent calls made (the last two in the example) will not call the underlying supplier.

Supplier<String> supplier = () -> {
    System.out.println("Underlying supplier called");
    return "value";
};
Supplier<String> memoizedSupplier = Suppliers.memoizeWithExpiration(supplier, 2, TimeUnit.SECONDS);
System.out.println(memoizedSupplier.get());
System.out.println(memoizedSupplier.get());
System.out.println(memoizedSupplier.get());

try {
    System.out.println("\nSleeping");
    Thread.sleep(2000);
    System.out.println("Sleep complete\n");
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println(memoizedSupplier.get());
System.out.println(memoizedSupplier.get());
System.out.println(memoizedSupplier.get());

Prints,

Underlying supplier called
value
value
value

Sleeping
Sleep complete

Underlying supplier called
value
value
value

As the other memoize method, if the underlying delegate throws an exception, it will keep delegating the calls till it gets a valid data back.

Synchronizing a Supplier

The synchronizedSupplier method takes a Supplier and returns a supplier which is thread-safe i.e., the get() method of the returned supplier synchronizes on the underlying supplier instance before calling its get() method.

A non-thread safe supplier

Let us say we have a NonThreadSafeSupplier as shown below. It has an integer field and calling the get() on it, will return the current value of the int field and increments it.

@NotThreadSafe
public class NonThreadSafeSupplier implements Supplier<Integer> {
    private Integer i = 0;
    
    @Override
    public Integer get() {
        return i++;
    }
}

Clearly this is not thread-safe as multiple threads calling the get() on this supplier instance could get the same value. To see this, let us create a Runnable which takes an instance of the NonThreadSafeSupplier and calls it 10 times in a loop. To easily simulate the race condition, I’ve added a sleep as well.

public class MyRunnable implements Runnable {
    private final Supplier<Integer> supplier;

    MyRunnable(Supplier<Integer> supplier) {
        this.supplier = supplier;
    }

    @Override
    public void run() {
        int count = 0;
        while (count < 10) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.printf("%s got %s%n", Thread.currentThread().getName(), supplier.get());
            count++;
        }
    }
}

It prints the name of the thread and the value it got from the supplier invocation.

Now, let us create a runnable and two threads and start both and wait for their completion. The passed supplier is an instance of the NonThreadSafeSupplier.

public void process(Supplier<Integer> supplier) throws InterruptedException {
    MyRunnable runnable = new MyRunnable(supplier);
    Thread thread1 = new Thread(runnable);
    thread1.start();
    Thread thread2 = new Thread(runnable);
    thread2.start();
    thread1.join();
    thread2.join();
}

//Call the above passing a NonThreadSafeSupplier
Supplier<Integer> supplier = new NonThreadSafeSupplier();
process(supplier);

A sample run might look like,

Thread-0 got 0
Thread-1 got 0
Thread-0 got 1
Thread-1 got 2
Thread-0 got 3
Thread-1 got 4
Thread-0 got 5
Thread-1 got 6
Thread-0 got 7
Thread-1 got 7
Thread-0 got 8
Thread-1 got 9
Thread-1 got 10
Thread-0 got 10
Thread-1 got 11
Thread-0 got 11
Thread-0 got 12
Thread-1 got 12
Thread-0 got 13
Thread-1 got 14

Both thread 0 and thread 1 got many values from the supplier which is not unique. Both got values 0, 7, 10, 11, 12 in the above sample run. Each run will give different results.

Synchronizing on the supplier

Let us use the synchronizedSupplier method and created a supplier which synchronizes on the underlying supplier instance before calling it. This way, only one of the threads will be calling the underlying supplier’s get() method (at a given time). And this will ensure each thread will get back a unique integer value.

Supplier<Integer> supplier = new NonThreadSafeSupplier();
process(Suppliers.synchronizedSupplier(supplier));

We call the above process() method with a synchronized supplier. A sample run output is shown below.

Thread-0 got 0
Thread-1 got 1
Thread-0 got 2
Thread-1 got 3
Thread-0 got 4
Thread-1 got 5
Thread-0 got 6
Thread-1 got 7
Thread-0 got 8
Thread-1 got 9
Thread-1 got 10
Thread-0 got 11
Thread-1 got 12
Thread-0 got 13
Thread-1 got 14
Thread-0 got 15
Thread-1 got 16
Thread-0 got 17
Thread-1 got 18
Thread-0 got 19

The threads get back values 0 to 19 in order (but we cannot determine which threads will get which value). No two threads can get the same value when using the synchronized supplier this way.

Conclusion

In this post we saw the Supplier interface and the Suppliers utility class in Google Guava. We learnt about the many useful static methods the Suppliers class has. Check out the other google-guava posts here as well.