【问题标题】:Java Generics, support "Specialization"? Conceptual similarities to C++ Templates?Java 泛型,支持“专业化”吗?与 C++ 模板的概念相似之处?
【发布时间】:2011-04-02 11:13:32
【问题描述】:

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

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

C++ 中,我可以设计一个 Template(函数类),它作用于 any 类型 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++ 中,我可以选择 specialize 处理没有 add() 的类型:

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

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

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

【问题讨论】:

    标签: java c++ generics template-specialization


    【解决方案1】:

    不,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++ 模板之间的关键区别),因此我们不能简单地用专门的方法替换生成的方法之一。 p>

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

    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 的语言。)

    【讨论】:

    • 类型擦除方法应该是Addable sum(Addable first, Addable second)
    • 很好地解释了泛型如何为sum 工作。现在我可以将它转化为我的旧 Java 知识。但是这里擦除是什么意思? “类型擦除”这个名称指的是什么?
    • 当然!通过重载进行专业化。我多么愚蠢:-) 那不是。谢谢。
    • @towi:“类型擦除”是指将每个泛型类型(可能是参数化类型、泛型数组类型或类型变量)转换为非泛型类型的过程,因为 VM只能与那些直接合作。请参阅 JLS 中的 4.6 Type Erasure 部分。
    【解决方案2】:

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

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

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

    即使MyXAddable,这仍然有效; javac 知道第二个plus 比第一个plus 更具体,因此当您使用两个MyX 参数调用plus 时,将选择第二个plus。在某种意义上,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&lt;Integer&gt; 来选择“专用”版本。


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

    【讨论】:

    • 这只是重载,如果使用T从某个泛型方法调用plus方法并使用MyX调用,则它不起作用。
    【解决方案3】:

    嗨,

    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 的实现。

    再见

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-04
      • 2022-01-04
      • 1970-01-01
      • 1970-01-01
      • 2012-04-21
      • 1970-01-01
      • 2020-07-18
      • 1970-01-01
      相关资源
      最近更新 更多