3.6. Laziness
In the preceding section, you saw how users of an image transformation method can precompose operations to avoid intermediate images. But why should they have to do that? Another approach is for the library to accumulate all operations and then fuse them. This is, of course, what the stream library does.
If you do lazy processing, your API needs to distinguish between intermediate operations, which accumulate the tasks to be done, and terminal operations which deliver the result. In the image processing example, we can make transform lazy, but then it needs to return another object that is not an Image. For example,
LatentImage latent = transform(image, Color::brighter);
A LatentImage can simply store the original image and a sequence of image operations.
public class LatentImage { private Image in; private List<UnaryOperator<Color>> pendingOperations; ... }
This class also needs a transform method:
LatentImage transform(UnaryOperator<Color> f) { pendingOperations.add(f); return this; }
To avoid duplicate transform methods, you can follow the approach of the stream library where an initial stream() operation is required to turn a collection into a stream. Since we can’t add a method to the Image class, we can provide a LatentImage constructor or a static factory method.
LatentImage latent = LatentImage.from(image) .transform(Color::brighter).transform(Color::grayscale);
You can only be lazy for so long. Eventually, the work needs to be done. We can provide a toImage method that applies all operations and returns the result:
Image finalImage = LatentImage.from(image) .transform(Color::brighter).transform(Color::grayscale) .toImage();
Here is the implementation of the method:
public Image toImage() { int width = (int) in.getWidth(); int height = (int) in.getHeight(); WritableImage out = new WritableImage(width, height); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { Color c = in.getPixelReader().getColor(x, y); for (UnaryOperator<Color> f : pendingOperations) c = f.apply(c); out.getPixelWriter().setColor(x, y, c); } return out; }