具有泛型的静态工厂方法

时间:2018-08-04 09:55:16

标签: java generics

我在使用Java泛型时遇到了麻烦。我有一个通用基类override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_web_view) val webView = findViewById<WebView>(R.id.webv) val url: String = intent.getStringExtra("targetURL") ?: "" webView.webViewClient = WebViewClient() webView.loadUrl(url) btn_go_back.setOnClickListener( webView.reload(url} } ,它定义了一个静态工厂方法来创建具有初始值的A的实例。我这样做是为了在创建A的实例时设置一些初始值,而不是指定一个设置初始值的构造函数,因为这会涉及在构造函数中调用可重写的方法(强烈建议不要这样做)。

A

我还有一个类public class A<T> { private List<T> values; public A() { this.values = new ArrayList<>(); } public void setValues(List<T> values) { this.values.clear(); for (T value : values) { this.values.add(this.modify(value)); } } protected T modify(T value) { // Make modifications to value before it is stored. My real implementation actually does stuff. return value; } public static <T> A<T> create(List<T> values) { A<T> a = new A<>(); a.setValues(values); return a; } } ,它扩展了B并指定了要使用的通用类型。

A

我希望能够在public class B extends A<Integer> { public B() { super(); } } 中使用create静态方法来创建带有初始项目的B实例。尝试致电B时出现错误消息:

  

错误:(5,23)java:不兼容的类型:不存在类型变量T,E的实例,因此A符合B

因此,我决定尝试定义特定于B.create(...)类的create的实现:

B

但是这次我得到了另一个错误:

  

错误:(8,21)java:名称冲突:B中的create(java.util.List)和A中的create(java.util.List)具有相同的擦除,但没有隐藏另一个

虽然我了解每个错误的实际含义,但我不知道如何解决它们。如何在public static B create(List<String> values) { B b = new B(); b.setValues(values); return b; } 上定义可用于任何子类的静态工厂方法,或者如何在A上定义静态工厂方法以及在{{1 }}覆盖/隐藏A的实现?

3 个答案:

答案 0 :(得分:1)

好吧,您所走的道路是Guava went,现在是somewhat regret it。也就是说,您要将静态工厂方法放在非最终类中。

我想提出一种不同的方法:创建一个“ 带有复数名称(例如SomeType-> SomeTypes)的“同伴”实用程序类,然后将静态工厂放置在那里。然后对其他类型执行相同的操作。

通过这种方法,您无法(甚至偶然地)获得与预期不同的类型(现在,调用B.create会产生A的实例)。

答案 1 :(得分:1)

这里可能没有完美的答案。有几个选项可能很有意义,但是细节可能取决于应用案例。我将列出一些有关以下问题的选项:静态工厂方法应该在哪里,它们的名称是什么?


  1. 将它们放入课堂

这是您现在要做的,但可能有一些警告:A中的方法创建了一个A的实例,您不能神奇地将其更改为B通过“通过” B来称呼它。无论您做什么,B.create()仍会创建一个A

如您所见,在create()中创建一个B方法将引起名称冲突。


  1. 将它们放入与它们所操作的类相对应的类

Tomasz Linkowski在他的答案中提到的方法并不罕见:对于名为ClassName的类,有一个名为ClassNames的类,其中包含用于创建实例的方法。

当类名没有合理的复数时,这真让我烦恼...

例如:

class Customer { /* With package-private constructor, usually ... */ }
public final class Customers {
    public static Customer create() { ... }
}

class PremiumCustomer { /* With package-private constructor, usually ... */ }
public final class PremiumCustomers {
    public static PremiumCustomer create() { ... }
}

这样做的好处是,您可以轻松添加扩展Customer的类,而不会影响现有的代码库。

这里的一个小缺点是您不能使用方法的静态导入,因为它们都具有相同的名称,但是在实际代码中很少会出现问题。


  1. 将它们放到一个汇总不同类型的类中

您的类称为AB,它们不传达实际的结构。但是根据这种结构,让具有不同工厂方法(名称不同)的一类表示专业化可能更有意义:

public final class Customers {
    public static Customer createStandard() { ... }
    public static PremiumCustomer createPremium() { ... }
}

后一种选择中哪个是“更好”的很难说。这也取决于包的结构:当这些类位于不同的包中时,您不能使其构造函数使用包私有的,这可能是不希望的。


可能很重要的注释:您专门询问了 static 工厂方法。但是您描述的场景提出了另一个问题:谁应该调用这些方法,以及如何调用?

每个人都必须知道他要创建的实例的确切类型...

通常,当涉及到多态性和继承时,static方法总是会失败并且会引起头痛。您应考虑使用(“简单”,非静态)工厂。

拥有此功能时:

void example() {
    Customer customer = Customer.create();
    doSomeComplexStuffWith(customer);
}

然后,实现者必须显式调用Customer.create()(或Customers.create()Customers.createDefault()-在这里无关紧要)。

无法更改在那里创建的元素的类型

此时,您可以考虑将静态工厂方法的使用更改为Supplier

void example(Supplier<? extends Customer> supplier) {
    Customer customer = supplier.get();
    doSomeComplexStuffWith(customer);
}

这样,您可以将静态方法用作实际的工厂实例,可能用作方法的引用。 example方法的实现不受此处类型更改的影响:

example(Customers::createStandard); // Run example with standard customers
example(Customers::createPremium); // Run example with premium customers

但是,这再次取决于您打算如何首先创建和使用实例。


一个简短的评论:

  强烈建议

在构造函数中调用可重写的方法...

是真的。但是,当您拥有静态工厂方法并创建类privateprotected constructor 时,可以将其视为一个极端情况。然后,您可以建立合同并对创建过程具有更大的控制权,并确保没有由于覆盖方法而引起的不良影响。

但是总的来说,工厂可能确实不需要在构造函数中调用可重写的方法。

答案 2 :(得分:1)

  

如何在A上定义与   任何子类

在这种情况下,为什么要在A中定义方法?

  

或者如何在A上定义静态工厂方法以及静态方法   A的每个适用子类上覆盖/隐藏的工厂方法   A的实现?

static方法和替代方法不能很好地结合在一起。
您必须更改方法。

我将使用以下两种方式之一:

  • 在每个类中定义一个静态工厂方法。
  • 定义一个工厂类,该类提供了一个工厂方法来依靠泛型,推断和Supplier创建任何实例。

1)定义静态工厂方法并不复杂,但是您应该谨慎两件事(类的泛型或不存在类)以及以下事实:带有特定名称和相同擦除的静态方法类和子类在子类中产生阴影行为。这不是很优雅,可能容易出错。

它看起来像:

public class A<T> {
    private List<T> values;

    public A() {
        this.values = new ArrayList<>();
    }

    public static <T> A<T> createA(List<T> values) {
        A<T> a = new A<>();
        a.setValues(values);
        return a;
    }

}

public class B extends A<Integer> {
    public B() {
        super();
    }

    public static  B createB(List<Integer> values) {
        B b = new B();
        b.setValues(values);
        return b;
    }           
}

并以这种方式使用它们:

A<String> a = A.createA(Arrays.asList("hello" ,"you"));
B b = B.createB(Arrays.asList(1 ,2));

2)定义提供静态工厂方法的工厂类。

public final class MyFactory{

    public static <T, U extends A<T>> U create(Supplier<U> supplierA, List<T> values) {     
        U a = supplierA.get();
        a.setValues(values);
        return a;
    }
}


public class A<T> {
    public void setValues(List<T> values) {
        this.values.clear();
        for (T value : values) {
            this.values.add(this.modify(value));
        }
    }
 }

public class B extends A<Integer> {
}

现在,您拥有一种独特的方法来创建A的任何实例:

A<String> list = MyFactory.create(A::new, Arrays.asList("hello", "you"));
B b = MyFactory.create(B::new, Arrays.asList(1, 2));