为什么通用接口不能用作类型参数?

时间:2017-09-01 14:41:23

标签: java generics programming-languages computer-science generic-programming

以下是Programming Language Pragmatics, by Scott

中的一些Java通用代码
interface Chooser<T> {
    public boolean better(T a, T b);
}
class Arbiter<T> {
    T bestSoFar;
    Chooser<? super T> comp;

    public Arbiter(Chooser<? super T> c) {
        comp = c;
    }
    public void consider(T t) {
        if (bestSoFar == null || comp.better(t, bestSoFar)) bestSoFar = t;
    }
    public T best() {
        return bestSoFar;
    }
}

class CaseSensitive implements Chooser<String> {
    public boolean better(String a, String b) {
        return a.compareTo(b) < 1;
    }
}
...
Arbiter<String> csNames = new Arbiter<String>(new CaseSensitive());
csNames.consider(new String("Apple"));
csNames.consider(new String("aardvark"));
System.out.println(csNames.best()); // prints "Apple"
  

Java要求    每个泛型类的代码显然(自明)类型安全,独立于任何特定实例。这意味着现场comp的类型 - 并且在   特别是它提供better方法的事实 - 必须静态声明。   因此,必须指定给定Chooser实例使用的Arbiter   作为构造函数参数;它不能是通用参数。 (我们本来可以用的   C ++中的构造函数参数;在Java中,它是强制性的。)

  1. 显然,每个泛型类的代码是什么?#34; (自我显然)类型安全,独立于任何特定 实例&#34;意思?
  2. 为什么会得出结论&#34;现场的类型comp - 和 特别是它提供better方法的事实必须是 静态宣布&#34;?
  3. &#34;静态声明&#34;意思?它反对什么?
  4. Chooser用作Java中的泛型参数时是什么感觉 (即使它不正确)?我只是想看看它的样子 喜欢在Java中出错。是&#34;我们可以使用构造函数参数 在C ++中;在Java中它是强制性的#34;意味着错误的方式 用C ++方式编写(我在本书前面的C ++中看到了类似的代码,我引用了in a previous post)?以下是Java中错误的代码,&#34; Chooser暗示的不能是通用参数&#34;:

    class Arbiter<T, Chooser> {
        T bestSoFar;
        Chooser comp;
    
        public Arbiter(Chooser c) {
            comp = c;
        }
        public void consider(T t) {
            if (bestSoFar == null || comp.better(t, bestSoFar)) bestSoFar = t;
        }
        public T best() {
            return bestSoFar;
        }
    }
    
  5. 感谢。

1 个答案:

答案 0 :(得分:2)

快速免责声明:我不知道C ++在哪里,也知道Java。如果我在这个答案中弄错了,我很乐意纠正它。

通过解释Java泛型和C ++模板之间的重要区别,可以回答这里的大多数问题。 Java泛型是使用类型擦除实现的,这意味着编译器通过删除对泛型的引用并在适当的位置插入强制转换,将泛型代码转换为非泛型代码。例如,我们可能有如下通用代码:

class Holder<T> {
    private T obj;
    Holder(T obj) { this.obj = obj; }
    T get() { return obj; }
}

Holder<String> h = new Holder<String>("hello");
String s = h.get();

在编译期间,转换为更像这样的代码:

class Holder {
    private Object obj;
    Holder(Object obj) { this.obj = obj; }
    Object get() { return obj; }
}

Holder h = new Holder("hello");
String s = (String) h.get();

相比之下,C ++模板更像是一个非常复杂的复制和粘贴。我们可以从这样的代码开始:

template<typename T>
class holder {
    T obj;
public:
    holder(T obj) : obj(obj) {}
    T& get() { return obj; }
};

holder<std::string> h{"hello"};
std::string& s = h.get();

当我们执行holder<std::string>时,编译器实际上创建了一个新类(称为模板实例化),其T替换为std::string

class holder_std_string {
    std::string obj;
public:
    holder_std_string(std::string obj) : obj(obj) {}
    std::string& get() { return obj; }
};

这个的主要含义是:

  1. 每个模板实例化实际上都知道&#34;它的类型参数在其类体内是什么。
  2. 编译器可以为不同的模板实例生成不同的代码。
  3. 例如,您可能会在C ++ arbiter的以下方法中观察到:

    void consider(T* t) {
        if (!best_so_far || comp(*t, *best_so_far)) best_so_far = t;
        //                  ^^^^^^^^^^^^^^^^^^^^^^ note!
    }
    

    方法体期望C的模板参数具有()的运算符重载,但是没有关于模板声明的指示。相反,C ++基本上通过模板进行鸭子输入。 arbiter<string, case_sensitive>的模板实例化实际上知道其模板参数是case_sensitive,因此可以生成调用重载()运算符的代码。我们可以传递任何东西作为模板参数,只要它有一个重载的()运算符,它与调用它的表达式兼容。如果您将一些参数传递给C并且没有重载()运算符,则模板实例化将无法编译。

    关于C ++代码的另一个有趣的注意事项是,您不需要将case_sensitive对象传递给构造函数,因为模板实例化实际上知道如何默认构造它本身。

    对于您的问题#4,在Java中使Chooser类型成为通用参数将是这样的:

    class Arbiter<T, C extends Chooser<? super T>> {
        T bestSoFar;
        C comp;
    
        public Arbiter(C c) {
            comp = c;
        }
        public void consider(T t) {
            if (bestSoFar == null || comp.better(t, bestSoFar)) bestSoFar = t;
        }
        public T best() {
            return bestSoFar;
        }
    }
    

    但请注意,我们已通过有界类型变量C extends Chooser<? super T>完成此操作,该变量告诉编译器C实际上必须是Chooser的某个子类型。这就是编译器知道我们可以调用方法comp.better(...)以及该表达式引用的方法的方式。请记住,Java中只有一个Arbiter类,因此所有通用Arbiters都需要共享字节码。

    C ++代码没有这样的要求,因为在实例化模板期间决定了表达式comp(...)的有效性。

      

    &#34;静态声明&#34;意思?它反对什么?

    在这种情况下,它只是意味着它在编译时已知。举例说明:

    // We know statically (at compile-time)
    // that n is some type if Number.
    void m(Number n) {
        // We could determine at runtime what
        // the actual type of n is a couple
        // of different ways.
        if (n instanceof Double)
            /* n is actually a Double */;
        // Prints the actual type of n.
        System.out.println(n.getClass());
    }
    

    C ++中的类型也是静态知道的。只是C ++模板的实现方式允许Java泛型不允许的许多事情。