为什么不是每个方法都是静态方法?

时间:2011-07-23 20:17:51

标签: java methods static stack heap

如果将非static函数复制到具有该方法的每个对象的堆中,那么为什么默认情况下不是Java static中的所有方法?为什么要以这种方式浪费所有堆内存?

图解说明对我理解这一点更有帮助。

6 个答案:

答案 0 :(得分:3)

通常,通过将方法复制到堆上每个对象一次,Java方法实现。相反,方法通常使用称为虚拟功能表(或“vtable”)的东西来实现。这个想法是每个方法只有一个副本,无论是静态方法还是非静态方法,并且指向这些方法的指针都放在表格中。然后,堆中的每个对象都存储一个指向vtable的指针,指向其对象类型。这意味着任何堆对象的大小都不依赖于对象具有的方法数。实际上,具有100个方法的对象与具有1个方法的对象的大小相同(假设它们具有相同的字段)。每个只存储一个指向vtable的指针,指向其对象类型,其中只有一个副本。

此优化最初用于C ++以支持快速虚拟功能,并且已经在许多其他面向对象的语言中使用。它允许对象很小,但支持动态调度。

换句话说,默认情况下,方法不需要static,因为它们不会影响堆中对象的大小。对于具有更多函数的对象,创建对象不需要更长时间,或者它使用更多堆空间。

这是一些可能的一些对象布局图(ASCII艺术的道歉!)。假设我们有两个类,A和B.然后在内存中,这些类型的对象可能如下所示:

   A                      vtable for A
 +-------------+        +---------------+
 | vtable ptr  | --+->  | method one    |
 +-------------+   |    +---------------+
 |             |   |    | method two    |
 | fields of A |   |    +---------------+
 |             |   |    |     ...       |
 +-------------+   |    +----------------
                   |    | method N      |
   A               |    +---------------+
 +-------------+   |
 | vtable ptr  |---+
 +-------------+
 |             |
 | fields of A |
 |             |
 +-------------+

   B                  vtable for B
 +-------------+     +------------+
 | vtable ptr  | --> | method one |
 +-------------+     +------------+
 |             |     | method two |
 | fields of B |     +------------+
 |             |     |    ...     |
 +-------------+     +------------+
                     | method M   |
                     +------------+

注意类型A的两个对象如何共享相同的vtable,以及类型A和B的对象如何仅为其vtable指针使用相同数量的空间,即使它们具有不同数量的方法。

答案 1 :(得分:1)

静态方法无法访问对象的实例成员变量......没有状态,没有OOP。

答案 2 :(得分:1)

您的问题是在程序编程和面向对象编程之间进行比较。 http://en.wikipedia.org/wiki/Procedural_programming#Comparison_with_object-oriented_programming。面向对象与程序的主要动机是使数据结构能够自行执行操作。

关于内存分配的假设,方法不会复制到每个对象,因为Java不支持动态类型修改。例如,如果对象的类型为Foo,那么它将具有由Foo类型声明的所有方法。在不更改类型本身的情况下,无法将新方法添加到Foo的实例。每当在一个对象上调用一个方法时,它就会在后面作为一个过程运行。

无论何时运行:

foo.say( "Hello, world!" );

Java实际上是这样的:

  1. 创建字符串“Hello,world!”
  2. 查找为foo声明的类型。
  3. 在该类型中查找具有签名say(String)的方法。
  4. 使用foo对象的实例状态运行该方法。
  5. 因为方法保持自己的状态,所以通过将对象实例作为方法参数传递,可以以静态方式实现任何非静态方法。实际上,上面的步骤4很可能是由Java编译器以这种方式实现的。

答案 3 :(得分:1)

不会“为堆上的每个对象复制”非静态方法。每个类加载器只有一个方法(代码)的副本,与static方法的副本相同。

使方法static与内存管理无关 - 静态方法需要一些堆栈和堆,就像动态方法一样。

有关static用途的说明,请参阅Understanding Instance and Class Members Java教程。

答案 4 :(得分:0)

因为如果每个方法都是静态的,那么java就不会是面向对象的。

您需要在不同的对象上调用方法,因为它们具有不同的状态。

至于内存 - 每个静态调用也会进入堆栈。

答案 5 :(得分:0)

该方法的代码不在堆栈中,它位于堆的一部分中(永久生成,由垃圾收集器特殊引用)。在堆栈上只定位了方法的局部变量(以及类似的运行时数据,如返回地址)。

这与静态或非静态方法无关。

此外,该方法的代码将为该类的每个对象复制 - 只是在执行该方法时,它会获得一个额外的参数,该参数指向调用它的对象。这个参数是静态和非静态方法之间唯一的内存开销。