Java 以接口作为类型参数并使用构建器模式的泛型

Java generics with interface as type argument and using builder pattern

我有一个接口 Foo 和一个实现 Foo 的枚举 Bar。然后我使用一个简单的构建器模式 class 结果。请参阅下面的代码。

public interface Foo {}

public enum Bar implements Foo { BAR }

public class Result<T> {
    public Result(T result) {}

    public static <T> Result<T> of(T result) {
        return new Result<>(result);
    }

    public Result<T> set() {
        return this;
    }
}

当尝试构建 Bar.BAR 类型的结果并将其分配给 Result 变量时,这工作正常:

Result<Foo> r1 = Result.of(Bar.BAR);

但是编译报错:

Result<Foo> r2 = Result.of(Bar.BAR).set();

不兼容的类型。要求:结果,找到:结果

谁能解释一下为什么?

Could anyone please explain why?

因为泛型很难,编译器很难猜出你的意思。

它在 Java 8 中变得更好。您的第一次尝试将无法在 Java 7 及更低版本上编译。

of 是一种通用方法。 Java 上的类型推断查看周围的上下文,在这种情况下是分配变量的类型,以确定目标类型参数。在您的示例中,它可以推断 Foo,因为 Bar.BARFoo.

的子类型

在您的第二个示例中,链接方​​法使事情变得更加困难。 set 不是通用方法。因此,它取决于调用它的表达式的类型。由于方法链接,编译器不能依赖于 of 调用的上下文。它采用它所看到的类型,即。 Bar 对于 Bar.BAR。然后调用变为(澄清类型)

((Result<Bar>) Bar.BAR).set();

其中 set 有一个 return 类型的 Bar

并且因为

  • Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?

a Result<Bar> 不是 Result<Foo> 所以一个表达式不能分配给另一个。

第一个示例编译是因为 T 被推断为 Foo,而不是 Bar,作为 Java 8 的目标类型推断的结果。

Result<Foo> r1 = Result.of(Bar.BAR);

它编译是因为 BarFoo,所以它可以作为参数传递给 of 方法。

第二个示例无法编译,因为必须将 T 推断为 Bar,而不是 Foo

Result<Foo> r1 = Result.of(Bar.BAR).set();

在赋值运算符将结果赋值给r1之前调用set()方法。这里必须孤立地考虑Result.of(Bar.BAR).set(),没有考虑r1的类型,所以T被推断为Bar.

此外,Java 的泛型是不变的,所以即使 BarFooResult<Bar> 也不是 Result<Foo>。但是您可以使用通配符来解决这种情况。

Result<? extends Foo> r1 = Result.of(Bar.BAR).set();

您的第一个示例当然是另一种解决方法。

另一种解决方法,如 Paul Boddington 的评论中所述,是对泛型方法使用显式类型参数 of。这明确地将 T 设置为 Foo.

Result<Foo> r2 = Result.<Foo>of(Bar.BAR).set();

还有,这不是Builder Pattern;您的 of 方法只是一个工厂方法。构建器模式使用单独的 class,其全部目的是构造目标 class.

的实例
public class Result<T> {
    // Prevent anyone except the Builder class from instantiating
    // this class by making the constructor private.
    private Result(T result) {}

    public static class Builder<T>
    {
        private T result;

        public void setResult(T result)
        {
            this.result = result;
        }

        public Result<T> build()
        {
            return new Result(result);
        }
    }

    public Result<T> set() {
        return this;
    }
}