【问题标题】:Using Java and C#'s generics to simulate duck typing使用 Java 和 C# 的泛型来模拟鸭子类型
【发布时间】:2017-09-18 02:33:13
【问题描述】:

http://nullprogram.com/blog/2014/04/01/试图用一个例子来解释Java的泛型不能模拟鸭子类型:

class Caller<T> {
    final T callee;
    Caller(T callee) {
        this.callee = callee;
    }
    public void go() {
        callee.call();  // compiler error: cannot find symbol call
    }
}

class Foo {
    public void call() { System.out.print("Foo"); }
}

class Bar {
    public void call() { System.out.print("Bar"); }
}

public class Main {
    public static void main(String args[]) {
        Caller<Foo> f = new Caller<>(new Foo());
        Caller<Bar> b = new Caller<>(new Bar());
        f.go();
        b.go();
        System.out.println();
    }
}

程序将因编译时错误而失败。这是结果 类型擦除。与 C++ 的模板不同,只有一个 CallerT 的编译版本将变为 Object。由于Object 有 没有call()方法,编译失败。

是不是说通过Java泛型,类型参数的方法仅限于类java.lang.Object的方法?

C# 的泛型是根据具体化而不是类型擦除来实现的。 C#的泛型和Java的泛型一样没有上述限制吗?那么 C# 的泛型真的可以实现和鸭子类型一样的功能吗?

谢谢。

【问题讨论】:

  • (不是很有帮助,但作为一个琐事,C# 编译器使用鸭子类型实现foreach - 您可以 foreach 任何具有所需方法的东西,没有接口要求。)

标签: java c# generics programming-languages duck-typing


【解决方案1】:

C# 的泛型真的可以实现与鸭子类型相同的功能吗?

没有。但是 C# 的泛型可以包含一个约束,其中类型参数被限制为继承或实现某些特定类型。完成后,该类型参数的类型的任何表达式都被解析为受约束的类型,并且可以访问该类型的成员。

这类似于您阅读的文章中描述的extends 约束。

C# 中唯一的鸭子类型支持是 dynamic 关键字,其中涉及 dynamic 值的表达式的最终编译被推迟到实际运行时类型已知的运行时。

相关阅读:

Trivial C# class with a generic parameter wouldn't compile for no apparent reason
Call a method of type parameter

【讨论】:

  • 谢谢。 “C# 的泛型可以包含一个约束,其中类型参数被限制为继承或实现某些特定类型。”是否允许 C# 泛型没有这样的约束?如果是,那么没有这种约束的 C# 泛型难道不能实现与鸭子类型相同的功能吗?
  • “没有这种约束的 C# 泛型不能实现与鸭子类型相同的事情” -- 不。如果没有约束(这是允许的...如果您愿意,类型参数可以完全打开),类型被隐式约束为 System.Object 并且使用该泛型类型的代码只能使用来自 System.Object 的成员(即 @987654328 @ 和 GetHashCode())。
  • 为什么“没有约束(这是允许的……如果你愿意,类型参数可以完全打开),类型被隐式约束为System.Object”?你的意思是说“类型参数你喜欢可以完全开放”是不可能的?
  • 顺便说一句,恕我直言,引用的博客作者将 C++ 模板与鸭子类型错误混为一谈。我的意思是,是的……C++ 中的模板具有类似的效果。但是仍然存在编译时类型安全;模板在编译时应用,并且模板中使用的对象类型必须在那时知道。它看起来有点像鸭子打字,但恕我直言,“鸭子打字”更适合应用于动态类型语言中的行为。
  • “你的意思是不是说“类型参数可以完全开放,如果你喜欢”是不可能的””——我想这取决于你所说的“完全开放”。 C# 中的每个对象都继承System.Object。没有办法用 C# 编写代码,你至少不能假设那么多。对我而言,知道System.Object 类型的代码是类型为“完全开放”的代码。但是,如果您的意思是“根本不知道类型是什么”,那么从这个意义上说,类型不可能“完全开放”。恕我直言,这不是“完全开放”的有用定义。
【解决方案2】:

是不是说通过Java泛型,类型参数的方法仅限于类java.lang.Object的方法?

不完全是。虽然大多数泛型都被删除了,但可以在 Java 中包含一个约束,使得类型参数必须是某种类型。这实际上使它不是Object

未经测试,但这应该很接近。

class Caller<T extends CallMe> {
    final T callee;
    Caller(T callee) {
        this.callee = callee;
    }
    public void go() {
        callee.call();  // should work now
    }
}

interface CallMe {
    void call();
}

class Foo implements CallMe {
    public void call() { System.out.print("Foo"); }
}

class Bar implements CallMe {
    public void call() { System.out.print("Bar"); }
}

public class Main {
    public static void main(String args[]) {
        Caller<Foo> f = new Caller<>(new Foo());
        Caller<Bar> b = new Caller<>(new Bar());
        f.go();
        b.go();
        System.out.println();
    }
}

【讨论】:

  • 我认为@Antonín 想说的是,OP(应该)已经知道您答案中的信息,因为在他们阅读并参考的博客文章中通过示例完全解决了这个问题他们问题的开始。
  • 谢谢。 @Peter:(1)Java的泛型不能模拟鸭子类型的原因不是因为类型擦除,而是因为所有类都继承java.lang.Object,这是否正确(与C#的泛型不能模拟鸭子类型的原因相同) )? (2) 为什么T extends CallMe的信息在类型擦除过程中不会丢失?如果CallMe不是接口而是类,那么T extends CallMe的信息会不会在类型擦除过程中丢失?
  • @Tim:Java 和 C# 泛型都“无法模拟鸭子类型”的主要原因(不确定“模拟”与这里的任何东西有什么关系,但无论如何......)是那只鸭子打字真的与泛型无关,与 C++ 模板无关(尽管我同意 C++ 模板的行为有点像鸭子打字)。 Java 约束确实在类型擦除期间丢失。它仅在编译时使用。在运行时,没有办法恢复约束。而且CallMe是类还是接口都没有关系;它们的工作方式相同。
  • @Peter:你的意思是在编译的时候,T extends CallMe的信息是用来验证callee有一个方法call(),然后在类型擦除的时候被擦除的?
  • @Tim:是的,完全正确。泛型类型的用法需要在编译时满足约束。在运行时,无需进行类型检查,因此类型信息被“擦除”。
【解决方案3】:

C# 也有同样的限制。

除了dynamic,C# 在任何地方都没有任意的ducky-typing;即使使用泛型,您也只能调用由类型定义的方法(特别是泛型类型参数的约束,默认为object)。

【讨论】:

  • 谢谢。 “即使使用泛型,您也只能调用由类型定义的方法(特别是泛型类型参数的约束,默认为对象。” C# 泛型是否允许没有这样的一个约束?如果是,那么没有这种约束的 C# 泛型是否不能实现与鸭子类型相同的功能?
  • @Tim:如果没有约束,除了object之外,你不能对类型参数做任何假设。
猜你喜欢
  • 2011-05-18
  • 1970-01-01
  • 1970-01-01
  • 2011-03-20
  • 2023-03-11
  • 2020-09-30
  • 1970-01-01
  • 2011-02-25
  • 2014-09-22
相关资源
最近更新 更多