【问题标题】:Java Interfaces and Templates ConfusionJava 接口和模板混淆
【发布时间】:2014-01-16 20:02:30
【问题描述】:

我正在阅读一些关于不同编程语言中模板之间差异的问题。我明白这主要归功于这个问题:What are the differences between Generics in C# and Java... and Templates in C++?。然而,当他开始谈论接口和添加一些东西时,我在接受的答案结束时确实有点困惑。我主要从这个问题中了解接口的一般情况:Explaining Interfaces to Students。我仍然对问题中所说的内容感到困惑。那么有人可以更好地解释最后一部分:

因此,C++ 编译器对您可以使用模板执行的操作没有任何限制 - 基本上是您可以手动编写的任何代码, 你可以得到模板来为你写。最明显的例子是 添加东西:

在 C# 和 Java 中,泛型系统需要知道什么是方法 可用于类,它需要将其传递给虚拟 机器。告诉它的唯一方法是通过硬编码 实际类中或使用接口。例如:

string addNames( T first, T second ) { return first.Name() + 第二个名字(); }

该代码不会在 C# 或 Java 中编译,因为它不知道 T 类型实际上提供了一个名为 Name() 的方法。你必须告诉 它 - 在 C# 中是这样的:

interface IHasName{ string Name(); };字符串 addNames( T first, T 第二 ) 其中 T : IHasName { .... }

然后你必须确保你传递给 addNames 的东西 实现 IHasName 接口等等。 java语法是 不同的(),但它受到相同的影响 问题。

这个问题的“经典”案例是尝试编写一个函数 这是做什么的

string addNames( T first, T second ) { return first + second; }

您实际上无法编写此代码,因为没有办法 声明一个带有 + 方法的接口。你失败了。

C++ 没有这些问题。编译器不在乎 关于将类型传递给任何虚拟机——如果你的两个对象都有 .Name() 函数,它将编译。如果他们不这样做,就不会。很简单。

我真的很想理解这个答案中的代码,因为我很困惑 .Name() 方法如何在 IHasName 接口中工作。有人有更好的例子可以进一步解释如何使用接口向 Person 类或其他内容添加名称等内容...

编辑:我对 Java 代码更感兴趣。

【问题讨论】:

    标签: java templates interface


    【解决方案1】:

    '+' 在 C++ 中编译的原因是 C++ 模板(C++ 专家请原谅我的笨拙)就像巨大的宏。它将等到模板实例化后检查每个操作是否可行(仍在编译时,正如 foo 在他们的评论中警告我的那样)。

    例如这段代码:

    #include "stdafx.h"
    
    #include <stdio.h>
    
    #include <vector>
    #include <iostream>
    
    template <class T> class adder {
    public:
        adder();
        T add(T t1, T t2);
    };
    
    
    template <class T> adder<T>::adder() {}
    
    template <class T> T adder<T>::add (T t1, T t2) {
            return t1 + t2;
    }
    
    using namespace std;
    
    typedef vector<int> int_vector;
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
        adder<int_vector> vector_adder;
        int_vector v1;
        int_vector v2;
    
        v1.push_back(1);
        v1.push_back(2);
    
        v2.push_back(3);
        v2.push_back(4);
        v2.push_back(5);
    
        // will fail here, in compile time!
        int_vector v3 = vector_adder.add(v1, v2);
    
        for (int_vector::iterator it = v3.begin(); it != v3.end(); it++) {
            cout << *it;
        }
    
        return 0;
    }
    

    在我尝试添加两个向量之前会完美运行;在那一刻,C++ 编译器将意识到我在模板中使用的实际类型没有重载。但是,我可以在模板中定义一些其他操作,如果我没有尝试添加实际类型,编译器将不会意识到(然后它会尝试解决宏)。但是像adder&lt;int&gt; 这样的东西会很好用。

    Java 不允许您达到这一点,因为它不允许对泛型类型进行真正的操作(定义泛型包含,例如 java.util.* 下的类是很好的,但仅此而已)。最好的办法是像 java 文档中那样使用有界类型泛型:

    public class NaturalNumber<T extends Integer> {
    
        private T n;
    
        public NaturalNumber(T n)  { this.n = n; }
    
        public boolean isEven() {
            return n.intValue() % 2 == 0;
        }
    
        // ...
    }
    

    在这里你可以将泛型的类型限制为某个超类,并对其进行一些操作。

    【讨论】:

    • 抱歉,您似乎在这里混淆了两种语言。 C++ 模板在编译时解析,而不是运行时解析。你的“巨大的宏”是正确的,你在自相矛盾。你在考虑 Java 泛型吗?
    • 你是完全正确的,我对 C++ 的遗忘比我想象的要多;) 刚刚测试过,会更新答案
    【解决方案2】:

    C++ 模板非常不同。但我不明白为什么 Java 的泛型不能做你引用的那样。

    interface IHasName {
        String getName();
    }
    
    class Person implements IHasName {
        private final String name;
        public Person(String name) {
            this.name = name;
        }
    
        @Override
        public String getName() {
            return name;
        }
    }
    
    class GenericUtility {
        public static <T extends IHasName> String addNames(T first, T second) {
            return first.getName() + ", " + second.getName();
        }
    }
    
    class Main {
        public static void main(String[] args) {
            Person first = new Person("Peter");
            IHasName second = new IHasName() {
                @Override
                public String getName() {
                    return "John";
                }
            };
            String result = GenericUtility.addNames(first, second);
            System.out.println(result);
        }
    
    }
    

    打印

    Peter, John
    

    我认为这是按预期工作的。

    您实际上无法编写此代码,因为无法使用其中的 + 方法声明接口。你失败了。

    Java 不允许根据参数类型覆盖 + 运算符。这里没有失败,因为您首先必须采用不同的方法(可以写成接口)。

    C++ 没有这些问题。编译器不关心将类型传递给任何 VM - 如果您的两个对象都有 .Name() 函数,它将编译。如果他们不这样做,就不会。很简单。

    Java 也不会。如果你声明泛型参数必须扩展/实现某个接口,你会得到一个编译时错误,以防参数没有。

    【讨论】:

    • 感谢您的回答。我的问题不是代码是否有效,而更多的是寻找代码的解释,因为我确信它可以正常工作,但不确定“如何”。看到像这样的完整程序更好地解释了这个例子,实际上回答了我的问题。谢谢!
    猜你喜欢
    • 2016-07-18
    • 1970-01-01
    • 2021-09-20
    • 1970-01-01
    • 2011-05-17
    • 2012-02-25
    • 1970-01-01
    • 1970-01-01
    • 2019-12-22
    相关资源
    最近更新 更多