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

时间:2016-04-15 18:12:55

标签: java generics interface builder-pattern

我有一个界面Foo和一个实现Foo的枚举栏。然后我使用一个简单的构建器模式类Result。请参阅下面的代码。

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&lt; Foo&gt;变量这很好用:

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

但是下面给出了一个编译错误:

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

不兼容的类型。必填:结果&lt; Foo&gt ;,找到:结果&lt; Bar&gt;

有人可以解释一下原因吗?

2 个答案:

答案 0 :(得分:3)

  

有人可以解释一下原因吗?

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

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

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

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

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

其中set的返回类型为Bar

因为

Result<Bar>不是Result<Foo>,因此一个表达式不能分配给另一个。

答案 1 :(得分:3)

由于Java 8的目标类型推断,第一个示例进行了编译,因为T被推断为Foo,而不是Bar

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

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

第二个例子没有编译,因为T必须推断为Bar,而不是Foo

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

在赋值运算符将结果赋给set()之前调用r1方法。在此,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方法只是一种工厂方法。 Builder Pattern使用一个单独的类,其目的是构造目标类的实例。

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;
    }
}