title: Java 8 streams: can you capture/reuse a value calculated in a filter?
body: <p>I'm trying to convert an "old way" loop into a streams-based approach.  The loop takes one large set of elements and returns a subset that fall within a given radius.  The results are sorted by distance, and the results themselves have the calculated distance handy (for presentation).  It works fine the old way, and I don't <em>need</em> to Java8-ify it.  But I really want to.  :-)  If only to be able to go .parallel() on this sucker.</p>
  
  <p>The catch is...my filter() uses a calculated value (the distance), which I then need in a subsequent map() step (to construct the "with distance" instance).  Assume the distance calculation is expensive.  Here's the Java 7 way...scroll down to see the getNearestStations() method:</p>
  
  <pre><code>public interface Coordinate {
      double distanceTo(Coordinate other);
  }
  
  public class Station {
      private final String name;
      private final Coordinate coordinate;
  
      public Station(String name, Coordinate coordinate) {
          this.name = name;
          this.coordinate = coordinate;
      }
  
      public String getName() {
          return name;
      }
  
      public Coordinate getCoordinate() {
          return coordinate;
      }
  }
  
  public class StationWithDistance extends Station implements Comparable&lt;StationWithDistance&gt; {
      private final double distance;
  
      public StationWithDistance(Station station, double distance) {
          super(station.getName(), station.getCoordinate());
          this.distance = distance;
      }
  
      public double getDistance() {
          return distance;
      }
  
      public int compareTo(StationWithDistance s2) {
          return Double.compare(this.distance, s2.distance);
      }
  }
  
  // Assume this contains many entries
  private final List&lt;Station&gt; allStations = new ArrayList&lt;&gt;();
  
  public List&lt;StationWithDistance&gt; getNearbyStations(Coordinate origin, double radius) {
      List&lt;StationWithDistance&gt; nearbyStations = new ArrayList&lt;&gt;();
      for (Station station : allStations) {
          double distance = origin.distanceTo(station.getCoordinate());
          if (distance &lt;= radius) {
              nearbyStations.add(new StationWithDistance(station, distance));
          }
      }
      Collections.sort(nearbyStations);
      return nearbyStations;
  }
  </code></pre>
  
  <p>Now...here's a dumb/brute force streams-based approach.  Note that it performs the distance calculation twice (stoopid), but it's a step closer to parallel()ized:</p>
  
  <pre><code>public List&lt;StationWithDistance&gt; getNearbyStationsNewWay(Coordinate origin, double radius) {
      return allStations.stream()
          .parallel()
          .filter(s -&gt; origin.distanceTo(s.getCoordinate()) &lt;= radius)
          .map(s -&gt; new StationWithDistance(s, origin.distanceTo(s.getCoordinate())))
          .sorted()
          .collect(Collectors.toList());
  }
  </code></pre>
  
  <p>Trying to figure out a Better Way(tm), this is all I've come up with so far in order to avoid the duplicate calculation:</p>
  
  <pre><code>public List&lt;StationWithDistance&gt; getNearbyStationsNewWay(Coordinate origin, double radius) {
      return allStations.stream()
          .parallel()
          .map(s -&gt; new StationWithDistance(s, origin.distanceTo(s.getCoordinate())))
          .filter(s -&gt; s.getDistance() &lt;= radius)
          .sorted()
          .collect(Collectors.toList());
  }
  </code></pre>
  
  <p>...but that produces garbage -- most of the StationWithDistance instances created get filtered out.</p>
  
  <p>What am I missing?  Is there an elegant way to do this in Java 8 that (a) avoids the duplicate calculation, and (b) doesn't produce unwanted garbage?</p>
  
  <p>I could do this with a forEach() call, mixing old &amp; new methodologies...so I could at least take advantage of streams, but "optimize" the calc/filter/add in an old-school way.  There's gotta be a nice, easy, elegant solution to this.  Help me see the light...</p>