是否可以检测不兼容类型与Java类型系统的比较?

Phi*_*ßen 9 java generics type-systems type-erasure type-safety

我正在尝试编写一个辅助函数来以类型安全的方式比较两种类型:

typesafeEquals("abc", new Integer(42)); // should not compile
Run Code Online (Sandbox Code Playgroud)

我的第一次直接尝试失败了:

<T> boolean typesafeEquals(T x, T y) { // does not work!
    return x.equals(y);
}
Run Code Online (Sandbox Code Playgroud)

那么,问题是T可以推断出是一个Object.现在,我想知道typesafeEquals在Java类型系统中是否无法实现.

我知道FindBugs找到的工具可以警告不兼容类型的比较.无论如何,要么看到没有外部工具的解决方案,要么解释为什么它是不可能的,这将是有趣的.


更新:我认为答案是不可能的.但是,我没有证据支持这种说法.只是似乎很难找到适用于所有情况的解决方案.

一些答案很接近,但我相信Java的类型系统不支持在所有普遍性中解决问题.

Mak*_*oto 6

添加第三个参数 - Class<T>方法的类型 - 将有助于解决此类问题.正如你所说,你可以得到一个ObjectT; 除非你为它添加边界,否则它将无法识别除了之外的任何东西Object.

虽然添加类类型会强制您在每次使用该方法时将其放入,但它会强制执行编译器和隐含的合约,即您将它们作为特定类进行比较.

<T> boolean typesafeEquals(T x, T y, Class<T> clazz) {
    return x.equals(y);
}
Run Code Online (Sandbox Code Playgroud)

请注意,从开发人员的角度来看,这种检查有点过度反应.情况应该是两个等同的对象应该简单地返回false.

由于它是一个下拉更换为的要求Object.equals,给周围的方法行之有效的松散型的安全性,因为它是根本不可能的.虽然泛型是解决这个问题的理想方法,但是Java 5中存在泛型,而equals从一开始就存在泛型.

  • 以下是可以接受的吗?`typesafeEquals("abc",new Integer(42),Object.class);`我们明确地说我们想要`Object`. (4认同)

sup*_*cat 5

询问实例Apple是否等于的特定实例Orange不会给它带来任何麻烦。它应该简单地观察到,因为它不认为自己等于任何非类型的对象Apple,并且因为它被赋予了对非类型对象的引用Apple,所以它并不认为自己等于传入的对象。

此外,某些接口具有合同,要求其实现将其视为自己与满足某些条件的任何其他实现相同。因此,不相关类型的两个对象都可能实现一个接口,该接口要求两个对象彼此报告相等。即使引用的类型没有共同的接口,除非至少密封了一个接口,否则从它们派生的类型的实例将有可能共享一个接口,这将迫使相互对等关系。

因此,尽管在某些情况下静态分析可能会发现由两个不同类的引用标识的对象可能无法认为自己相等,但是所需的静态分析级别将远远超出通用类型系统可能封装的所有对象。 ,则需要使用不同类型的equals方法检查代码]。

我建议,虽然理论上编译器可以确定代码试图比较不可能相等的事物的情况可能会很好,但是equals定义规则的方式(包括允许的事实以及在某些情况下)所需的大小写-对于不相关的类的实例进行相互比较,以确保彼此之间的比较)通常意味着您无法找到所需的内容。如果可以询问一个实例Apple是否等于任何特定实例Fruit,那么必须可以询问它是否等于派生自任何类型的任何特定实例Fruit。这个Apple问题应该傻吗?它应该简单地回答它。


Hug*_* M. 2

是否可以检测与 Java 类型系统不兼容的类型的比较?
[...]
我相信Java的类型系统不支持解决所有通用问题。

类型系统本身无法做到这一点,至少不能以一种适用于绝对通用的所有类型的通用方式,因为它无法告诉你的类型在其equals实现中做了什么(正如 supercat 的答案中已经说过的那样),Foo可以接受与 a 进行比较Bar,该逻辑被刻在代码本身中,因此编译器无法检查。


话虽这么说,对于“类型安全”的“受限”定义,如果您可以声明每次使用的期望,您自己的方法几乎就在那里,只需将其设为静态,使用类名调用它,并显式指定预期类型:

public class TypeSafe {

    public static <T> boolean areEqual(T x, T y) {
        return x.equals(y);
    }

    void test() {
        TypeSafe.areEqual("a", 1); // Compiles because no restriction is present.
                                   // Both are resolved to Serializable
                                   // [there is not only "Object" in common ;)]

        TypeSafe.<CharSequence>areEqual("a", 1); // Does not compile
        //                                   ^
        //          Found: int, required: java.lang.CharSequence
    }
}
Run Code Online (Sandbox Code Playgroud)

类似于 Makoto 的答案,不传递类型作为参数,以及 Jeremy 的答案,不创建新对象。(都投了赞成票。)

虽然它会有点误导,因为TypeSafe.<Number>areEqual(1f, 1d)编译但返回 false。它可能会产生错误的含义“这两个数字相等吗?” (这不是它的作用)。所以你必须知道你在做什么...


现在,即使您比较两个Long值,一个可能是以毫秒为单位的基于纪元的时间戳,另一个以秒为单位,2 个相同的原始值也不会“相等”。

在 Java 8 中,我们拥有类型注释,并且编译器可以使用注释处理器进行检测,以根据这些注释执行其他检查(请参阅检查器框架)。

假设我们有这些方法返回以毫秒和秒为单位的持续时间,如注释@m@s由框架提供)所指定:

@m long getFooInMillis() { /* ... */ }
@s long getBarInSeconds() { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

(当然,在这种情况下,最好首先使用正确的类型,例如Instantor Duration...但请忽略它一分钟)

这样,您可以使用检查器框架和注释更加具体地了解作为泛型类型参数传递给方法的约束:

long t1 = getFooInMillis();
long t2 = getBarInSeconds();

TypeSafe.<Long>areEqual(t1, t2);    // OK, just as we've seen earlier

TypeSafe.<@m Long>areEqual(t1, t2); // Error: incompatible types in argument
//                         ^^  ^^
// found   : @UnknownUnits long
// required: @m Long

@m long t1m = getFooInMillis();
@s long t2s = getBarInSeconds();
@m long t2m = getFooInMillis();

TypeSafe.<Long>areEqual(t1m, t2s);    // OK

TypeSafe.<@m Long>areEqual(t1m, t2s); // Error: incompatible types in argument
//                              ^^^
// found   : @s long
// required: @m Long

TypeSafe.<@m Long>areEqual(t1m, t2m); // OK
Run Code Online (Sandbox Code Playgroud)