Java 使用流合并两个集合,但仅使用唯一值,并使用谓词而不是相等?
我正在尝试合并两个集合,但是在我只想添加唯一值的地方有条件地进行合并。而什么构成了唯一性应该由谓词(或类似谓词)决定,而不是等于函数 例如,假设我们有以下两个Person对象集合:Java 使用流合并两个集合,但仅使用唯一值,并使用谓词而不是相等?,java,collections,java-8,java-stream,java-9,Java,Collections,Java 8,Java Stream,Java 9,我正在尝试合并两个集合,但是在我只想添加唯一值的地方有条件地进行合并。而什么构成了唯一性应该由谓词(或类似谓词)决定,而不是等于函数 例如,假设我们有以下两个Person对象集合: List<Employee> list1 = Arrays.asList(new Employee(1, "Adam", "Smith", Type.CEO), new Employee(2, "Bob", "Jones", Type.OfficeManager), new Employee(3, "Ca
List<Employee> list1 = Arrays.asList(new Employee(1, "Adam", "Smith", Type.CEO), new Employee(2, "Bob", "Jones", Type.OfficeManager), new Employee(3, "Carl", "Lewis", Type.SalesPerson));
List<Employee> list2 = Arrays.asList(new Employee(4, "Xerxes", "Brown", Type.OfficeManager), new Employee(5, "Yuri", "Gagarin", Type.Janitor), new Employee(6, "Zain", "Wilson", Type.SalesPerson));
以一般的Java8/9方式实现这一点的“最佳”方法是什么?也就是说,我不想写一些特定于Person对象或类型enum的东西,也不想写一些使用对象的equals方法的东西。相反,我想使用双预测或类似的东西
但是,我也不想自己执行任何循环Streams似乎是个不错的选择,但我不知道如何实现这一点。如果一个值来自一个流,而另一个值来自另一个流,我如何编写一个双预测,而不用自己执行循环
我想使用BiPredicate(或类似)的原因是,我希望能够将此函数与高级逻辑一起使用,在高级逻辑中,不可能简单地提取所有元素的某些属性,然后根据此属性的唯一性对值进行分组
有什么建议吗
/吉米
更新:为了澄清我为什么要谈论谓词,这里有一个更复杂的例子:
假设我们有两个Employee对象集合,与前面一样。但这一次,唯一性逻辑无法使用映射函数表示为Employee对象的特定属性。相反,它使用了EmployeeRegistry中的一些数据,如:如果两名员工属于相同的纳税等级或如果他们属于相同的“类型”,则认为他们是平等的。由于这一点或逻辑,不可能将其简化为用于分组数据或类似内容的唯一键
Update2:为了简单起见,下面是一个不太复杂的示例,但它仍然足够复杂,不能简单地映射到字段。这有点做作,但那是为了简单
假设我们有两个字符串集合。唯一性的计算如下:
- 如果两个字符串长度相等,则认为它们相等
- 否则,如果两个字符串以相同字符开头,则认为它们相等
hashCode()
实现。我能想到的唯一一个函数实现是返回一个常量值(即不管字符串是什么,都返回相同的值)
更新3:考虑到我的“equalness”算法的或逻辑打破了等于契约,并且使得编写有效的哈希代码实现变得困难(不可能?),我现在回到了我开始的地方。例如需要某种类型的谓词。下面是一个更新的“真实世界”示例:
假设我们像以前一样拥有两个Employee对象集合,并且希望将这些集合合并为一个。但这一次,我们希望避免包括不合群的人。为了确定两个人是否相处融洽,我们有一个HumanRelationsDepartment对象,方法是IsokToWorkwithther(Person,Person)。如果检测到两个相处不融洽的人,则只将其中一个添加到新集合中。哪一个可以由映射函数确定,默认逻辑可能是选择第一个人
编写解决这个问题的老派代码相当简单。我要寻找的是一个基于无循环流的解决方案。这样的解决方案存在吗?性能不是问题。将它们映射到具有唯一值作为键的映射,然后将条目映射到列表。将它们映射到具有唯一值作为键的映射,然后将条目映射到列表。对于两个流的简单合并,可以使用concat(只需更新reducer的逻辑):
// Concatenate the streams
Stream.concat(list1.stream(), list2.stream())
.collect(
// Collect similar employees together
groupingBy(
// Where "similar" is determined by a function, e.g. Employee::getType
keyFn,
// Take the first employee found
(a, b) -> a)
// Discard the keys.
.values();
2-声明将在同一索引处合并2个对象的“减缩器”:
要简化它:创建一个通用的
zip
方法:
public static <T> List<T> zipStreams(List<T> list1, List<T> list2, BiFunction<T, T, T> employeeMerger, Comparator<T> sortComparator) {
if(list1.size() != list2.size()) {
throw new IllegalArgumentException("Lists must be of the same length");
}
List<T> list1Sorted = sortComparator == null ? list1: list1.stream()
.sorted(sortComparator)
.collect(Collectors.toList()),
list2Sorted = sortComparator == null ? list2: list2.stream()
.sorted(sortComparator)
.collect(Collectors.toList());
return IntStream.range(0, list1Sorted.size())
.mapToObj(i -> Arrays.<T>asList(list1Sorted.get(i), list2Sorted.get(i)))
.map(list -> employeeMerger.apply(list.get(0), list.get(1)))
.collect(Collectors.toList());
}
对于两个流的简单合并,可以使用concat(只需更新reducer的逻辑): 2-声明将在同一索引处合并2个对象的“减缩器”:
要简化它:创建一个通用的
zip
方法:
public static <T> List<T> zipStreams(List<T> list1, List<T> list2, BiFunction<T, T, T> employeeMerger, Comparator<T> sortComparator) {
if(list1.size() != list2.size()) {
throw new IllegalArgumentException("Lists must be of the same length");
}
List<T> list1Sorted = sortComparator == null ? list1: list1.stream()
.sorted(sortComparator)
.collect(Collectors.toList()),
list2Sorted = sortComparator == null ? list2: list2.stream()
.sorted(sortComparator)
.collect(Collectors.toList());
return IntStream.range(0, list1Sorted.size())
.mapToObj(i -> Arrays.<T>asList(list1Sorted.get(i), list2Sorted.get(i)))
.map(list -> employeeMerger.apply(list.get(0), list.get(1)))
.collect(Collectors.toList());
}
您可以通过以下方式实现您的目标:
Collection merged=Stream.of(列表1、列表2)
.flatMap(集合::流)
.collect(Collectors.toMap(e->calculateGroup(e),e->e,(e1,e2)->e1)))
.values();
因此,这将根据一些calculateGroup
方法创建一个Map
,该方法接收Employee
实例并返回表示员工所属组的内容。这可能是员工
的某些属性,即类型
,也可能是更复杂的属性,可以从其他地方获取数据以根据员工的年收入确定集团,即税级。这是地图的钥匙,它将根据您的具体需要确定唯一性。这种方法的唯一要求是,无论您对键使用什么类,它都必须一致地实现equals
和hashCode
映射的值将只是连接流的Employee
实例。对于合并函数(Collectors.toMap
3参数),我使用了(
List<Employee> list1Sorted = list1.stream()
.sorted(Comparator.comparing(Employee::getType))
.collect(Collectors.toList());
List<Employee> list2Sorted = list2.stream()
.sorted(Comparator.comparing(Employee::getType))
.collect(Collectors.toList());
//This is returning an arbitrary value. You may want to add your own logic:
BiFunction<Employee, Employee, Employee> reducer = (e1, e2) -> e1;
List<Employee> mergedList = IntStream.range(0, list1.size())
.mapToObj(i -> new Employee[] {list1Sorted.get(i), list2Sorted.get(i)})
.map(e -> reducer.apply(e[0], e[1]))
.collect(Collectors.toList());
public static <T> List<T> zipStreams(List<T> list1, List<T> list2, BiFunction<T, T, T> employeeMerger, Comparator<T> sortComparator) {
if(list1.size() != list2.size()) {
throw new IllegalArgumentException("Lists must be of the same length");
}
List<T> list1Sorted = sortComparator == null ? list1: list1.stream()
.sorted(sortComparator)
.collect(Collectors.toList()),
list2Sorted = sortComparator == null ? list2: list2.stream()
.sorted(sortComparator)
.collect(Collectors.toList());
return IntStream.range(0, list1Sorted.size())
.mapToObj(i -> Arrays.<T>asList(list1Sorted.get(i), list2Sorted.get(i)))
.map(list -> employeeMerger.apply(list.get(0), list.get(1)))
.collect(Collectors.toList());
}
zipStreams(list1, list2, (e1, e2) -> e1, Comparator.comparing(Employee::getType));
Collection<Employee> merged = Stream.of(list1, list2)
.flatMap(Collection::stream)
.collect(Collectors.toMap(e -> calculateGroup(e), e -> e, (e1, e2) -> e1)))
.values();