IntSupplier, LongSupplier, and DoubleSupplier in Java

1. Introduction to Primitive Supplier Interfaces

Hello everyone, welcome back! In this blog post, we’re going to talk about the primitive supplier interfaces in Java: IntSupplier, LongSupplier, and DoubleSupplier. These interfaces are part of Java’s functional programming toolkit, specifically designed to work with primitive types (int, long, double). They help avoid the overhead caused by autoboxing, making your code more efficient. Let’s dive into what these interfaces are and how they can help optimize your code.

2. What is Autoboxing?

Before we get into the primitive supplier interfaces, let’s briefly discuss autoboxing. Autoboxing occurs when Java automatically converts primitive types like int, long, and double into their corresponding wrapper classes: Integer, Long, and Double. While this is convenient, it introduces performance overhead by consuming more memory and requiring extra processing.

2.1 Example: Autoboxing with Supplier<Integer>

import java.util.function.Supplier;

public class AutoboxingExample {

    public static void main(String[] args) {
        // Supplier<Integer> causes autoboxing
        Supplier<Integer> randomValue = () -> (int) (Math.random() * 100);

        // Autoboxing happens when returning a primitive int
        System.out.println(randomValue.get());  // Output: [Random int value]
    }
}

Explanation:

  • When returning an int from Supplier<Integer>, autoboxing happens, converting the primitive int into an Integer object.
  • Problem: Autoboxing leads to performance overhead, especially when working with large datasets or high-frequency operations.

3. What Are IntSupplier, LongSupplier, and DoubleSupplier?

To avoid the autoboxing issue, Java provides primitive supplier interfaces: IntSupplier, LongSupplier, and DoubleSupplier. These interfaces allow you to generate or return primitive values directly (int, long, or double) without converting them into their wrapper types. This improves performance and reduces memory consumption.

  • IntSupplier: Supplies int values directly, avoiding boxing into Integer.
  • LongSupplier: Supplies long values directly, avoiding boxing into Long.
  • DoubleSupplier: Supplies double values directly, avoiding boxing into Double.

Each of these interfaces has the method:

T getAs<T>();

Where T is the primitive type (int, long, or double).

4. Using IntSupplier to Avoid Autoboxing

Let’s start with IntSupplier, which is a functional interface that supplies int values without returning a result. It’s commonly used when generating values dynamically, such as random numbers or counters, and works directly with the int type to avoid autoboxing.

4.1 Example: Using IntSupplier to Generate Random Numbers

import java.util.function.IntSupplier;

public class IntSupplierExample {

    public static void main(String[] args) {
        // IntSupplier to generate random int values
        IntSupplier randomIntSupplier = () -> (int) (Math.random() * 100);

        // Get random int value
        System.out.println("Random int: " + randomIntSupplier.getAsInt());
    }
}

Explanation:

  • The IntSupplier generates a random int value and returns it directly without autoboxing into an Integer.
  • This is a simple, efficient way to work with primitive values in performance-sensitive code.

5. Using LongSupplier to Avoid Autoboxing

Next, let’s look at LongSupplier, which supplies long values. This interface is useful for situations where large numeric values are involved, such as timestamps, IDs, or counters. By avoiding the boxing of long into Long, it helps save memory and processing time.

5.1 Example: Using LongSupplier to Get the Current Timestamp

import java.util.function.LongSupplier;

public class LongSupplierExample {

    public static void main(String[] args) {
        // LongSupplier to get the current system timestamp
        LongSupplier timestampSupplier = System::currentTimeMillis;

        // Get the current timestamp
        System.out.println("Current Timestamp: " + timestampSupplier.getAsLong());
    }
}

Explanation:

  • The LongSupplier returns the current system time as a long value, avoiding boxing into a Long object.
  • This is a practical use case when working with timestamps or other large numeric values.

6. Using DoubleSupplier to Avoid Autoboxing

Finally, let’s talk about DoubleSupplier, which supplies double values. This is useful in scenarios where precision is important, such as scientific calculations, financial applications, or any context that involves floating-point values. Using DoubleSupplier avoids the overhead of boxing double into Double.

6.1 Example: Using DoubleSupplier to Simulate Sensor Data

import java.util.function.DoubleSupplier;

public class DoubleSupplierExample {

    public static void main(String[] args) {
        // DoubleSupplier to simulate a temperature sensor reading
        DoubleSupplier temperatureSupplier = () -> 15.5 + (Math.random() * 10);

        // Get a simulated temperature reading
        System.out.println("Temperature: " + temperatureSupplier.getAsDouble() + " °C");
    }
}

Explanation:

  • The DoubleSupplier returns a double value representing a simulated temperature reading.
  • This approach is common in real-time systems, where precision is key and performance is critical.

7. Performance Comparison: Generic Supplier vs Primitive Supplier

Let’s compare the performance between a generic Supplier<T> (which requires autoboxing) and a primitive supplier like IntSupplier. This comparison will demonstrate why primitive supplier interfaces are more efficient, especially when generating values in high-frequency operations.

7.1 Example: Performance Overhead with Supplier<Integer>

import java.util.function.Supplier;

public class SupplierAutoboxingExample {

    public static void main(String[] args) {
        // Supplier<Integer> causes autoboxing
        Supplier<Integer> randomValue = () -> (int) (Math.random() * 100);

        // Generate many values with autoboxing
        for (int i = 0; i < 1_000_000; i++) {
            randomValue.get();  // Autoboxing happens here
        }
    }
}

7.2 Example: Improved Performance with IntSupplier

import java.util.function.IntSupplier;

public class IntSupplierPerformanceExample {

    public static void main(String[] args) {
        // IntSupplier to avoid autoboxing
        IntSupplier randomIntSupplier = () -> (int) (Math.random() * 100);

        // Generate many values without autoboxing
        for (int i = 0; i < 1_000_000; i++) {
            randomIntSupplier.getAsInt();  // No autoboxing, better performance
        }
    }
}

Explanation:

  • In the first example, using Supplier<Integer>, each int value is boxed into an Integer, adding performance overhead.
  • In the second example, using IntSupplier, no autoboxing occurs, making the process faster and more efficient.

8. When to Use IntSupplier, LongSupplier, and DoubleSupplier

So, when should you use these primitive supplier interfaces? Here’s a simple guide:

  • Use IntSupplier when generating or supplying int values, especially in performance-sensitive applications where boxing into Integer would be wasteful.
  • Use LongSupplier for operations involving large numbers like IDs, counters, or timestamps to avoid boxing into Long.
  • Use DoubleSupplier for precise floating-point values, such as in scientific or financial calculations, to avoid boxing into Double.

In each case, you’ll benefit from improved performance and reduced memory usage.

9. Conclusion

In today’s blog post, we explored the primitive supplier interfaces: IntSupplier, LongSupplier, and DoubleSupplier. These interfaces allow you to generate or return primitive values directly without the need for autoboxing, which makes your code more efficient. By using these interfaces, you can reduce memory consumption and improve the performance of your applications, especially when working with large datasets or frequent value generation. 

Comments