查找集合中的对象是否具有相同的字段值
Find If Objects in Collection have same field values
我正在尝试创建一个编辑器来编辑在 JTree 中选择的多个设备。
如果集合中的项目都对某个字段具有相同的值,我将在编辑器表单中显示该值。如果它们有不同的值,我将显示 "Multiple Values"
我曾尝试使用类似的东西,但仅限于比较两个项目。我想对集合中的所有项目执行此操作。
private static List<String> difference(Student s1, Student s2) {
List<String> values = new ArrayList<>();
for (Field field : s1.getClass().getDeclaredFields()) {
// You might want to set modifier to public first (if it is not public yet)
field.setAccessible(true);
Object value1 = field.get(s1);
Object value2 = field.get(s2);
if (value != null && value != null) {
System.out.println(field.getName() + "=" + value1);
System.out.println(field.getName() + "=" + value2);
if (!Objects.equals(value1, value2) {
values.add(value2);
}
}
}
return values;
}
有人可以举例说明您如何确定集合中的对象具有相同值的字段吗?
我的哈希和等于代码如下。我假设这可以使用 Collection 的内置方法来完成,但我希望能举个例子。
@Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (this.isSelected ? 1 : 0);
hash = 47 * hash + Objects.hashCode(this.user);
hash = 47 * hash + Objects.hashCode(this.password);
hash = 47 * hash + Objects.hashCode(this.address);
hash = 47 * hash + (int) (this.addressAsLong ^ (this.addressAsLong >>> 32));
hash = 47 * hash + this.port;
hash = 47 * hash + Objects.hashCode(this.vendor);
hash = 47 * hash + Objects.hashCode(this.model);
hash = 47 * hash + Objects.hashCode(this.OS);
hash = 47 * hash + Objects.hashCode(this.description);
hash = 47 * hash + Objects.hashCode(this.version);
hash = 47 * hash + Objects.hashCode(this.hostName);
hash = 47 * hash + Objects.hashCode(this.domain);
hash = 47 * hash + Objects.hashCode(this.deviceType);
hash = 47 * hash + Objects.hashCode(this.Location);
hash = 47 * hash + Objects.hashCode(this.SerialNumber);
// hash = 47 * hash + Objects.hashCode(this.parent);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DefaultDevice other = (DefaultDevice) obj;
if (this.isSelected != other.isSelected) {
System.out.println("isSelected");
return false;
}
if (this.addressAsLong != other.addressAsLong) {
// System.out.println("long");
return false;
}
if (this.port != other.port) {
//System.out.println("port");
return false;
}
if (!Objects.equals(this.user, other.user)) {
// System.out.println("user");
return false;
}
if (!Objects.equals(this.password, other.password)) {
//System.out.println("pass");
return false;
}
if (!Objects.equals(this.vendor, other.vendor)) {
//System.out.println("ven");
return false;
}
if (!Objects.equals(this.model, other.model)) {
//System.out.println("mod");
return false;
}
if (!Objects.equals(this.OS, other.OS)) {
// System.out.println("os");
return false;
}
if (!Objects.equals(this.description, other.description)) {
//System.out.println("des");
return false;
}
if (!Objects.equals(this.version, other.version)) {
//System.out.println("ver");
return false;
}
if (!Objects.equals(this.hostName, other.hostName)) {
// System.out.println("hostNa");
return false;
}
if (!Objects.equals(this.domain, other.domain)) {
// System.out.println("dom");
return false;
}
if (!Objects.equals(this.deviceType, other.deviceType)) {
// System.out.println("dt");
return false;
}
if (!Objects.equals(this.Location, other.Location)) {
//System.out.println("loc");
return false;
}
if (!Objects.equals(this.SerialNumber, other.SerialNumber)) {
// System.out.println("sn");
return false;
}
return true;
}
考虑到可访问性、使用 getters/setters 而不是字段访问等的综合解决方案将需要相当多的努力,但在您描述的范围内,算法可能如下所示:
- 在你的方法中return一个你迭代的类型的对象作为结果,并为不匹配的情况取一个具有默认值的对象(如果你愿意,用
Map<String, Object>
替换那些)
- 从非空集合中取出第一项并确定其class
- 遍历所有声明的字段,每个字段都从第一个项目中获取值,然后遍历项目直到第一个不匹配
- 如果不匹配,从默认值分配值并中断迭代
- 将找到的值设置到结果字段并移动到下一个字段
在代码中它看起来像这样:
class ObjectMatcher {
// NPE on null items
// assumes public default constructor for T is available
public <T> T match(Collection<T> items, T defaults) {
if (items.isEmpty()) {
return defaults;
}
try {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) items.iterator().next().getClass();
Field[] fields = clazz.getDeclaredFields();
T res = clazz.newInstance();
for (Field field : fields) {
boolean firstItem = true;
Object match = null;
for (T item : items) {
Object value = field.get(item);
if (firstItem) {
match = value;
}
else if (!Objects.equals(value, match)) {
match = field.get(defaults);
break;
} // otherwise keep the match as is
firstItem = false;
}
field.set(res, match);
}
return res;
}
catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
下面是字段不匹配、匹配和 null/non-null 不匹配情况的简单测试:
@Test
public void match_onMistmatchMatchAndNull_ok() {
Student s1 = new Student("Andrew", "Physics", null);
Student s2 = new Student("Joe", "Physics", 3.45);
Student s3 = new Student("Nicki", "Physics", 2.39);
Student defaults = new Student("Multiple Names", "Multiple Courses", 1.0);
ObjectMatcher matcher = new ObjectMatcher();
Student res = matcher.match(Arrays.asList(s1, s2, s3), defaults);
assertEquals("Multiple Names", res.name);
assertEquals("Physics", res.course);
assertEquals(1.0, res.grade.doubleValue(), 0.001);
}
您将在 dedicated GitHub repo 上找到测试 class 的完整代码。
我正在尝试创建一个编辑器来编辑在 JTree 中选择的多个设备。
如果集合中的项目都对某个字段具有相同的值,我将在编辑器表单中显示该值。如果它们有不同的值,我将显示 "Multiple Values"
我曾尝试使用类似的东西,但仅限于比较两个项目。我想对集合中的所有项目执行此操作。
private static List<String> difference(Student s1, Student s2) {
List<String> values = new ArrayList<>();
for (Field field : s1.getClass().getDeclaredFields()) {
// You might want to set modifier to public first (if it is not public yet)
field.setAccessible(true);
Object value1 = field.get(s1);
Object value2 = field.get(s2);
if (value != null && value != null) {
System.out.println(field.getName() + "=" + value1);
System.out.println(field.getName() + "=" + value2);
if (!Objects.equals(value1, value2) {
values.add(value2);
}
}
}
return values;
}
有人可以举例说明您如何确定集合中的对象具有相同值的字段吗?
我的哈希和等于代码如下。我假设这可以使用 Collection 的内置方法来完成,但我希望能举个例子。
@Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (this.isSelected ? 1 : 0);
hash = 47 * hash + Objects.hashCode(this.user);
hash = 47 * hash + Objects.hashCode(this.password);
hash = 47 * hash + Objects.hashCode(this.address);
hash = 47 * hash + (int) (this.addressAsLong ^ (this.addressAsLong >>> 32));
hash = 47 * hash + this.port;
hash = 47 * hash + Objects.hashCode(this.vendor);
hash = 47 * hash + Objects.hashCode(this.model);
hash = 47 * hash + Objects.hashCode(this.OS);
hash = 47 * hash + Objects.hashCode(this.description);
hash = 47 * hash + Objects.hashCode(this.version);
hash = 47 * hash + Objects.hashCode(this.hostName);
hash = 47 * hash + Objects.hashCode(this.domain);
hash = 47 * hash + Objects.hashCode(this.deviceType);
hash = 47 * hash + Objects.hashCode(this.Location);
hash = 47 * hash + Objects.hashCode(this.SerialNumber);
// hash = 47 * hash + Objects.hashCode(this.parent);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DefaultDevice other = (DefaultDevice) obj;
if (this.isSelected != other.isSelected) {
System.out.println("isSelected");
return false;
}
if (this.addressAsLong != other.addressAsLong) {
// System.out.println("long");
return false;
}
if (this.port != other.port) {
//System.out.println("port");
return false;
}
if (!Objects.equals(this.user, other.user)) {
// System.out.println("user");
return false;
}
if (!Objects.equals(this.password, other.password)) {
//System.out.println("pass");
return false;
}
if (!Objects.equals(this.vendor, other.vendor)) {
//System.out.println("ven");
return false;
}
if (!Objects.equals(this.model, other.model)) {
//System.out.println("mod");
return false;
}
if (!Objects.equals(this.OS, other.OS)) {
// System.out.println("os");
return false;
}
if (!Objects.equals(this.description, other.description)) {
//System.out.println("des");
return false;
}
if (!Objects.equals(this.version, other.version)) {
//System.out.println("ver");
return false;
}
if (!Objects.equals(this.hostName, other.hostName)) {
// System.out.println("hostNa");
return false;
}
if (!Objects.equals(this.domain, other.domain)) {
// System.out.println("dom");
return false;
}
if (!Objects.equals(this.deviceType, other.deviceType)) {
// System.out.println("dt");
return false;
}
if (!Objects.equals(this.Location, other.Location)) {
//System.out.println("loc");
return false;
}
if (!Objects.equals(this.SerialNumber, other.SerialNumber)) {
// System.out.println("sn");
return false;
}
return true;
}
考虑到可访问性、使用 getters/setters 而不是字段访问等的综合解决方案将需要相当多的努力,但在您描述的范围内,算法可能如下所示:
- 在你的方法中return一个你迭代的类型的对象作为结果,并为不匹配的情况取一个具有默认值的对象(如果你愿意,用
Map<String, Object>
替换那些) - 从非空集合中取出第一项并确定其class
- 遍历所有声明的字段,每个字段都从第一个项目中获取值,然后遍历项目直到第一个不匹配
- 如果不匹配,从默认值分配值并中断迭代
- 将找到的值设置到结果字段并移动到下一个字段
在代码中它看起来像这样:
class ObjectMatcher {
// NPE on null items
// assumes public default constructor for T is available
public <T> T match(Collection<T> items, T defaults) {
if (items.isEmpty()) {
return defaults;
}
try {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) items.iterator().next().getClass();
Field[] fields = clazz.getDeclaredFields();
T res = clazz.newInstance();
for (Field field : fields) {
boolean firstItem = true;
Object match = null;
for (T item : items) {
Object value = field.get(item);
if (firstItem) {
match = value;
}
else if (!Objects.equals(value, match)) {
match = field.get(defaults);
break;
} // otherwise keep the match as is
firstItem = false;
}
field.set(res, match);
}
return res;
}
catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
下面是字段不匹配、匹配和 null/non-null 不匹配情况的简单测试:
@Test
public void match_onMistmatchMatchAndNull_ok() {
Student s1 = new Student("Andrew", "Physics", null);
Student s2 = new Student("Joe", "Physics", 3.45);
Student s3 = new Student("Nicki", "Physics", 2.39);
Student defaults = new Student("Multiple Names", "Multiple Courses", 1.0);
ObjectMatcher matcher = new ObjectMatcher();
Student res = matcher.match(Arrays.asList(s1, s2, s3), defaults);
assertEquals("Multiple Names", res.name);
assertEquals("Physics", res.course);
assertEquals(1.0, res.grade.doubleValue(), 0.001);
}
您将在 dedicated GitHub repo 上找到测试 class 的完整代码。