异构数组有什么用?
我知道像Python和Ruby这样的语言比Java更灵活,通常允许你在数组中放入不同类型的对象,比如这样:
["hello", 120, ["world"]]
但我不太明白为什么会需要这样的功能。如果我想在Java中存储不同类型的数据,我通常会为它创建一个对象。
举个例子,假设一个User
有int ID
和String name
。在Python/Ruby/PHP中,你可以这样做:
[["John Smith", 000], ["Smith John", 001], ...]
但我觉得这样做比创建一个包含ID
和name
属性的User
类要不安全一些,也不太符合面向对象的原则,然后再用这个类来创建数组:
[<User: name="John Smith", id=000>, <User: name="Smith John", id=001>, ...]
这里的<User ...>
表示的是User对象。
在支持这种特性的语言中,使用前者有什么理由吗?或者说使用不同类型的数组有什么更大的理由呢?
注意,我不是在说那些包含不同对象但都实现了相同接口或继承自同一父类的数组,比如:
class Square extends Shape
class Triangle extends Shape
[new Square(), new Triangle()]
因为对程序员来说,这仍然是一个同质数组,因为你对每个形状做的事情是一样的(例如,调用draw()
方法),只是它们之间共同定义的方法而已。
8 个回答
在支持这种特性的编程语言中,使用前者而不是后者有什么理由吗?
有的,为什么在Python中可以这样做(我想在Ruby中也是同样的原因)非常简单:
你怎么检查一个列表是不是混合类型的?
- 不能直接比较类型,因为Python使用的是鸭子类型(也就是说,只要对象有某些方法,就可以被当作某种类型使用)。
- 如果所有对象都有一些共同的类型,Python也无法猜测出来。反正所有对象都支持被
repr
表示,所以你也应该能把它们放在一个列表里。 - 把列表变成唯一需要类型声明的类型也没有任何意义。
根本没有办法阻止你创建一个混合类型的列表!
那么使用混合数组是否有更大的理由呢?
没有,我想不出有什么理由。正如你在问题中提到的,使用混合数组只会让事情变得比必要的更复杂。
把一个多方法应用到数组上可能是个不错的主意。你可以换个思路,采用更偏向函数式的风格,关注某个具体的逻辑部分(也就是多方法),而不是关注具体的数据部分(也就是数组里的对象)。
在你的图形示例中,这样做可以避免你需要定义和实现一个Shape
接口。(是的,这里可能不算什么大事,但如果形状是你想扩展的多个父类之一呢?在Java中,这时候你就麻烦了。)相反,你可以实现一个聪明的draw()
多方法,它首先检查传入的参数,然后根据对象是否可以绘制,调用合适的绘制功能或者处理错误。
关于函数式和面向对象的风格的比较到处都是;这里有几个相关的问题,可以帮助你入门:函数式编程与面向对象编程和向面向对象程序员和不太懂技术的人解释函数式编程。
正如katrielalex所说:没有理由不支持不同类型的列表。实际上,如果不允许这样做,就需要静态类型,这样我们又回到了老问题。但我们还是别纠结这个,先来聊聊“为什么要这样用”这个部分吧……
老实说,这种用法并不是特别常见——如果我们参考你最后一段提到的例外情况,并选择一个比Java或C#更宽松的“实现相同接口”的定义。几乎我所有处理可迭代对象的代码都期望所有的项目都实现某个接口。当然,必须这样做,不然代码能做的事情就很有限了!
别误会我的意思,确实有合理的使用场景——通常没有太好的理由去写一个完整的类来存放一些数据(即使你加了一些可调用的功能,函数式编程有时也能帮上忙)。不过,使用字典会是更常见的选择,而namedtuple也非常不错。但这些用法并没有你想象的那么普遍,它们的使用需要考虑和规范,而不是随意编码。
(另外,你提到的“User
作为嵌套列表”的例子并不好——因为里面的列表是固定大小的,最好用元组,这样在Haskell中也是有效的(类型会是[(String, Integer)]
))