Java 的 Stream.distinct() 如何选择要使用的重复项



在Java中,假设我有一个名为Person的类。它有四个属性:

  1. 长人 ID
  2. 字符串名称
  3. 国际年龄
  4. 列表<字符串>宠物名称

假设我有一个名为 peopleList 的人的数组列表变量:

personId: 1, 姓名:
  1. "Tim", 年龄: 28, 宠物姓名: [白利糖度]
  2. personId: 1, 姓名:
  3. "Tim", 年龄: 28, 宠物姓名: [白利糖度, 牛仔]
  4. personId: 1, 姓名: "Tim", 年龄: 28, 宠物名称: [白利糖度, 牛仔, 菲多]
  5. 人物 ID: 2, 姓名:
  6. "杰米", 年龄: 19, 宠物姓名: []
  7. personId: 3, 姓名: "Fred", 年龄: 23, 宠物姓名: []

我覆盖哈希代码并等于 Person 类是这样的:

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + personId.intValue();
return result;
}
@Override
public boolean equals(Object obj) {
System.out.println("Person equals method");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (personId != other.personId)
return false;
return true;
}

然后我使用peopleList.stream().distinct().collect(Collectors.toList());删除重复项。我最初错了,原来它选择了 Tim 的第一次出现,它只有一只宠物,而不是所有三只宠物的实例。我最初的问题是:distinct() 选择哪个实例?这在下面已经得到了回答。

distinct使用equals方法

您是否阅读了文档,Javadoc forStream#distinct?通过文档编程通常比通过直觉编程效果更好。

第一句话说:

返回由此流的不同元素(根据 Object.equals(Object))组成的流。

是否覆盖了equals方法?对您的问题的编辑说您确实这样做了。

equals方法中,仅比较类型为Long的成员字段personId。那么,您为什么会期望考虑宠物名称列表呢?

同样,如果三个Tim对象的年龄不同,例如 28、48 和 98,那也是无关紧要的。您的代码说只要personId数字相同,就将它们视为相同。

您告诉 JVM 仅检查 id 字段。因此,如果两个Person对象在其personId字段中具有相同的 64 位整数,则它们被视为相等。如果两个数字不同,则两个Person对象不相等。

至于您更一般的问题,即流中遇到的两个或多个相等对象中的哪一个作为distinct的结果被保留:再次阅读文档。

对于

有序流,不同元素的选择是稳定的(对于重复的元素,将保留在遭遇顺序中首先出现的元素。对于无序流,不做稳定性保证。

所以:

  • 如果您有有序流,则第一个对象获胜。将消除后面的任何重复对象。
  • 如果未对流进行排序,则任何对象都可能获胜。你不应该依赖任何特定的人来获胜。

您没有透露您正在使用哪种流。所以我们无法知道它是否被订购。因此,我们无法提供进一步的见解。

equalshashCode必须共享相同的逻辑

equalshashCode的实现应始终使用相同的逻辑。应始终重写hashCode方法以及equals以维护它们之间的一般协定,即:相等的对象必须具有相等的哈希码。

如果为了相等,您比较personId字段,则哈希代码应基于personId字段值。

您在代码中正确执行此操作。但是,您可以通过使用Objects.hash( this.id )来更简单地做到这一点。请参阅代码的此备用实现。

package work.basil.example.distinct;
import java.util.List;
import java.util.Objects;
public final class Person
{
private final Long id;
private final String name;
private final int age;
private final List < String > petNames;
public Person ( Long id , String name , int age , List < String > petNames )
{
this.id = id;
this.name = name;
this.age = age;
this.petNames = petNames;
}
public Long id ( ) { return id; }
public String name ( ) { return name; }
public int age ( ) { return age; }
public List < String > petNames ( ) { return petNames; }
@Override
public boolean equals ( final Object o )
{
if ( this == o ) { return true; }
if ( o == null || getClass() != o.getClass() ) { return false; }
Person person = ( Person ) o;
return id.equals( person.id );
}
@Override
public int hashCode ( )
{
return Objects.hash( this.id );
}
@Override
public String toString ( )
{
return "Person[" +
"id=" + this.id + ", " +
"name=" + this.name + ", " +
"age=" + this.age + ", " +
"petNames=" + this.petNames + ']';
}
}

equalshashCode需要共享相同逻辑的问题在文档、Java 文献和 Stack Overflow 中都有广泛的介绍。搜索以了解更多信息。从这里开始。

下面是使用上述类的示例应用。

package work.basil.example.distinct;
import java.util.List;
public class App
{
public static void main ( String[] args )
{
List < Person > persons =
List.of(
new Person( 1L , "Tim" , 28 , List.of( "Brix" ) ) ,
new Person( 1L , "Tim" , 28 , List.of( "Brix" , "Cowboy" ) ) ,
new Person( 1L , "Tim" , 28 , List.of( "Brix" , "Cowboy" , "Fido" ) ) ,
new Person( 2L , "Jamie" , 19 , List.of() ) ,
new Person( 3L , "Fred" , 23 , List.of() )
);
List < Person > personsDistinct = persons.stream().distinct().toList();
System.out.println( "persons = " + persons );
System.out.println( "personsDistinct = " + personsDistinct );
}
}

运行时:

人员 = [Person[id=1, 姓名=Tim, 年龄=28, 宠物名字=[Brix]], 人[id=1, 姓名=蒂姆, 年龄=28, 宠物名字=[白利糖度, 牛仔]], 人[id=1, 姓名=蒂姆, 年龄=28, 宠物名字=[白利糖度, 牛仔, 菲多]], 人物[id=2, 姓名=杰米, 年龄=19, 宠物名字=[]],

人[id=3, 姓名=弗雷德, 年龄=23, 宠物名字=[

]]] personsDistinct = [Person[id=1, name=Tim, age=28, petNames=[Brix]], Person[id=2, name=Jamie, age=19, petNames=[]], Person[id=3,name=Fred, age=23, petNames=[

]]]

record

提示: 如果您希望自动考虑equals&hashCode的所有成员字段,并且您的类的主要目的是透明且不可变地传达数据,请将您的类定义为记录。

在记录中,默认情况下,编译器隐式创建构造函数、getter、equals&hashCodetoString

此外,您可以在本地定义记录,也可以将其定义为嵌套类或单独的类。

record Person( Long id , String name , int age , List < String > petNames ) { }

您可以尝试实现"可比"接口并使用stream.sorted()首先对list.size()进行排序

代码示例

public class Person implements Comparable<Person>

重写compareTo方法

@Override
public int compareTo(Person person) {
if (this.getPersonId().equals(person.getPersonId())) {
if (person.getPetNames().size() > this.getPetNames().size()) {
return 1;
} else{
return -1;
}
}
return 0;
}

最后

peopleList.stream().sorted().distinct().collect(Collectors.toList())

谢谢

相关内容

  • 没有找到相关文章

最新更新