使用函数进行 bean 比较的库函数?

Library function to do bean comparison using Functions?

由于 Java8 比以往任何时候都更容易直接引用方法 (Function/lambda),传统的基于反射的 bean 比较器(例如 common-lang 的 EqualsBuilder)可以现在无需反思即可干净地实施。我想知道这是否已经在任何知名图书馆中可用?

明确一点,我正在寻找类似于此签名的函数:

static <T> boolean equals(T a, T b, Function<T, ?>... propRefs);

或者,使用 Streams 实现它的最简单的方法?

举个例子class喜欢

class Person {
    long id;
    String name, surname;
    int age;

    public long getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public String getSurname() {
        return surname;
    }
    public int getAge() {
        return age;
    }
}

当然,我们可以像

这样用最紧凑的形式来做
@Override
public boolean equals(Object obj) {
    if(obj==this) return true;
    if(!(obj instanceof Person)) return false;
    Person p=(Person)obj;
    return Stream.<Function<Person,?>>of(
          Person::getName, Person::getSurname, Person::getAge)
      .allMatch(f->Objects.equals(f.apply(this), f.apply(p)));
}

但它提出了一个大问题,与简单的形式相比,它是否真的是一场胜利

@Override
public boolean equals(Object obj) {
    if(obj==this) return true;
    if(!(obj instanceof Person)) return false;
    Person p=(Person)obj;
    return this.age==p.age && Objects.equals(this.name, p.name)
      && Objects.equals(this.surname, p.surname);
}

因为它不会增加代码的简洁性,也不能阻止,所以忘记了重要的 属性,也没有忘记此 equals 实现和 hashCode 实现之间的不匹配。

请注意,这也适用于 EqualsBuilder;它在最关键的问题上并没有真正的帮助(实际上,我看不到 任何 优势)。

如果我们想从中获得优势,我们必须求助于一个不太紧凑的实现:

static List<Function<Person,?>> EQ_PROPS=Arrays.asList(
    Person::getName, Person::getSurname, Person::getAge);

@Override
public boolean equals(Object obj) {
    if(obj==this) return true;
    if(!(obj instanceof Person)) return false;
    Person p=(Person)obj;
    return EQ_PROPS.stream().allMatch(f->Objects.equals(f.apply(this), f.apply(p)));
}
@Override
public int hashCode() {
    return Objects.hash(EQ_PROPS.stream().map(f->f.apply(this)).toArray());
}

我们仍然不能保证没有相关的 属性 遗漏,但至少我们有一个责任点需要检查并且我们已经确保相等性和哈希码是一致的, 因此最终 one 优于手动实施。

如果你担心临时对象的性能影响,你可以将所有功能组合到实际操作中,可以在没有任何临时对象的情况下执行:

static List<Function<Person,?>> EQ_PROPS=Arrays.asList(
    Person::getName, Person::getSurname, Person::getAge);

static BiPredicate<Person,Person> EQUAL=EQ_PROPS.stream()
    .<BiPredicate<Person,Person>>map(f -> (a,b) -> Objects.equals(f.apply(a), f.apply(b)))
    .reduce(BiPredicate::and).get();

static ToIntFunction<Person> HASH=EQ_PROPS.stream()
    .<ToIntFunction<Person>>map(f -> a -> Objects.hash(f.apply(a)))
    .reduce((f,g) -> x -> f.applyAsInt(x)*31+g.applyAsInt(x)).get();

@Override
public boolean equals(Object obj) {
    return obj==this || (obj instanceof Person)&& EQUAL.test(this, (Person)obj);
}
@Override
public int hashCode() {
    return HASH.applyAsInt(this);
}