11

我知道如何使用C++ 模板——不是专家,请注意。使用Java 泛型(和 Scala,就此而言),我遇到了困难。也许是因为我试图将我的 C++ 知识转化为 Java 世界。我在其他地方读到,“它们完全不同:Java 泛型只是语法糖节省演员,C++ 模板只是一个美化的预处理器”:-)

我很确定,两者都是一个有点简化的视图。因此,为了了解大和细微的差异,我尝试从Specialization开始:

C++中,我可以设计一个模板(函数类),它作用于支持我所需操作的任何类型T

template<typename T>
T plus(T a, T b) { return a.add(b); }

现在,这可能会将plus()操作添加到任何可以add().[note1][1]的类型

因此,如果T支持add(T)我的模板就可以工作。如果没有,只要我不使用plus(). 在 Python 中,我们称之为“鸭子类型”:*如果它的行为像鸭子,像鸭子一样嘎嘎叫,它就是鸭子。*(当然,使用 type_traits 会稍微修改一下,但只要我们没有概念,这就是 C++ 模板的工作方式,对吧?)

我想,Java 中的泛型也是这样工作的,不是吗?通用类型 I 设备用作“模板”,如何对我尝试放入的任何内容进行操作,对吗?据我了解,我可以(或必须?)对类型参数设置一些约束:如果我想add在我的模板中使用,我必须将类型参数声明为implement Addable. 正确的?所以,没有“鸭子打字”(无论好坏)。

现在,在C++中,我可以选择专注于没有 的类型add()

template<>
T plus<MyX>(MyX a, MyX b) { return a + b; }

即使所有其他类型仍然可以使用“默认”实现,现在我添加了一个特殊的MyX——没有运行时开销。

是否有任何具有相同目的的Java 泛型机制?当然,在编程中一切都是可行的,但我的意思是在概念上,没有任何技巧和魔法?

4

3 回答 3

8

不,Java 中的泛型不是这样工作的。

使用泛型,如果没有泛型,您将无法做任何事情 - 您只需避免编写大量强制转换,并且编译器确保一切都是类型安全的(只要您没有收到一些警告或禁止这些警告) .

因此,对于每个类型变量,您只能调用其边界中定义的方法(没有鸭子类型)。

此外,没有代码生成(除了一些适配器方法委托给具有其他参数类型的方法以实现泛型类型)。假设你有这样的事情

/**
 * interface for objects who allow adding some other objects
 */
interface Addable<T> {
   /** returns the sum of this object and another object. */
   T plus(T summand);
}

然后我们可以sum使用两个参数创建我们的方法:

public static <T extends Addable<T>> T sum(T first, T second) {
    return first.plus(second);
}

静态方法被编译为相同的字节码,如下所示(在注释中带有额外的类型信息):

public static Addable sum(Addable first, Addable second) {
    return first.plus(second);
}

这称为类型擦除

现在可以为可添加类型的每对两个元素调用此方法,如下所示:

public class Integer implements Addable<Integer> {
    public Integer plus(Integer that) {
       return new Integer(this.value + that.value);
    }

     // private implementation details omitted
}

这里发生的是编译器创建了一个额外的合成方法,如下所示:

public Object plus(Object that) {
    return this.plus((Integer)that);
}

这个方法只会被具有正确类型的通用代码调用,这保证了编译器,假设你没有在某个地方做一些不安全的强制转换——那么(Integer)这里的强制转换会捕获错误(并抛出 ClassCastException)。

sum方法现在总是调用plus第一个对象的方法,没有办法解决这个问题。没有为每个可能的类型参数生成代码(这Java 泛型和 C++ 模板之间的主要区别),因此我们不能简单地将生成的方法之一替换为专门的方法。

当然,您可以创建第二个sum方法,例如不可靠的建议(带有重载),但是只有在您直接在源代码中使用该MyX类型时才会选择此方法,而不是当您sum从其他一些碰巧被参数化的通用代码中调用该方法时使用 MyX,如下所示:

public static <T extends Addable<T>> product (int times, T factor) {
    T result = factor;
    while(n > 1) {
        result = sum(result, factor);
    }
    return result;
}

现在product(5, new MyX(...))将调用我们的sum(T,T)方法(依次调用该plus方法),而不是任何重载sum(MyX, MyX)方法。

(JDK 7 增加了一个新的dynamic方法分派模式,它允许在运行时对每个参数进行特殊化,但这不是 Java 语言使用的,只打算被其他基于 JVM 的语言使用。)

于 2011-04-02T13:00:57.143 回答
3

不-但您的特定问题更多是超载问题。

plus定义这样的2个方法没有问题

<T extends Addable> 
T   plus(T   a, T   b) { .. }

MyX plus(MyX a, MyX b) { .. }

即使MyXAddable;这也有效 javac 知道 2ndplus比 1st 更具体plus,因此当您plus使用两个MyXargs 调用时,将plus选择 2nd。从某种意义上说,Java 确实允许“专门”版本的方法:

f(T1, T2, .. Tn)

f(S1, S2, .. Sn)

如果每个Si都是Ti

对于泛型类,我们可以做

class C<T extends Number> { ... }

class C_Integer extends C<Integer>{ ... } 

调用者必须使用C_Integer而不是C<Integer>选择“专业”版本。


关于鸭子类型:Java 在静态类型方面更加严格——除非它是鸭子,否则它就不是鸭子。

于 2011-04-02T12:37:08.630 回答
2

你好,

java 泛型它与 C++ 模板不同。

例子:

Java代码:

 public <T> T sum(T a, T b) {
  T newValue = a.sum(b);
  return newValue;
 }

在java中这段代码不起作用,因为泛型基类是java.lang.Object类,所以你只能使用这个类的方法。

你可以像这样构造这个方法:

public <T extends Number> T sum(T a, T b) {
  T newValue = a.sum(b);
  return newValue;
 } 

在这种情况下,泛型的基础是类 java.lang.Number,因此您可以使用 Integer、Double、Long ecc..

方法“sum”取决于 java.lang.Number 的实现。

再见

于 2011-04-02T11:25:27.840 回答