使用Java8将数组迭代转换为Lambda函数

3

我正在试图转换为Lambda函数

到目前为止,我已经能够将上面的代码转换为Lambda函数,如下所示

Stream.of(acceptedDetails, rejectedDetails)
.filter(list -> !isNull(list) && list.length > 0)
.forEach(new Consumer<Object>() {
    public void accept(Object acceptedOrRejected) {
        String id;
        if(acceptedOrRejected instanceof EmployeeValidationAccepted) {
            id = ((EmployeeValidationAccepted) acceptedOrRejected).getId();
        } else {
            id = ((EmployeeValidationRejected) acceptedOrRejected).getAd().getId();
        }

        if(acceptedOrRejected instanceof EmployeeValidationAccepted) {
            dates1.add(new Integer(id.split("something")[1]));
            Integer empId = Integer.valueOf(id.split("something")[2]);
            empIds1.add(empId);
        } else {
            dates2.add(new Integer(id.split("something")[1]));
            Integer empId = Integer.valueOf(id.split("something")[2]);
            empIds2.add(empId);
        }
    }
});

但是我的目标仍然是避免重复相同的逻辑并转换为Lambda函数,在我转换后的Lambda函数中,我觉得它不够简洁和高效。

这只是为了我的学习方面,我通过采用一个现有的代码片段来做这个东西。

请问有人可以告诉我如何改进转换后的Lambda函数吗?


这是一个相当大的示例,下面的代码进行了instanceof检查,而命令式没有,你的目标是什么?在我看来,使用instanceof似乎是不好的风格。 - roookeee
如果您的应用程序这部分不是非常敏感于性能,我会为empIdAccepteddateAccepted等各自使用一个stream.map()等。因为这样更清晰,并将您展示的逻辑分解成不相关的不同方面。这是函数式编程的基础:将其分解为子操作或步骤。 - roookeee
我的目标是将代码和逻辑重复的部分转换为Lambda函数。 - Alex Man
函数式编程部分地涉及到更细粒度的方法,其中你需要将工作分割成多个步骤 - 当你停止实现那些做所有事情的函数时(例如,空值检查 + 转换而非单一操作),重复代码和 / 或步骤就是一个必然。 - roookeee
1
在您的“filter”步骤中,流元素似乎是数组(您正在访问“list.length”),然后在“forEach”步骤中,它们突然应该是“EmployeeValidationAccepted”或“EmployeeValidationRejected”的实例。这是行不通的。用这个不完整的破碎代码替换您的工作原始代码也没有建设性。在原始代码中,您有“dateRejected”和“empIdRejected”,但现在您有“dates1”,“dates2”,“empIds1”和“empIds2”,它们没有被声明。 - Holger
3个回答

4
通常情况下,在尝试重构代码时,你应该只关注必要的更改。
仅仅因为你将使用Stream API,并没有理由在代码中添加检查空或空数组的代码,这些代码在基于循环的代码中并不存在。你也不应该将BigInteger更改为Integer。
然后,你有两个不同的输入,想要从它们中获取不同的结果,换句话说,你有两个完全不同的操作。虽然考虑共享公共代码是合理的,但一旦确定了相同的代码,就没有必要将两个完全不同的操作表达为单个操作。
首先,让我们看看如何在传统循环中实现此操作:
static void addToLists(String id, List<Integer> empIdList, List<BigInteger> dateList) {
    String[] array = id.split("-");
    dateList.add(new BigInteger(array[1]));
    empIdList.add(Integer.valueOf(array[2]));
}

List<Integer> empIdAccepted = new ArrayList<>();
List<BigInteger> dateAccepted = new ArrayList<>();

for(EmployeeValidationAccepted acceptedDetail : acceptedDetails) {
    addToLists(acceptedDetail.getId(), empIdAccepted, dateAccepted);
}

List<Integer> empIdRejected = new ArrayList<>();
List<BigInteger> dateRejected = new ArrayList<>();

for(EmployeeValidationRejected rejectedDetail : rejectedDetails) {
    addToLists(rejectedDetail.getAd().getId(), empIdRejected, dateRejected);
}

如果我们想要表达与流操作相同的内容,那么每个操作都会有两个结果的障碍。直到 JDK 12 才推出了内置解决方案:
static Collector<String,?,Map.Entry<List<Integer>,List<BigInteger>>> idAndDate() {
    return Collectors.mapping(s -> s.split("-"),
        Collectors.teeing(
            Collectors.mapping(a -> Integer.valueOf(a[2]), Collectors.toList()),
            Collectors.mapping(a -> new BigInteger(a[1]),  Collectors.toList()),
            Map::entry));
}

Map.Entry<List<Integer>, List<BigInteger>> e;
e = Arrays.stream(acceptedDetails)
        .map(EmployeeValidationAccepted::getId)
        .collect(idAndDate());

List<Integer> empIdAccepted = e.getKey();
List<BigInteger> dateAccepted = e.getValue();

e = Arrays.stream(rejectedDetails)
    .map(r -> r.getAd().getId())
    .collect(idAndDate());

List<Integer> empIdRejected = e.getKey();
List<BigInteger> dateRejected = e.getValue();

由于一个方法无法返回两个值,因此使用 Map.Entry 来保存它们。

如果要在 JDK 12 之前的 Java 版本中使用此解决方案,可以使用 this answer 末尾发布的实现。然后,您还必须将 Map::entry 替换为 AbstractMap.SimpleImmutableEntry::new

或者,您可以使用为此特定操作编写的自定义收集器:

static Collector<String,?,Map.Entry<List<Integer>,List<BigInteger>>> idAndDate() {
    return Collector.of(
        () -> new AbstractMap.SimpleImmutableEntry<>(new ArrayList<>(), new ArrayList<>()),
        (e,id) -> {
            String[] array = id.split("-");
            e.getValue().add(new BigInteger(array[1]));
            e.getKey().add(Integer.valueOf(array[2]));
        },
        (e1, e2) -> {
            e1.getKey().addAll(e2.getKey());
            e1.getValue().addAll(e2.getValue());
            return e1;
        });
}

换句话说,使用Stream API并不总是能使代码更简单。
最后需要注意的是,我们不需要使用Stream API来利用lambda表达式。我们也可以使用它们将循环移入公共代码中。
static <T> void addToLists(T[] elements, Function<T,String> tToId,
                           List<Integer> empIdList, List<BigInteger> dateList) {
    for(T t: elements) {
        String[] array = tToId.apply(t).split("-");
        dateList.add(new BigInteger(array[1]));
        empIdList.add(Integer.valueOf(array[2]));
    }
}

List<Integer> empIdAccepted = new ArrayList<>();
List<BigInteger> dateAccepted = new ArrayList<>();
addToLists(acceptedDetails, EmployeeValidationAccepted::getId, empIdAccepted, dateAccepted);

List<Integer> empIdRejected = new ArrayList<>();
List<BigInteger> dateRejected = new ArrayList<>();
addToLists(rejectedDetails, r -> r.getAd().getId(), empIdRejected, dateRejected);

2
一个与@roookeee类似的方法,但可能更加简洁的方法是使用声明为映射函数的映射来存储映射关系:

(原始回答翻译成“最初的回答”)

Function<String, Integer> extractEmployeeId = empId -> Integer.valueOf(empId.split("-")[2]);
Function<String, BigInteger> extractDate = empId -> new BigInteger(empId.split("-")[1]);

then proceed with mapping as:

Map<Integer, BigInteger> acceptedDetailMapping = Arrays.stream(acceptedDetails)
        .collect(Collectors.toMap(a -> extractEmployeeId.apply(a.getId()),
                a -> extractDate.apply(a.getId())));

Map<Integer, BigInteger> rejectedDetailMapping = Arrays.stream(rejectedDetails)
        .collect(Collectors.toMap(a -> extractEmployeeId.apply(a.getAd().getId()),
                a -> extractDate.apply(a.getAd().getId())));

此后,您还可以访问与员工的employeeId相对应的接受或拒绝日期。最初的回答中已经包含了这个信息。

1
另一个好主意!我的答案可以通过使用Stream.concat而不是像你描述的那样收集两种变量然后过滤来扩展。 - roookeee
2
这假设ID是真正唯一的,而且顺序并不重要。此外,它对每个元素执行了两次“split”。 - Holger

1
这个怎么样:
 class EmployeeValidationResult {
    //constructor + getters omitted for brevity
    private final BigInteger date;
    private final Integer employeeId;
}

List<EmployeeValidationResult> accepted = Stream.of(acceptedDetails)
    .filter(Objects:nonNull)
    .map(this::extractValidationResult)
    .collect(Collectors.toList());

List<EmployeeValidationResult> rejected = Stream.of(rejectedDetails)
    .filter(Objects:nonNull)
    .map(this::extractValidationResult)
    .collect(Collectors.toList());


EmployeeValidationResult extractValidationResult(EmployeeValidationAccepted accepted) {
    return extractValidationResult(accepted.getId());
}

EmployeeValidationResult extractValidationResult(EmployeeValidationRejected rejected) {
    return extractValidationResult(rejected.getAd().getId());
}

EmployeeValidationResult extractValidationResult(String id) {
    String[] empIdList = id.split("-");
    BigInteger date = extractDate(empIdList[1])
    Integer empId = extractId(empIdList[2]);

    return new EmployeeValidationResult(date, employeeId);
}

重复使用 filtermap 操作是良好的风格,并且明确了正在发生的事情。将两个对象列表合并为一个并使用 instanceof 会使实现变得混乱,难以阅读/维护。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接