Java中的通用方法模式

5

我对Java泛型非常陌生,已经花费了大量时间寻找适当的解决方案(如果有的话)。

我正在尝试设计适配器来处理特定类型的类对象。如下所述,CAdapter类仅处理"CClass"对象。我正在尝试提供一个抽象的通用适配器,它处理大部分工作(就像Java集合LinkedList一样)。然后为需要支持的每种类型提供具体的适配器实现。

// Classes

public interface AInterface {
  public String toString();
}

public class BClass extends AInterface  {
  public String toString() { return "BClass "; }
}

public class CClass extends AInterface  {
  public String toString() { return "CClass"; }
}

// Adapters

public interface AdapterInterface<T extends AInterface> {
  public T getInterface();
}

public class BAdapter implements AdapterInterface<BClass> {
  private BClass aInterface = null;
  public BClass getInterface() { return aInterface; }
}

public class CAdapter implements AdapterInterface<CClass> {
  private CClass aInterface = null;
  public CClass getInterface() { return aInterface; }
}

首先,我看到有人说提供一个具体实现的通用适配器是被嫌弃的(好像会导致上帝打死一只小猫)。也许有人能进一步解释一下这个情况?

其次,我遇到了一个问题,就是动态实例化适配器时没有让Java编译器发出警告。例如,我有一个方法:

public <T extends AInterface> AdapterInterface<T> getAdapter(String type) {
  AdapterInterface<T> result = null;
  if (type.equals("C") {
    result = new CAdapter();
  }
  return result;
}

当然,编译器会抱怨CAdapter与之不匹配。对于任何类型的AInterface对象,我想能够加载正确的适配器并适当地处理它。我无法理解工厂模式以实现这一点。如有任何想法,请不吝赐教。

你是否在调用foo.<CAdapter> getAdapter(C)?否则T将无法绑定。更常见的模式是public AdapterInterface<T> getAdapter(Class<T> clazz); - Nialscorva
你需要提供一些关于“杀小猫”的背景信息,否则我们无法判断互联网上的某个人是对还是错。我们需要了解他实际说了什么。 - millimoose
@Nialscorva 这并不重要。类型系统无法证明您是否提供了类型标记的约束条件。当方法签名类似于 <T> T getWhateverOfType(Class<T> type) 时,类型标记很有用,因为涉及到 Class.newInstance()Class.cast() 等。如果您不处理给定类的实例,则它们是没有用处的。 - millimoose
@Inerdial,这不是关于newInstance的问题,而是关于它作为一个伪枚举开关进行编译时检查——字符串中没有拼写错误的机会。虽然没有经过测试,但如果您调用原始foo.getAdapter("C"),那么T就没有绑定,foo.<CAdapter> getAdapter("C")将使其可证明。同样地,foo.getAdapter(CAdapter.class)是类型为Class<CAdapter>并绑定T,允许CAdapter匹配。 - Nialscorva
2
当我看到类似于AdapterInterface的代码时,我首先会在浏览器中打开Langer的泛型FAQ - gnat
显示剩余3条评论
2个回答

4

我知道我的回答有点多余,但是:

任何带有 <T> 的东西意味着“我的调用者知道这个类型是什么,但我不知道”。所以

AdapterInterface<T> result = null;

意思是“我实际上不知道结果的类型,这取决于我的调用者认为它是什么”。编译器会对此进行投诉:
result = new CAdapter();

由于该代码无法假定T是CClass,因此需要进行翻译。

事实上,没有办法在不进行类型转换的情况下完成这个过程(声明方法为通配符只意味着您需要在调用时转换结果)。类型转换是告诉编译器“我知道你无法知道这是什么,没关系:我知道。相信我吧”。是的,你会收到一个警告。但是这没关系。

泛型不能消除所有强制类型转换,但它们允许您仅进行一次强制类型转换。您只需要在您-程序员-确定您正在使用类型进行游戏的那个地方进行转换,而不是在所有地方都需要转换。所有其余代码,即使用您刚刚创建的适配器的内容,可以安全地使用泛型类型。


谢谢你的回答。我很感激你讲解得非常清楚。 - Michael Cronk
我借用“我的调用者知道这是什么类型,但我不知道”的措辞,它比我通常的解释更清晰。 - millimoose

3

不要在这里使用泛型,协变(或者逆变,我总是记不住哪个是哪个)返回类型似乎可以做到你想要的:

interface AdapterInterface {
    public AInterface getInterface();
}

class BAdapter implements AdapterInterface {
    private BClass aInterface = null;

    public BClass getInterface() {
        return aInterface;
    }
}

class CAdapter implements AdapterInterface {
    private CClass aInterface = null;

    public CClass getInterface() {
        return aInterface;
    }
}

public AdapterInterface getAdapter(String type) {
    AdapterInterface result = null;
    if (type.equals("C")) {
        result = new CAdapter();
    }
    return result;
}

除非在接口中您没有提到其他方法,否则以下代码也可以编译通过,类可以是泛型的:
public
AdapterInterface<? extends AInterface> getAdapter(String type) {
    if (type.equals("C")) {
        return new CAdapter();
    } else {
        // …
    }
}

你原来的方法无法编译的原因是,T是某个未知类型,它扩展自AInterface;它并不意味着“任何继承自AInterface的类型”。无法静态地证明你返回的适配器是调用方想要的类型的适配器。

谢谢您的输入。在您的答案和下面PaulMurrayCbr的答案之间,我终于明白了如何处理Java泛型 - 特别是在使用工厂设计模式时。我将在工厂中进行一次简单的转换并忽略警告,理解到泛型(至少在Java 5/6中)无法消除所有强制类型转换。 - Michael Cronk
@MichaelCronk 没错。实际上,Java泛型相关的许多SO问题往往是OP询问如何实现他所考虑的类型约束,只是最终发现为什么这是不可能的。然而,您可以并且应该尽可能避免未经检查的转换(转换为类型参数和参数化类型),通过使用Class.cast()和其他运行时类型检查来替换它们。或者将它们尽可能地封装在您知道即使类型系统无法验证也是正确的代码中。 - millimoose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接