在Java中,假设我有一个名为Person的类。它有四个属性:
- 长人 ID
- 字符串名称
- 国际年龄
- 列表<字符串>宠物名称字符串>
假设我有一个名为 peopleList 的人的数组列表变量:
personId: 1, 姓名:- "Tim", 年龄: 28, 宠物姓名: [白利糖度] personId: 1, 姓名:
- "Tim", 年龄: 28, 宠物姓名: [白利糖度, 牛仔]
- personId: 1, 姓名: "Tim", 年龄: 28, 宠物名称: [白利糖度, 牛仔, 菲多] 人物 ID: 2, 姓名:
- "杰米", 年龄: 19, 宠物姓名: []
- 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
的结果被保留:再次阅读文档。
有序流,不同元素的选择是稳定的(对于重复的元素,将保留在遭遇顺序中首先出现的元素。对于无序流,不做稳定性保证。
所以:
- 如果您有有序流,则第一个对象获胜。将消除后面的任何重复对象。
- 如果未对流进行排序,则任何对象都可能获胜。你不应该依赖任何特定的人来获胜。
您没有透露您正在使用哪种流。所以我们无法知道它是否被订购。因此,我们无法提供进一步的见解。
equals
和hashCode
必须共享相同的逻辑
equals
和hashCode
的实现应始终使用相同的逻辑。应始终重写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 + ']';
}
}
equals
和hashCode
需要共享相同逻辑的问题在文档、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
&hashCode
和toString
。
此外,您可以在本地定义记录,也可以将其定义为嵌套类或单独的类。
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())
谢谢