Java 8 流:您可以捕获/重用过滤器中计算出的值吗?

use*_*922 5 java parallel-processing filter garbage java-8

我正在尝试将“旧方式”循环转换为基于流的方法。该循环采用一大组元素并返回一个落在给定半径内的子集。结果按距离排序,并且结果本身具有方便的计算距离(用于演示)。它以旧方式工作正常,我不需要对它进行 Java8 化。但我真的很想。:-) 如果只是为了能够在这个傻瓜上使用 .parallel() 就好了。

问题是……我的 filter() 使用了一个计算值(距离),然后我需要在后续的 map() 步骤中使用它(以构建“with distance”实例)。假设距离计算很昂贵。这是 Java 7 的方式……向下滚动以查看 getNearestStations() 方法:

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<StationWithDistance> {
    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<Station> allStations = new ArrayList<>();

public List<StationWithDistance> getNearbyStations(Coordinate origin, double radius) {
    List<StationWithDistance> nearbyStations = new ArrayList<>();
    for (Station station : allStations) {
        double distance = origin.distanceTo(station.getCoordinate());
        if (distance <= radius) {
            nearbyStations.add(new StationWithDistance(station, distance));
        }
    }
    Collections.sort(nearbyStations);
    return nearbyStations;
}
Run Code Online (Sandbox Code Playgroud)

现在......这是一种基于愚蠢/蛮力流的方法。请注意,它执行了两次距离计算(愚蠢),但它更接近于 parallel()ized:

public List<StationWithDistance> getNearbyStationsNewWay(Coordinate origin, double radius) {
    return allStations.stream()
        .parallel()
        .filter(s -> origin.distanceTo(s.getCoordinate()) <= radius)
        .map(s -> new StationWithDistance(s, origin.distanceTo(s.getCoordinate())))
        .sorted()
        .collect(Collectors.toList());
}
Run Code Online (Sandbox Code Playgroud)

试图找出更好的方法(tm),这是我迄今为止为了避免重复计算而想出的全部内容:

public List<StationWithDistance> getNearbyStationsNewWay(Coordinate origin, double radius) {
    return allStations.stream()
        .parallel()
        .map(s -> new StationWithDistance(s, origin.distanceTo(s.getCoordinate())))
        .filter(s -> s.getDistance() <= radius)
        .sorted()
        .collect(Collectors.toList());
}
Run Code Online (Sandbox Code Playgroud)

...但这会产生垃圾——大多数创建的 StationWithDistance 实例都被过滤掉了。

我错过了什么?在 Java 8 中是否有一种优雅的方法可以做到(a)避免重复计算,并且(b)不会产生不需要的垃圾?

我可以通过 forEach() 调用来做到这一点,混合新旧方法......所以我至少可以利用流,但以老派的方式“优化”计算/过滤器/添加。必须有一个很好的、简单的、优雅的解决方案。帮我看看光明...

Stu*_*rks 2

您可以使用flatMap融合 afiltermapstage,这允许您将计算出的距离保存在局部变量中,直到您知道创建新对象有用为止。

在这里,我将 flatmapper 提取到一个辅助方法中,因为我更喜欢这种风格,但它当然可以将其内联为语句 lambda(甚至是使用三元? :运算符的表达式 lambda)。

Stream<StationWithDistance> nearbyStation(Station s, Coordinate origin, double radius) {
    double distance = origin.distanceTo(s.getCoordinate());
    if (distance <= radius) {
        return Stream.of(new StationWithDistance(s, distance));
    } else {
        return Stream.empty();
    }
}

public List<StationWithDistance> getNearbyStationsNewerWay(Coordinate origin, double radius) {
    return allStations.stream()
        .parallel()
        .flatMap(s -> nearbyStation(s, origin, radius))
        .sorted()
        .collect(Collectors.toList());
}
Run Code Online (Sandbox Code Playgroud)