Java有指针吗?

58
如果Java没有指针,那么在Java中new关键字是用来做什么的?

这里有多篇帖子明确表示“不太可能”。我想知道 - Java是否有任何机制(在模块/扩展中)可以直接写入内存,就像旧的outb(0x08,0x378);或旧的peek / poke在“小型CPU”上一样。 - SF.
1
SF,sun.misc.Unsafe类(http://www.docjar.com/docs/api/sun/misc/Unsafe.html),你要找的是putByte方法。 - Vlad Gudim
@Totophil:严格来说,这不是“Java”,因为它没有标准化。这是Sun JVM(因此也是OpenJDK)的实现细节。 - Joachim Sauer
Java具有调用本地(即非Java)代码的能力。这样,您可以做任何本地代码无法完成的事情,但是Java中没有直接硬件访问。 - Joachim Sauer
4
空指针异常。这就足以解决问题了。我认为这里答案的得票数很高,声称Java没有指针,这是反对stackoverflow的最好论据。 - user4590120
显示剩余2条评论
12个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
48

正如所指出的,Java有引用。 它们与其他引用有何不同?

  1. 您无法对这些引用执行算术或其他类似操作。
  2. 它们不指向包含对象的内存(即它们不是指针的另一种名称)。 JVM可以自由地在VM内存中移动对象,并且在垃圾收集期间很可能会这样做。 然而,这些引用仍然指向该对象,尽管它在内存中移动。

因此,它们不像C++引用(直接指向对象)。 也许更好的名称应该是句柄


5
实际上,在实现层面上,引用通常指向包含对象的内存的指针。然而,普通的Java程序无法像C语言那样“探究底层”,并将引用用作指针。 - Stephen C
7
你能否提供以上内容的参考资料?考虑到JVM可以任意移动对象并保持其引用,我怀疑至少一个引用应该是指向指针的指针。 - Brian Agnew
14
实际上有一个著名的指针,空指针(NullPointer),在我的应用程序中似乎总是与异常结为伴侣 :) - extraneon
2
@jalf 这只是一种模糊的表述。如果你真的有一个实际上说了这个的参考资料,那么你应该按要求发布它;否则,就会有结论认为你是在编造。我刚刚查阅了我的两本编译器实现101书籍之一是Aho、Sethi和Ullman,它们都没有说过这样的话。JVM文档中出现“托管指针”和“压缩oops”等语言,清楚地表明对象引用并不是直接指针。 - user207421
2
@jalf - 我并不是特别怀疑你,但我只是想找到一些真正描述正在发生的事情的参考资料。 - Brian Agnew
显示剩余9条评论

46

Java没有指针,而是有引用。

这是一个微妙的区别。指针有一些额外的操作,你可能(也可能不)会经常使用。而引用缺少这些操作,因为这些操作可能会不安全。

例如,如果你使用指针来索引数组的第一个元素:

int squares[] = {1, 4, 9, 16, 25, 36, 49};
int* intPointer = squares;

你可能想要解引用指针并获得值“1”,但你也可能:

intPointer++

然后当你解引用该指针时,你将得到值为"4"。第二个

intPointer++;

当引用它时,会得到值“9”。这是因为 ++ 操作将指针在内存中向前移动了一个“单位”。

问题在于 C / C++ 类型检查系统的弱点(C++ 必须保持与 C 的兼容性,因此允许相同的问题)。指针存储内存中的地址,++ 操作会向地址添加适当数量的字节。在许多系统中,对 int 进行 ++ 操作会添加四个字节,但如果指针是 char 指针,则 ++ 操作应该只添加一个字节。请注意,由于指针的底层数据类型是内存中的地址,因此以下内容是合法的(但不推荐):

char* charPointer = squares;
charPointer++;

void* voidPointer = squares;
voidPointer++;
由于指针是内存中的地址,因此它们可以(正确地)表示计算机中任何一位内存,但只有在底层数据匹配指针的类型和对齐方式时才能正确引用。对于没有经过大量代码管理以使其安全的指针,这意味着您可能会偏离所需信息的数据类型(或对齐方式),并且引用解除引用可能会导致灾难性后果。尝试使用自定义代码解决此问题往往会严重减慢一个指针,从而注意到性能问题,并为添加错误打开了自定义“指针管理”代码的大门。 Java通过返回引用来避免所有这些问题。引用不会引用内存中的任何位置;Java维护内部“引用到指针”表。该表获取引用并返回与其相关联的数据,无论该数据在内存中的哪个位置。这会减慢代码执行,因为每次“解引用”都要进行两次查找,一次在引用表中查找,一次在机器的内存中查找。 Java使用引用的一大优点是可以移动内存而不会破坏原本应该存在的指针地址。在C程序中,如果将数据移到新的内存位置,则很难知道程序的其他部分是否有指向数据的指针。如果在内存移动后解除引用过时的指针,则程序将访问损坏的数据,并且通常会崩溃。 在运行程序中移动内存的能力使程序可以轻松地回收内存。任何不需要大块内存的程序都可以释放未使用的内存,但是这会在已使用内存块之间创建未使用内存的内存空洞。计算机内部使用的内存页面非常大。如果可以将稀疏使用的内存页面中的一些已使用位移动到另一个页面中,则可以释放内存页面。这增加了数据对内存的密度,提高了缓存性能。有时,这会带来相当显著的性能改进。 Java的垃圾回收器利用引用的使用方法,通过暂时阻塞一组引用对数据的访问来移动数据(以压缩数据)。在此阻塞期间,引用地址表具有新的内存地址。由于“功能”代码层首先根本不知道这些地址,因此此操作不会破坏正在运行的Java程序。

1
不用担心吹毛求疵的问题,但是引用最终必须转换为地址。指针表是最合适的方法,但你说得对,其他方法也可能存在。指针表并非必需,但如果没有某种查找方式,你将处理一些由算法生成的东西,这几乎不会有助于内存压缩。 - Edwin Buck
1
@EdwinBuck:最合逻辑的做法难道不就是创建一个指向所指数据的指针吗?在垃圾回收时移动所指对象时,只需更新指针即可。 - jalf
1
@jalf 这是一个逻辑上正确的做法,但容易出现问题。基本上指针值可以复制到多个位置,因此除非你有某种奇特的“跟踪每次这个指针的值被复制,并保留所有这些值的位置的引用,包括跟踪这些位置是否随时间而被摧毁”的系统,否则你只会更新一部分指针值(这将非常糟糕)。实际上,你甚至不能真正创建这样的系统;因为其中一些值可能会在程序之间传递。有指向指针表的引用,它只存在于一个地方。 - Edwin Buck
@JoachimSauer 有哪些其他可能的实现方法来解决“引用类型<->对象”的问题? - Quazi Irfan
在C++中,void*没有指针算术运算或隐式的int*char*转换。 - T.C.
显示剩余9条评论

14

Java有指针,它们是存储内存数据引用的变量。在这个意义上,Java中所有的Object类型的变量都是指针。

然而,与像C语言一样的语言不同,Java语言不允许对指针值进行算术运算。


2
“直接操作”有点不太准确:可以将另一个值(即另一个引用)赋给变量,但是指针算术运算(例如将指针加1)是不可能的。 - Joachim Sauer
这意味着如果在Java中不允许使用指针进行算术运算,并不意味着没有指针存在。 - hipokito

7

new 大致执行以下步骤:

  1. 查找一个连续的自由堆内存块,大小等于您要创建的类的实例大小,再加上一些用于簿记的空间。
  2. 将该空间清零并从空闲列表中删除它。
  3. 运行构造函数。
  4. 返回对已创建实例的引用(不是指针,因为其他帖子已经解释过)。

深刻的事实。 - lft93ryt

6
Java确实有指针,它们被称为“引用(reference)”。当人们说“Java没有指针”时,他们通常将指针的概念与C和C衍生语言中的指针的特定实现和能力混淆了。 特别地: - Java引用不能被设置为任意地址。标准的Pascal和Fortran指针也不能这样做。 - Java引用不能被设置为指向一个变量。标准的Pascal指针也不能这样做。 - Java引用不支持指针算术运算。Pascal和Fortran指针也不支持。 - Java引用不能指向对象的某个部分(例如数组的第三个元素)。Pascal指针也不能这样做。 此外,与普遍信仰相反,指针不一定是一个地址。指针通常被实现为一个地址,但即使在C或C++中也没有必要这样做。

1
我认为混淆的原因在于“指针”这个术语几乎普遍与C/C++相关联。因此,大多数人会更好地理解Java没有指针的说法。不过:点赞。 - B M
这是这里最好的答案。我希望我能给它更多的赞。 - klutt
我想知道这个错误的梗(即Java没有指针)是从哪里来的。这造成了很多混乱。声称这一点的人不应该被允许编写软件。 - user12411795

5

java.lang.NullPointerException

在面试中,有人告诉我“Java没有指针”。我通常会给他们一些Java代码,并让他们解释这段代码发生了什么:

public class TestPointers {

    public static void main(String args[]) {
        Object p1, p2;
        p1 = new Object();
        p2 = p1;
        p1 = null;
        System.out.println(p2);
    }
}

6
今天我上了第一节Java课,我对这个例子感到非常困惑。为什么大家都说Java有引用(reference)呢?在我看来,似乎所有的东西都是指针(pointer),但你不能进行指针算术运算。 - tom
1
发生的情况是你在第一行声明了两个对象。在第二行,你初始化p1,然后在第三行将p2设置为等于p1。最后,你使p1引用null。p2仍然会(并且应该)等于新对象(new object())。 - J Code
1
@ChrisGray:我不太理解这个答案。练习结束时p2有一个值并且不是null,这证明Java确实没有指针,否则p2将指向p1并且也是null - user1191027
1
请所有还不清楚的人,谷歌本回答开头提到的类名。 - user4590120
2
@ThreaT 不会,因为当你修改p1时,你使它引用null,但你并没有销毁对象;对象和它的引用是不同的东西:p2仍然指向创建的对象,直到你使p2引用null;然后,对象将被GC销毁,因为没有任何引用它。C指针类似于此:考虑以下代码void main() { int n, *p1, *p2; n = 42; p1 = &n; p2 = p1; *p1 = 100; p1 = NULL; printf("%d\n", *p2); }它将打印“100”,因为当你做p1 = null时,你修改了指针值,而不是它所指向的值。 - bd95
显示剩余3条评论

1

Java有引用。所有对象都是通过引用它们的实例来访问的。您可以使用new创建一个新实例,该实例返回对对象的引用。

Java引用不像C中的指针,您不能“查看”组成对象的原始内存。


3
“看看引擎盖下面”不是指针的属性,而是语言的属性;特别是在C和C++中,您可以“看看引擎盖下面”,因为(1)任何指针都可以转换为指向char的指针(类型转换不是指针的固有属性),(2)char保证直接映射到机器字节(这显然不是指针的属性),以及(3)C和C++具有指针算术运算(再次强调,这不是通用指针特性,而是这些语言的特定功能)。 - celtschk

0

new 在 Java 中返回一个指向新创建对象的引用


0

new 返回引用。它与指针有一些相似之处(如果传递给函数,引用会被传递,就像指针一样),但没有指针算术运算。


-1
Java没有指针。在Java中,使用"new"运算符来引用变量。

这不太清楚。new 操作符与变量有什么本质关系吗?例如,如果我运行 System.out.println(new String("hello"));,其中使用了 new,那么“引用变量”在哪里? - Chai T. Rex

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