【问题标题】:How to bind generic type of static nested class from a static factory method?如何从静态工厂方法绑定静态嵌套类的泛型类型?
【发布时间】:2019-09-25 04:16:18
【问题描述】:

我正在使用Effective Java 中概述的 Builder 模式的变体,我对 Java 泛型的行为感到困惑。

考虑以下类:

public class Result<T extends Resource> {
    //final members
    final boolean success;
    final T resource;

    //private constructor
    private Result(Builder<T> builder) {
        success = builder.success;
        resource = builder.resource;
    }

    //getters
    public boolean isSuccess() {
        return success;
    }
    public T getResource() {
        return resource;
    }

    //static factory method to get a builder
    public static <T2 extends Resource> Builder<T2> builder(Class<T2> clazz) {
        return new Builder<T2>();
    }

    //nested Builder class
    public static class Builder<T extends Resource> {
        boolean success;
        T resource;

        private Builder() { }

        public Builder<T> success(boolean success) {
            this.success = success;
            return this;
        }

        public Builder<T> resource(T resource) {
            this.resource = resource;
            return this;
        }

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

当我有一个具体的资源子类时,这似乎很有效:

Result<Device> result = Result.builder(Device.class).build();

但是,当我从另一个定义了具有 Resource 的通用子类的方法的类中使用它时,它甚至无法编译:

public <T extends Resource> Result<T> createResult(T resource) {
    return Result.builder(resource.getClass()).build();
}

编译错误是:

类型不匹配:无法从 Result 扩展到 Result

既然 T 和 T2 都扩展了 Resource,为什么它不能确定 Result.Builder.build() 应该返回一个泛型类型 T 的 Result?

我发现的一种解决方法是放弃静态 builder() 方法并改用类似的方法:

public <T extends Resource> Result<T> createResult(T resource) {
    return new Result.Builder<T>().build();
}

似乎应该有一种方法可以使用静态工厂方法和隐藏的构造函数来做到这一点......我错过了什么吗?

【问题讨论】:

  • 有趣的是,如果我更新静态 builder() 方法以接受 T2 类型的实例(而不仅仅是它的类),它会起作用:public static &lt;T2 extends Resource&gt; Builder&lt;T2&gt; builder(T2 dummy) { return new Builder&lt;T2&gt;(); } 但强迫用户通过似乎有点傻一个 T 的虚拟实例只是为了让泛型工作。
  • 我建议看一下 Spring Boot 中使用的构建器层次结构,尤其是 HttpSecurity 构建器。泛型可能会有点曲折,但确实可以解决您正在查看的问题。

标签: java generics


【解决方案1】:

编译错误的原因是由于Object.getClass()方法。根据java文档:

返回此对象的运行时类。返回的 Class 对象是被表示类的静态同步方法锁定的对象。

实际结果类型是 Class where |X|是调用 getClass 的表达式的静态类型的擦除。 例如,此代码片段中不需要强制转换:

数字 n = 0;
类 c = n.getClass();

拨打return Result.builder(resource.getClass()).build();

  1. resource.getClass() 将返回 Class&lt;? extends Resource&gt; 因为 ResourceT 的擦除
  2. Result.builder(resource.getClass()) 方法将返回 Builder&lt;? extends Resource&gt;
  3. Result.builder(resource.getClass()).build() 将返回 Result&lt;? extends Resource&gt;

T 的类型信息在步骤 1 中丢失。因此编译错误显示为返回类型不兼容。可以进行以下更改以解决该错误。

  1. 将构建器方法更改为接受 T 实例而不是 Class&lt;T&gt; 作为评论中的状态。
  2. 将调用者更改为将Class&lt;T&gt; 而不是Class&lt;? extends Resource&gt; 传递给builder 方法。

修改代码 sn-p:

  // Caller
  // Solution 1
  public <T extends Resource> Result<T> createResult(T resource) {
    return Result.builder(resource).build();
  }

  // Solution 2
  public <T extends Resource> Result<T> createResult(Class<T> resourceClass) {
    return Result.builder(resourceClass).build();
  }


public class Result<T extends Resource> {

  ...
  //Solution 1
  public static <T2 extends Resource> Builder<T2> builder(T2 instance) {
    return new Builder<T2>();
  }

  // static factory method to get a builder
  // Solution 2
  public static <T2 extends Resource> Builder<T2> builder(Class<T2> clazz) {
    return new Builder<T2>();
  }
  ...

参考:Object Java DocErasure of Generic Type

【讨论】:

  • 哇,感谢您简洁明了的回答。我添加了解决方案 2 方法,果然,现在我可以使用 createResult(resource.getClass()); 调用该方法,它不再抱怨。太奇怪了,添加额外的跃点会安抚编译器......
  • 实际上,从头开始,当我以这种方式调用解决方案 2 时(由于擦除),解决方案 2 仍然返回泛型类型为 Result&lt;? extends Resource&gt; 的结果。我想我需要将 Class 添加到许多方法签名中才能获得传入的实际类型。我倾向于直接使用构造函数......但现在有了更好的理解。
猜你喜欢
  • 1970-01-01
  • 2021-07-26
  • 1970-01-01
  • 2010-10-16
  • 1970-01-01
  • 2015-03-09
  • 1970-01-01
  • 2011-01-19
  • 2012-02-10
相关资源
最近更新 更多