0

I'm teaching classes on the new Java constructs. I've already introduced my students to Optional<T>. Imagining that there is a Point.getQuadrant() which returns an Optional<Quadrant> (because some points lie in no quadrant at all), we can add the quadrant to a Set<Quadrant> if the point lies in a positive X quadrant, like this:

Set<Quadrant> quadrants = ...;
Optional<Point> point = ...;
point
    .filter(p -> p.getX() > 0)
    .flatMap(Point::getQuadrant)
    .ifPresent(quadrants::add);

(That's off the top of my head; let me know if I made a mistake.)

My next step was to tell my students, "Wouldn't it be neat if you could do the same internal filtering and decisions using functions, but with multiple objects? Well that's what streams are for!" It seemed the perfect segue. I was almost ready to write out the same form but with a list of points, using streams:

Set<Point> points = ...;
Set<Quadrant> quadrants = points.stream()
    .filter(p -> p.getX() > 0)
    .flatMap(Point::getQuadrant)
    .collect(Collectors.toSet());

But wait! Will Stream.flatMap(Point::getQuadrant) correctly unravel the Optional<Quadrant>? It appears not...

I had already read Using Java 8's Optional with Stream::flatMap , but I had thought that what was discussed there was some esoteric side case with no real-life relevance. But now that I'm playing it out, I see that this is directly relevant to most anything we do.

Put simply, if Java now expects us to use Optional<T> as the new optional-return idiom; and if Java now encourages us to use streams for processing our objects, wouldn't we expect to encounter mapping to an optional value all over the place? Do we really have to jump through series of .filter(Optional::isPresent).map(Optional::get) hoops as a workaround until Java 9 gets here years from now?

I'm hoping I just misinterpreted the other question, that I'm just misunderstanding something simple, and that someone can set me straight. Surely Java 8 doesn't have this big of a blind spot, does it?

Community
  • 1
  • 1
Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
  • Why `flatMap`? Also, if it's meant to return `Optional`, how come you're collecting into a `Set`? Did you mean to collect only those that are present? – Sotirios Delimanolis May 06 '16 at 23:55
  • Yes, I only want to collect quadrants that are present. – Garret Wilson May 06 '16 at 23:56
  • Then `map` to convert the `Point` to a `Optional`, then filter with `Optional#isPresent`, then `map` back with `get` and `collect` to your `Set`. – Sotirios Delimanolis May 06 '16 at 23:58
  • I realize now you already know this. – Sotirios Delimanolis May 07 '16 at 00:00
  • Something about your code feels odd. As in: it probably works out if you model things differently (e.g. point.filter - how do you filter a single thing?). But I already don't understand how points have no quadrant, or don't you mean the 4 quadrants of a cartesian coordinate system (and points therein)? – zapl May 07 '16 at 00:06
  • @zapl: I recommend you review `Optional` and quadrants. – Garret Wilson May 07 '16 at 00:13
  • @zapl: I meant to make `Point` an `Optional`; I'll edit that. But review quadrants; not all points are in a quadrant. – Garret Wilson May 07 '16 at 00:31
  • You have touched on a pain point significant enough that the very thing you bring up is being addressed in Java 9. See http://download.java.net/java/jdk9/docs/api/java/util/Optional.html#stream--, – Hank D May 07 '16 at 01:48
  • Ah, 0 coordinates. And with `point` being an `Optional` it makes sense. And no, `Stream.flatMap` will neither now nor in java 9 work directly on `Optional` because `Stream#flatMap()` expects a function that returns a `Stream`. Would be helpful, it's been suggested to make that happen: https://bugs.openjdk.java.net/browse/JDK-8050820?focusedCommentId=13572390&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13572390 but I doubt that this changes. You'll have to map from optional to streams yourself in an additional step. That step gets simpler in java 9 – zapl May 07 '16 at 15:04
  • @HankD, I still think Java 9's `Optional.stream()` is clunky. I would expect `Stream.flatMap()` to unravel an `Optional` for me, or that `Stream` would provide a separate mapping method for `Optional`. We're supposed to pass an `Optional` around like a normal value, but adding these extra extraction methods makes `Optional` look sort of clunky, wedged into the language later (and I guess it was). – Garret Wilson May 07 '16 at 16:26
  • Look at it that way: If Stream provided a special method for `Optional` then they'd give `Optional` a special treatment. It's hard to justify that it deserves that because it's just one of many things that can be used in streams. It's not even the only implementation of the `Optional` concept around (e.g. guava had one long before java 8) and there will be many other use cases where it would be nice to have a shortcut that saves a methodcall. API designers are well advised to keep the API minimal as long as it's not restricting you. – zapl May 07 '16 at 17:32
  • If they added a new interface e.g. `Streamable { Stream toStream() }` and support in `Stream#flatMap()` for that. That would be a good solution that would allow anyone to profit, not just `Optional`. – zapl May 07 '16 at 17:35
  • 1
    Yes, great idea @zapl. Why isn't there a `Streamable` interface, I wonder? It seems so obvious. – Garret Wilson May 07 '16 at 23:00

1 Answers1

1

I’m not sure where your question is aiming at. The answer of the linked question already states that this is a flaw of Java 8 that is addressed in Java 9. So there’s no sense in asking whether this really is a flaw in Java 8.

Besides that, the answer also mentions .flatMap(o -> o.isPresent()? Stream.of(o.get()): Stream.empty()) for converting the optional, rather than your .filter(Optional::isPresent) .map(Optional::get). Nevertheless, you can do it even simpler:

Instead of

.flatMap(Point::getQuadrant)

you can write

.flatMap(p -> p.getQuadrant().map(Stream::of).orElse(null))

as an alternative to Java 9’s

.flatMap(p -> p.getQuadrant().stream())

Note that

.map(Point::getQuadrant).flatMap(Optional::stream)

isn’t so much better compared to the alternatives, unless you have an irrational affinity to method references.

Holger
  • 285,553
  • 42
  • 434
  • 765