将对象转换为可比较类型,然后进行比较

7

我真的很想喜欢泛型,但到目前为止,它们带来的麻烦超过了任何好处。请,请向我展示我错了。

我理解在使用无泛型框架(Spring、Hibernate)时添加 @SuppressWarnings("unchecked") 的必要性。这本身就大大降低了泛型的价值,还需要传递类到构造函数中以避免擦除的问题。然而,真正的难点似乎总是在转换上。我通常会尝试一段时间来正确使用语法,但最终放弃纯粹的尝试,添加 @SuppressWarnings 并继续我的生活。

以下是一个例子:我正在反射一个 bean 来查找两个实例之间的差异。某些属性实现了 Comparable,使得 (a.equals(b) == false) 但 (a.compareTo(b) == 0)(例如 BigDecimal、Date)。在这些情况下,我希望将属性视为相同。

MyObject original = getOriginal();
MyObject updated = getUpdated();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(MyObject.class)) {
    // Assume I'm putting in the try/catch block
    Object pOriginal = pd.getReadMethod().invoke(original, (Object[]) null);
    Object pUpdated = pd.getReadMethod().invoke(updated, (Object[]) null);

    boolean isPropertySame;

    if (Comparable.class.isAssignableFrom(pOriginal.getClass())) {
        // Type safety: The method compareTo(Object) belongs to the raw type Comparable. References to generic type Comparable<T> should be parameterized
        isPropertySame = Comparable.class.cast(pOriginal).compareTo(Comparable.class.cast(pUpdated)) == 0;

        // The method compareTo(capture#19-of ?) in the type Comparable<capture#19-of ?> is not applicable for the arguments (capture#21-of ? extends Comparable)
        Comparable<?> comparable = Comparable.class.cast(pOriginal);
        isPropertySame  = comparable.compareTo(comparable.getClass().getTypeParameters()[0].getGenericDeclaration().cast(pUpdated)) == 0;

        // Even if I get the generics right, I still get an error if pOriginal is java.sql.Timestamp and pUpdated is java.util.Date (happens all the time with Hibernate).
        isPropertySame = (help);

    } else {
        isPropertySame = pOriginal.equals(pUpdated);
    }

    if (!isPropertySame) {
        PropertyDelta delta = new PropertyDelta(pd, pOriginal, pUpdated);
        dao.save(delta);
    }
}

你有什么想法可以放到(帮助)中吗?


1
TimestampDate存在的问题是一个众所周知的问题:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4631234。它已经在Java 6中修复(可能也在5u6中修复)。 - notnoop
变量“originalValue”和“updatedValue”分别应该是“pOriginal”和“pUpdated”吗? - erickson
我很高兴时间戳/日期混淆问题得到了解决。但恐怕这对我没有帮助,因为我还停留在1.5版本,不过了解这个对未来很有好处。 - Monkey Boson
@erickson 感谢指出错别字...已修复! - Monkey Boson
4个回答

4

在我看来,这似乎是走了一条艰难的路线。你可以让你的bean实现Comparable接口,这样你就可以直接进行比较,或者你可以创建一个比较器 -

public class Bean implements Comparable<Bean> {...}

   public int compareTo(Bean other){ ... }
}

或者
public int compare(Bean a, Bean b){ 
  Comparator<Bean> c = new Comparator<Bean>(){ 
    public int compareTo(Bean a, Bean b){ ... }
    public boolean equals(Object o){.. }
 };
   return c.compare(a, b);
}

我同意你的观点,Java 泛型有点...复杂。


感谢您的帮助!很抱歉,我过于简化了原始示例,以至于类特定比较器可以工作。我现在已经更新了示例,更准确地反映了我的要求,即收集每个已更改属性的增量记录。您还可以想象,我需要深度扫描一个非常大的对象图来生成许多这样的增量记录。 - Monkey Boson

2
我不太明白仅仅执行以下操作有什么问题:

MyObject original = getOriginal();
MyObject updated = getUpdated();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(MyObject.class)) {
    // Assume I'm putting in the try/catch block
    Object pOriginal = pd.getReadMethod().invoke(original, (Object[]) null);
    Object pUpdated = pd.getReadMethod().invoke(updated, (Object[]) null);

    boolean isPropertySame;
    if (pOriginal instanceof Comparable) {
        @SuppressWarnings("unchecked")
        Comparable<Object> originalValue = (Comparable<Object>) pOriginal;
        @SuppressWarnings("unchecked")
        Comparable<Object> updatedValue = (Comparable<Object>) pUpdated;
        isPropertySame = originalValue.compareTo(updatedValue) == 0;
    } else {
        isPropertySame = pOriginal.equals(pUpdated);
    }

    if (!isPropertySame) {
        PropertyDelta delta = new PropertyDelta(pd, pOriginal, pUpdated);
        dao.save(delta);
    }
}

在您的情况下使用Class.cast对类型安全性没有任何帮助。


我正在尝试避免这样做,因为它会抛出一个警告:“Comparable是原始类型。对泛型类型Comparable<T>的引用应该被参数化”。现在,我可以在我的方法顶部放一个@SuppressWarnings来消除这个警告,但我希望得到一个“纯粹”的答案。这个解决方案完全避免了泛型。你关于cast()使用的观点是完全正确的。它与你的解决方案中的转换一样脆弱。它存在的唯一原因是因为我正在尝试进行运行时类型反射/操作。 - Monkey Boson
鉴于泛型在运行时会被擦除,因此泛型对你没有任何帮助。我只是修改了代码以使其更加“通用”。比起满足编译器而不提供额外类型安全性的情况,让代码易读更为重要。 - notnoop
在运行时,难道不可能反射类型参数(即Comparable<X>中的“X”)吗?我认为你可以做类似这样的事情:comparable.getClass().getTypeParameters()[0].getGenericDeclaration()。漂亮吗?绝对不是。考虑到该方法中的所有代码都是用Java1.5编写的,我认为应该能够编写一些不会引发任何警告/错误且类型安全的代码,而无需抑制任何内容。这是一个美好的愿望吗? - Monkey Boson

1

看起来假定如果一个类实现了Comparable,类型参数就是该类本身。也就是说,"class X implements Comparable<X>"。如果是这种情况,那么这样说就有道理:

X a = new X(1), b = new X(2);
a.compareTo(b);

然而,定义一个类似于“class X implements Comparable<Y>”的类是完全可能的。然后,可以尝试像这样做...

X a = new X(1), b = new X(2);
a.compareTo((Y) b);

但显然,会引发ClassCastException,因为b不是Y的实例。

因此,警告是有效的。使用原始类型的代码不是类型安全的,并且可能在运行时引发异常。


这里的问题是在运行时你不知道Bean属性类型。你无法以任何方便的方式知道Comparable的类型参数。 - notnoop
@erickson 我完全同意你的分析。然而,难道没有一种方法可以在运行时实现正确的类型转换吗?这样我就可以测试两个对象是否可相互比较,然后进行比较操作了。考虑到我们可以使用的所有运行时方法,似乎应该能够做到这一点... - Monkey Boson

0

鉴于我找不到“纯粹”的方法来做这件事,而且我一直遇到一些特殊情况(比如,除了这个之外,还有处理集合属性的困难),所以我决定让我的增量生成方法变得更加简单。我意识到我只测试了9种不同类型的对象,所以我可以测试我正在比较的9种对象中的哪一个,然后将其转换为该对象并进行特定于对象的测试。

以这种方式实现大约需要一个小时,即使我必须每次任何对象发生更改时重新编译,我认为即使我花费几天时间进行维护,我仍然处于盈利状态。

因此,最终,我想答案是没有答案。Java泛型是以这样一种方式实现的,以至于不可能避免偶尔抑制编译器警告并冒着运行时类转换异常的风险。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接