异构数组有什么意义?

16

我知道比Java更具动态性的语言,如Python和Ruby,通常允许你将混合类型的对象放入数组中,像这样:

["hello", 120, ["world"]]

我不理解为什么会使用这样的功能。如果我想在Java中存储异构数据,通常会创建一个对象来处理。

例如,假设一个具有ID和name属性。虽然我知道在Python/Ruby/PHP中可以这样做:

[["John Smith", 000], ["Smith John", 001], ...]

这种方式似乎比创建一个带有属性 IDname 的类 User 并将其放入数组中要不太安全/面向对象。

但是这只是个人意见。

[<User: name="John Smith", id=000>, <User: name="Smith John", id=001>, ...]

这里的 <User ...> 表示 User 对象。

在支持这种语言特性的语言中,使用前者而不是后者有何优势?或者说,使用异构数组有更大的原因吗?

N.B. 我说的不是包含不同对象但都实现了相同接口或继承自相同父类的数组,例如:

class Square extends Shape
class Triangle extends Shape
[new Square(), new Triangle()]

因为对于程序员来说,这仍然是一个同质数组,因为您将对每个形状执行相同的操作(例如,调用draw()方法),只有两者之间通常定义的方法不同。


2
Python或Ruby是动态类型的,因此异构数组可以“自由”实现。 - Etienne de Martel
1
这似乎有点不太安全?你所说的“安全”是什么意思?请注意,“类型安全”在Python中是绝对保证,因为对象的类型与值耦合在一起,无法通过类似于强制转换操作的方式来撤消。请定义您在此问题中所说的“安全”是什么意思。 - S.Lott
6
一种安全性是在编译时检测到编码错误。这是许多种错误动态类型语言无法提供的。当然,您会因此而获得灵活性。 - CodesInChaos
1
程序怎么会把那样的东西插进去,除非你一开始就告诉它这样做? - the Tin Man
2
不是因为我经常犯错,而是因为有了构造函数形式为((String) name, (int) id)的对象,语言可以在某人编写错误代码时发送错误信息,例如name,id,email - Aaron Yodaiken
显示剩余3条评论
8个回答

4

多方法应用于数组可能有些意义。您将策略转换为更加功能化的风格,重点放在离散逻辑(即多方法)上,而不是离散数据(即数组对象)上。

在您的形状示例中,这样可以避免您定义和实现Shape接口。(是的,在这里并不重要,但如果形状是您想要扩展的几个超类之一呢?在Java中,此时您就会受挫。)相反,您实现了一个智能的draw()多方法,该方法首先检查参数,然后根据需要将其分派到正确的绘图功能或错误处理。

功能式和面向对象的风格之间的比较随处可见;以下是几个相关问题,应该提供很好的起点:函数式编程 vs 面向对象编程向面向对象程序员和非技术人员解释函数式编程


@aharon 最后一段关于函数式编程与面向对象编程的区别是为了日后阅读此答案的人们的普遍受益而写的。我并不意味着你不知道这些差异。 - G__

4

正如katrielalex所写:没有理由不支持异构列表。事实上,禁止它需要静态类型,我们又回到了那个老的辩论。但是让我们避免这样做,而是回答“为什么要使用它”的部分...

说实话,它并没有被经常使用——如果我们利用你最后一段中的例外情况,并选择比如Java或C#更自由的“实现相同接口”的定义。几乎所有我的可迭代代码都期望所有项目都实现了某些接口。当然,它确实如此,否则它就不能对其进行太多操作!

不要误会,绝对有合法的用例——很少有好的理由编写一个包含一些数据的整个类(即使您添加了一些可调用项,函数式编程有时也能解决问题)。字典可能是更常见的选择,namedtuple也非常好。但它们比你想象的要少见,并且它们是经过深思熟虑和纪律性使用的,而不是为了牛仔编码。

此外,你的"User作为嵌套列表"示例并不是一个好例子-由于内部列表是固定大小的,最好使用元组,这样即使在Haskell中也是有效的(类型将是[(String,Integer)])。


1
这些“绝对有效的用例”是什么? - Aaron Yodaiken
@aharon:几乎每次它们都能正常工作,而且比其他替代方案更少麻烦。例如,具有异构值的字典(问题是关于数组的,但实际上任何集合都可以是异构的)可以成为良好的轻量级类。异构元组/列表不太自我说明,因此不太建议使用,但根据数据也是有效的(例如,在使用较少的地方使用了太少的数据,以使字典明显优越)。 - user395760
我知道这是多年以后的事了,但对我来说,在JavaScript中常见的用例是将arguments转换为(异构)数组,弹出/移动一些参数,然后使用.apply()。当编写事件处理库(.on().off())或编写包装/提升/修改其他函数的函数时,我经常使用它。 - cloudfeet

3
有支持此用法的语言,为何要使用前者而不是后者?
是的,在Python中(我猜在Ruby中也是同样的原因),你可以这样做,原因非常简单:
如何检查一个列表是否是异构的?
它不能直接比较类型,因为Python采用鸭子类型。如果所有的对象都有一些共同的类型类,Python也无法猜测到。无论如何,所有 都支持被表示,所以你应该能够将它们放在一起放入列表中。
把列表转换成需要类型声明的唯一类型也没有任何意义。
根本没有办法阻止你创建异构列表!
或者说使用异构数组还有更大的原因吗?
不,我想不出来了。正如你在问题中所提到的,如果你使用异构数组,那么你只会让事情变得比必要的更加困难。

来吧,这很容易实现。例如,列表类的构造函数可以要求类型,因此类似于arr = list(type = int)。这些信息将存储在列表中。在后续操作中,如果您尝试执行arr.append("hello")之类的操作,它会引发异常。就这么简单! - Ayxan Haqverdili
请看这里:https://python.godbolt.org/z/Yq6Kc3xbo - Ayxan Haqverdili

2

在Java中,你可以使用不同类型的数组。但是这被认为是一种不良的编程风格,使用正确的POJO将比在Java或任何其他语言中使用异构数组更快/更有效,因为“字段”的类型是静态已知的,并且可以使用原始数据类型。

在Java中,你可以:

Object[][] array = {{"John Smith", 000}, {"Smith John", 001}, ...};

2

没有理由不支持异构列表。这是由于技术原因的限制,我们不喜欢这些限制。

并非所有东西都需要成为类!

在Python中,类基本上就是一个带有一些额外内容的字典。因此,创建一个名为User的类并不一定比一个字典{"name": ..., "id": ...}更清晰。


但是如果我想要稍后扩展它们,字典就不能有额外的方法,对吗? - Aaron Yodaiken
不过,如果你想要额外的方法,你可以子类化 dict。而且这是可扩展的;你可以混合和匹配字典与 FunkyDictionaries 和自定义映射,只要在运行代码时所有需要的方法都在那里。还要注意,你可以有全局函数(不附加到类),所以例如你可以定义一个函数 split_name = lambda d: d['name'].split() 来返回由空格分隔的名字的第一个和最后一个部分。而这个函数不必与 dict 类相关联。 - Katriel
2
如果您想要自定义方法,即真正的“行为”,则需要使用类。但如果您只想结构化一些数据并对其应用转换,则不需要使用类-对于这些(非常常见的)情况,类不会增加任何好处,而会增加太多额外的代码行数。 - user395760

0
在Lua中,对象和数组是相同的东西,因此原因更加清晰。可以说Lua将弱类型推向了极致。
除此之外,我有一个Google地图对象,需要删除到目前为止在该地图上创建的所有标记。所以我最终创建了一个markers数组,一个circles数组和一个places数组。然后我编写了一个函数来迭代这三个数组,并在每个数组上调用.remove()。然后我意识到我只需要一个单一的非同质数组,将所有对象插入其中,并对该数组进行一次迭代即可。

0
异构列表非常有用。例如,为了制作贪吃蛇游戏,我可以像这样拥有一个块的列表: [[x,y,'down'],[x1,y1,'down']] 而不是一个块的类,我可以更快地访问每个元素。

1
它仍然是一个由(int, int, string)元组组成的同质列表。 - Jochen Ritzel
1
但是int、int、string是异构的。 - pythonFoo
2
我在概念上不会把你的内部列表视为异构列表,而是看作一个3元组。但当然,在动态类型的语言中,它由相同的类型支持。但在静态类型的语言中,你可以使用元组来实现相同的效果,而不会失去(编译时)类型安全性。因此,这并不是一个很好的例子来展示异构列表在静态类型语言所提供的之外的用途。 - CodesInChaos
3
“并且我可以更快地访问每个元素。” [需要引用] - Tyler
1
@MatrixFrog: http://pastebin.com/10DCvtuQ 这个小片段在我的电脑上输出为:6.58470416069 6.20616889。 - pythonFoo
显示剩余2条评论

-1

这里有一个简单的答案:

注意,我不是在谈论包含所有实现相同接口或继承自相同父类的不同对象的数组,例如:

一切都扩展自java.lang.Object...这就足够了。没有理由不使用Object[]并放入任何你喜欢的东西。在任何中间件(如持久层)中,Object[]都非常有用。


如果你正在使用Java,最好利用它的编译时类型检查。任何使用“Object”或“Object []”的代码都会立即引起我的警惕。 - Tyler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接