

Duck Typing vs Type Erasure
source link: https://www.lujun9972.win/blog/2017/03/07/duck-typing-vs-type-erasure/index.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Duck Typing vs Type Erasure
原文URL: http://nullprogram.com/blog/2014/04/01/
假设有这么一个 C++ 类.
#include <iostream> template <typename T> struct Caller { const T callee_; Caller(const T callee) : callee_(callee) {} void go() { callee_.call(); } };
Caller可以接受任何类型的参数,只要该参数有 call()
方法就成.
比如,定义两个类 Foo
和 Bar
:
struct Foo { void call() const { std::cout << "Foo"; } }; struct Bar { void call() const { std::cout << "Bar"; } }; int main() { Caller<Foo> foo{Foo()}; Caller<Bar> bar{Bar()}; foo.go(); bar.go(); std::cout << std::endl; return 0; }
这段代码可以正常编译,运行后会显示“FooBar”.
这就是所谓的"鸭子类型"(Duck Typing) — 也就是说, “如果它看起来像鸭子,游起来像鸭子,叫起来像鸭子,那你就认为它是鸭子.”
Foo
和 Bar
是完全无关的两个类型. 他们并没有继承自同一个类,但是只要他们都提供了需要的接口,就都能被 Caller
所用.
这是一种很不一样的多态.
"鸭子类型"一般在动态语言中比较常见. 不过通过模板,像 C++ 这样的静态强类型语言也能使用"鸭子类型",而无需损害其保障类型安全的能力. without sacrificing any type safety.
Java Duck Typing
让我们再试试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(); } }
这几乎就是上一个程序的翻版, 但是该程序在编译时会由于类型擦除(type erasure)而报错.
与C++的模板不同,这里 Caller
编译后只会产生一个版本的类, 其中 T
会变成 Object
.
由于Object类型并没有 call()
方法,因此编译会失败.
这里泛型的作用只是推迟了编译器对类型的检查而已.
C++模板有点类似于宏, 它由编译器负责进行扩展,每一个类型的参数都会产生不同版本的实现.
检查 call
方法的时机实在类型已经完全确认之后,而不是在模板刚定义的时候.
为了解决这个问题, Foo
和 Bar
需要公用同一个祖先. 假设我们定义这个祖先为 Callee
.
interface Callee { void call(); }
在 Caller
的实现中需要声明T为 Callee
的子类.
class Caller<T extends Callee> { // ... }
现在编译可以通过了,因为 Callee
中有 call()
方法.
最后实现接口 Callee
.
class Foo implements Callee { // ... } class Bar implements Callee { // ... }
这已经不能算是"鸭子类型"了, 只是普通的多态而已. 类型擦除机制使得在Java中无法实现"鸭子类型"(除非使用反射机制).
Signals and Slots and Events! Oh My!
"鸭子类型" 在是现在观察者模型时非常有用,它无需让你定义那么多的模板(boilerplate). 它并不要求一个类必须 继承自特定的类 或者实现特定的接口(interface). 这里有一个很好的例子: the various signal and slots systems for C++. 相比之下Java 需要让所有的类型都实现EventListener接口:
- KeyListener
- MouseListener
- MouseMotionListener
- FocusListener
- ActionListener, etc.
如果一个类涉及到多种类型的事件的话,比如说想实现一个时间记录器,那么就需要实现多个接口了.
Recommend
-
19
Duck-typing Programming 2019年11月13日 翻译自Duck Typ...
-
10
Dependency Injection, Duck Typing, and Clean Code in Go 22 Apr 2015 3 Comments Along the path to learning...
-
7
Duck Typing in JavaScript and TypeScriptDuck Typing for beginnersPhoto by Anne Nygård on
-
8
Duck typing vs type safety in Ruby Posted on November 2, 2016 by solnic Blog
-
8
Why Duck Typing Is Safe Why Duck Typing Is Safe posted Jun 16, 2020 in Programming For the last 20 years I've been programming almost exclusive...
-
8
This is the second article of the Python vs C++ Series. In this article, we are going to talk about another basic object-oriented pr...
-
1
什么是鸭子类型(duck typing) 2019年8月10日 | 字数 1499 |
-
9
What are the duck-typing requirements of _com_ptr_t?
-
8
What are the duck-typing requirements of MFC IPTR?
-
3
What are the duck-typing requirements of ATL CComPtr?
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK