抱歉,如果这是一个非常愚蠢的问题,但听到“Java 数组实际上只是对象”,我认为它们需要具有预定义的长度是没有意义的?
我理解原始类型为什么这样做,例如 int myInt = 15;
分配 32 位内存来存储整数,这对我来说很有意义。但是如果我有以下代码:
class Integer{
int myValue;
public Integer(int myValue){
this.myValue = myValue;
}
}
后跟 Integer myInteger = new Integer(15);myInteger.myValue = 5;
,那么我可以在 myInteger
中存储的数据量没有限制。它不限于 32 位,而是一个指向对象的指针,该对象可以存储任意数量的 int
、double
、String
或任何东西。它分配了32位内存来存储指针,但是对象本身可以存储任意数量的数据,不需要事先指定。
那么为什么数组不能这样做呢?为什么我需要事先告诉数组分配多少内存?如果数组“实际上只是一个对象”,那么为什么我不能简单地说 String[] myStrings = new String[];myStrings[0] = "Something";
?
我是 Java 的新手,所以 100% 的可能性这是一个愚蠢的问题,并且有一个非常简单明了的答案,但我很好奇。
另外,再举一个例子,我可以说 ArrayList<String> myStrings = new ArrayList<String>();myStrings.add("Something");
没有任何问题......那么是什么使 ArrayList 与数组不同呢?为什么需要告诉数组分配多少内存而 ArrayList 不需要?
预先感谢任何花时间填写我的人。:)
编辑:好的,到目前为止,评论中的每个人都误解了我的帖子,我觉得措辞错误是我的错。 我的问题不是“我如何定义数组?”,或者“更改变量的值是否会改变其内存使用?”,或者“指针是否存储它们指向的对象的数据?”,或者“是数组吗?”对象?”,也不是“ArrayLists 如何工作?” 我的问题是,为什么当我创建一个数组时,我需要告诉它它指向的对象有多大,但是当我创建任何其他对象时,它会自行缩放而不需要我提前告诉它任何内容? (以 ArrayLists 为例)
我希望这现在更有意义...我不知道为什么每个人都误解了? (我说错了吗?如果是这样,请告诉我,我会更改以方便他人)
答案 0 :(得分:2)
我的问题是为什么指向数组的指针需要事先知道数组有多大,而指向任何其他对象的指针则不需要?
它没有。在这里,这运行得很好:
String[] x = new String[10];
x = new String[15];
整个“需要提前知道它有多大”仅指ARRAY OBJECT。如在,new int[10]
进入堆,它就像一个巨大的海滩,并凭空创建一个新的宝箱,大到足以容纳 10 个整数(作为原语,在这个例子中就像硬币一样)。然后它把它埋在沙子里,永远失去了。因此,为什么 new int[10];
单凭它的寂寞就毫无用处。
当你写 int[] arr = new int[10];
时,你仍然这样做,但你现在也制作了一张藏宝图。 X 标记该点。 'arr' 是这张地图。它不是整数数组。它是一个 map 到一个 int 数组。在 java 中,[]
和 .
都是“跟随地图,向下挖掘,然后打开”。
arr[5] = 10;
的意思是:按照你的 arr
地图,向下挖,打开你在那里找到的箱子,你会看到它有 10 个小袋子的空间,每个小袋子都足够容纳一枚硬币。取第 6 个袋子。取出任何东西,放入一个 10 克拉的硬币。
不是地图需要知道地图通向多大的箱子。是胸部本身。对象也是如此,java中不可能做一个可以任意调整大小的宝箱。
那么 ArrayList 是如何工作的?
盒子里的地图。
ArrayList 在内部有一个 Object[]
类型的字段。该字段不包含对象数组。它不能。它保存一个映射到一个对象数组:它是一个引用。
那么,当你创建一个新的数组列表时会发生什么?这是一个宝箱,大小固定,正好可以放两件东西:
如果你然后运行 list.add("foo")
,它的作用是复杂的:
list
藏宝图,跟随它,挖掘它,打开盒子,然后你大喊“哦!添加这个!',将您藏宝图的副本交给“foo”宝藏。然后这个盒子用它做什么对你来说是不透明的 - 这就是面向对象的重点。因此,ALL 宝箱的大小是固定的,但是通过用新地图替换地图并召唤新宝箱,您仍然可以使它看起来像 ArrayList
能够缩小和成长。
那为什么数组不允许呢?因为缩小和增长的东西很复杂,数组暴露了低级功能。不要使用数组,使用列表。
答案 1 :(得分:1)
您似乎误解了“存储”的含义。你说“我可以存储的数据量没有限制”,但如果你运行 myInteger.myValue = 15
,你覆盖你原来放在那里的 32 的值。您仍然不能存储超过 32 位的数据,只是您可以更改您在该变量中放入的 32 位。
如果你想看看ArrayList
是如何工作的,你可以read the source code;它可以展开because if it runs out of space it creates a new larger array and switches its single array variable elementData
to it。
根据您的更新,您似乎想知道是否能够将许多不同的字段添加到对象定义中。在这种情况下,这些字段和它们的类型在类被编译时是固定的,并且从那时起类具有固定的大小。您不能像在 JavaScript 中那样在运行时堆积额外的属性。您预先告诉它它需要的规模。
答案 2 :(得分:1)
我将忽略您提供的大部分细节,并在编辑中回答问题。
<块引用>我的问题是,为什么当我创建一个数组时,我需要告诉它它指向的对象有多大,但是当我创建任何其他对象时,它会自行缩放而不需要我事先告诉它任何事情?
从处理“当我制作任何其他对象时它会自行缩放”开始是值得的,因为这不是真的。如果你创建一个这样的类:
class MyInteger
public int value;
public MyInteger(int value) {
this.value = value;
}
}
然后该类具有静态定义的大小。一旦编译了这个类,MyInteger
实例的内存量就已经确定了。在这种情况下,它是对象头大小(取决于 JVM)和一个整数的大小(至少 4 个字节)。
对象一旦被 JVM 分配,它的大小就不能改变。 JVM(重要的是垃圾收集器)将其视为一个字节块,直到它被回收。像 ArrayList
这样的类给人一种增长的错觉,但它们实际上是通过分配其他对象来工作的,它们存储对这些对象的引用。
class MyArrayList {
public int[] values;
public MyArrayList(int[] values) {
this.values = values;
}
}
在这种情况下,MyArrayList
实例将始终占用相同数量的内存(对象头大小 + 引用大小),但引用的数组可能会发生变化。我们可以这样做:
MyArrayList list = new MyArrayList(new int[50]);
这会为 list
分配一块内存,为 list.values
分配一块内存。如果我们那么做(就像 ArrayList
在内部有效地做的那样):
list.values = new int[500];
那么分配给 list
的内存大小仍然相同,但我们分配了一个新块,然后在 list.values
中引用该块。这使得我们的旧 int[50]
没有任何引用(因此它可以被垃圾收集)。但重要的是,没有分配改变大小。我们重新分配了一个新的更大的块供我们的列表使用,并从我们的 MyArrayList
实例中引用了它。
答案 3 :(得分:1)
为什么 Java 中的数组需要有预定义的长度,而对象不需要?
为了理解这一点,我们需要确定“大小”在 Java 中是一个复杂的概念。有多种含义:
每个对象作为一个或多个堆节点存储在堆中,其中一个是主节点,其余是可以从主节点访问的组件对象。
主堆节点由固定且不变的堆内存字节数表示。我将把它称为1对象的本机大小。
一个数组有一个明确的 length
字段。该字段未声明。它的类型为 int
且无法分配给它。每个数组实例的头部实际上都有一个 32 位的字段,用于保存 length
。
数组的 length
直接映射到它的本机大小。 JVM 可以根据 length
计算本机大小。
不是数组实例的对象也具有本机大小。这是由对象字段的数量和类型决定的。由于无法在运行时添加或删除字段,因此本机大小不会更改。但它不需要存储,因为它可以在运行时从对象的类中确定(在需要时)。
某些对象支持类特定大小概念。例如,String 的大小由其 length()
方法返回,而 ArrayList
的大小由其 size()
方法返回。
注意:
特定于类的大小的含义是......特定于类。
特定于类的大小与实例的本机大小无关。 (退化情况除外……)
事实上,所有对象都有一个固定的原生大小。
1 - 该术语仅用于本答案。我声称对这个词没有权威......
示例:
String[]
具有取决于其长度的原生大小。在典型的 JVM 上,它将是 12 + length * (
您的 Integer
类具有固定的本机大小。在典型的 JVM 上,每个实例的长度为 16 字节。
一个 ArrayList
对象有 2 个 private int
字段和一个 private Object[]
字段。这为其提供了 16 或 24 字节的固定本机大小。 int
字段之一是 call size
,它包含 size()
返回的值。
size
的 ArrayList
可能会改变,但这是由类的代码实现的。为此,它可能需要重新分配其内部 Object[]
以使其足够大以容纳更多元素。如果您检查 ArrayList
类的源代码,您会看到这是如何发生的。 (查找 ensureCapacity
和 grow
方法。)
所以,普通对象的大小和数组的长度之间的区别是:
普通对象的自然大小完全由对象的类型决定,它永远不会改变。它很少与应用程序相关,也不会通过字段公开。
数组的长度取决于实例化它时提供的值。它永远不会改变。自然大小可以从长度上确定。
对象的类特定大小(如果相关)由类管理。
对于您修改后的问题:
<块引用>我的问题是,为什么当我创建一个数组时,我需要告诉它它指向的对象有多大,但是当我创建任何其他对象时,它会自行缩放而无需我事先告诉它任何事情? (以 ArrayLists 为例)
关键是在 JVM 级别,没有任何东西可以自动扩展。 Java 对象的本机大小不能改变。
为什么?因为增加对象的堆节点的大小将需要移动堆节点,并且在不更新对象的所有引用的情况下无法移动堆节点。这无法有效地完成。
(有人指出 GC 可以有效地移动堆节点。然而,这不是一个可行的解决方案。运行 GC 是昂贵的。执行 GC 以(例如)增长一个单个 Java 数组。如果已指定 Java 以便数组可以“增长”,则需要使用底层的不可增长数组类型来实现。)
ArrayList
的情况由 ArrayList
类本身处理,它通过(如有必要)创建一个新的更大的后备数组,将元素从旧元素复制到新元素,然后丢弃旧的支持数组。它还调整保存列表逻辑大小的 size
字段。
答案 4 :(得分:0)
对象数组为对象指针分配空间,而不是内存中的整个对象。
所以 new String[10]
不会为 10 个字符串分配空间,而是为 10 个对象引用分配空间,这些对象引用将指向数组中存储的字符串。