将值放入HashMap后更改该值是否会更改HashMap中的内容?

40
如果我创建一个新的HashMap和一个新的List,然后将List放入HashMap中并使用任意键,之后调用List.clear(),这会影响我放入HashMap中的内容吗?
更深层次的问题是:当我向HashMap添加内容时,是复制并放置一个新对象还是放置对原始对象的引用?
谢谢!
8个回答

50

这里发生的情况是你将一个列表的指针放在了哈希表中,而不是列表本身。

当你定义:

List<SomeType> list;

你正在定义一个指向列表的指针,而不是列表本身。

当你执行以下操作时

map.put(somekey, list);

你只是存储了指针的副本,而不是列表本身。

如果在其他地方跟随该指针并修改其末尾处的对象,则持有该指针的任何人仍将引用同一已修改的对象。

请参见http://javadude.com/articles/passbyvalue.htm,了解Java中传递值的详细信息。


9
当然有啊。你为什么认为会有一个“new”运算符呢?请阅读我上面引用的文章——它将帮助你理解Java实际上正在做什么。即使Java没有像C/C++一样实现指针,也并不意味着它没有指针。Pascal实现了没有对它们进行算术运算的指针... - Scott Stanchfield
4
永远不可能有足够多的链接到它......(我把它放在上面答案的评论中,因为它们是先发布的 - 许多人不会读取超过前几个答案并简单地给它们点赞)。程序员可以很清楚地看到区别--swap(x,y)和输出/变量参数是一些语言(例如C ++、Ada、Pascal)中存在但Java中不存在的两个非常重要的语言特性。 - Scott Stanchfield
2
@Diego,有一些术语问题。但是,如果您查看已接受的术语而不是Sun“官方”称呼它们的方式,那么正在发生什么就非常清楚了。Sun通过使用已接受的术语来定义事物名称的含义,明确说明引用指针(JLS 4.3.1)。 - Adam Jaskiewicz
3
人们看到“参考”这个词就感到困惑。Sun公司决定称指针为“引用值”,使得“按引用传递”深入人心。 - Adam Jaskiewicz
1
@HDave - final 关键字作用于指针或原始值 - 它只能被设置一次,非常有意义和有用。另一方面,不可变对象完全取决于实现。像Java的String类这样的良好实现确保对象持有的数据无法更改。糟糕的实现可能会泄漏可以用于更改数据的底层细节。良好的实现再次具有意义。 - Scott Stanchfield
显示剩余4条评论

13

Java是按值传递引用的。

将列表添加到哈希表中只是将引用添加到哈希表中,指向同一列表。因此,直接清除列表确实会清除您在哈希表中引用的列表。


7
啊啊啊!!!不!!!Java 严格按值传递! 参见 http://javadude.com/articles/passbyvalue.htm - Scott Stanchfield
2
我们知道,Java是按值传递的。然而,这样说可能会让人感到困惑。Java的行为就像是按引用传递一样。 - jjnguy
3
这件事情有巨大的危害。传递引用语义允许你做一些事情,比如创建一个交换方法(请参阅我在答案的第一条评论中提到的文章); 在Java中你不能这样做。 - Scott Stanchfield
1
我不介意“按值传递指针”(请注意连字符的位置 - 当您使用连字符时,它往往被视为官方术语)。术语“按值传递”和“按引用传递”具有非常具体(且重要)的含义。最好将这些概念分开思考:1)Java具有指针,2)指针的值是目标对象的位置,3)当您传递参数时,您正在传递一个值,4)目标位置被传递/复制到形式参数。因此,您最终得到的形式参数指向与参数相同的对象。 - Scott Stanchfield
4
我不明白为什么这个问题会这么难理解。实际上,这是一个非常明显的区别,唯一的争议点在于Sun决定将指针称为“引用值”。他们在JLS中提到,引用值是指向实例或数组的指针(4.3.1),并且清楚地说明方法参数存储的是(4.12.13)。因此,Java具有指针,并采用按值传递。 - Adam Jaskiewicz
显示剩余7条评论

5
Map<Integer, Integer> hasmapA = new HashMap<>();
hasmapA.put("key1", "value1");
hasmapA.put("key2", "value2");
hasmapA.put("key3", "value3");
  1. 按引用复制:如果您将一个HashMap赋值给另一个,则两者都指向内存中的同一引用。

    Map hasmapB;

    hashmapB = hashmapA;

如果您更改其中任何一个,更改将反映在两个HashMap中,因为两者都引用同一位置。

  1. 按值复制:如果您想要

克隆/深拷贝/创建单独的内存位置/创建单独的对象

hashmapA的内容并将其复制到hashmapB中

Map<Integer, Integer> hashmapB = new HashMap<>();;

hashmapB.putAll(hashmapA)

注意:** 你有没有注意到HashMapB声明中的两个点之间的不同之处?在第二个点中,我们必须调用HashMap构造函数。这样我们就可以将hashmapA中的所有数据复制到hashmapB中。

参考资料


5
当我向HashMap添加内容时,是复制一个新对象并放置它,还是放置一个对原始对象的引用?
答:总是放置一个对对象的引用。如果你清空HashMap,那么这个对象仍然会存在。如果没有人再引用该对象,那么垃圾回收器将销毁该对象。如果您需要复制它,请查看Object.clone()方法和Cloneable接口。

1

试一试

包测试32;
import java.util.ArrayList; import java.util.HashMap; import java.util.List;
class Foo { public Foo(int id, String name) { this.id=id; this.name=name; }
public static void main(String[] args) { HashMap strs = new HashMap<>();
// 创建一个对象列表 List ls = new ArrayList<>(); ls.add(new Foo(1, "Stavros")); ls.add(new Foo(2, "Makis")); ls.add(new Foo(3, "Teo")); ls.add(new Foo(4, "Jim"));
// 将对象的引用从列表复制到哈希映射中 strs.put("1", ls.get(0)); strs.put("2", ls.get(1)); strs.put("3", ls.get(2)); strs.put("4", ls.get(3));
System.out.println("更改前列表 : " + ls); System.out.println("更改前哈希映射: " + strs); // 从哈希映射中获取对象 Foo f=(Foo)strs.get("1"); // 设置不同的值 f.setId(5); // 观察到差异反映在列表和哈希映射中 System.out.println("更改后列表 : "+ls); System.out.println("更改后哈希映射: "+strs); }
private int id; public void setId(int id) { this.id=id; } public int getId() { return this.id; }
private String name; public void setName(String name) { this.name=name; } public String getName() { return this.name; }
public String toString() { StringBuilder sb = new StringBuilder(); sb.append(id); sb.append("-"); sb.append(name); return sb.toString(); } }

1
以下是正确的答案: 假设您有一个名为 hashmap 的 HashMap,最初您在此 HashMap 中放置了一个键值对,例如 hashmap<"test1","test2">。 之后,当您将此hashmap传递给一个函数时,在该函数中再次更改其值为test3,例如 hashmap.put("test1", "test3"),并在main方法中再次打印地图时,Java按值传递概念在这里失败了。
原因是: 当您使用HashMap时,它对Key(test1)进行哈希处理并存储值。当您将其传递到再次更改其值的函数中时,它会再次对同一键进行哈希处理并获取相同的内存地址,并相应地更改值。 这就是为什么当您尝试检索键“test1”时,它会给出“test3”的结果的原因。

1

通常在Java中,您总是处理引用(除非您使用“new” [1] 显式地创建新对象)。

因此,在映射中存储的是引用而不是完整的对象副本,更改列表也会影响遍历映射时看到的内容。

这是一种特性,而不是错误 :)

[1] 纯粹主义者会包括“clone()”和序列化,但对于大多数Java代码,“new”是获取对象的方法。


你是指“纯粹主义者”吗?我现在脑海里浮现出了燕麦片商标上的Quaker Oats小老头... - Scott Stanchfield
随便啦 :) 我不是以英语为母语的,哈哈 :-D - Thorbjørn Ravn Andersen
除了原始类型,Java中你总是处理引用。一个new表达式会被求值为一个引用。 - user102008

-2
Java中的所有东西都是按值传递。当您传递一个对象时,您传递的值是对象引用。具体来说,您传递的是对象引用的副本。在Java中也没有指针,尽管引用类似。请正确理解!

6
Java 中有指针。你认为 "Dog d;" 定义了什么?如果它实际上是一个对象,为什么不能在不指向它的情况下要求它叫:"d = new Dog();"? - Scott Stanchfield
4
根据《Java语言规范》第4.3.1节:"引用值(通常称为引用)是指向这些对象的指针,还有一个特殊的null引用,它不指向任何对象。" - Adam Jaskiewicz
1
另外需要注意的是:NullPointerException是一个非常好的例子,展示了在公关机器开始运转之前他们的思考方式。他们将概念视为指针,但在游戏晚期决定更改名称并错过了一些细节...请注意,有语言设计概念和特定的语言设计者命名。任何人都可以编写一种语言。不幸的是,有些人会犯命名错误。 - Scott Stanchfield
3
Java 有指针,但没有指针算术运算。 - wds
1
@Scott,非常同意,引用类型是按值传递的指针,因此当指针没有指向一个有效对象时,我们会遇到NullPointerException。仅仅因为它们不能通过赋值以外的方式进行操作,并不意味着它们不是指针,只是说明它们比C/C++等指针更受控制。 - Geoff
显示剩余5条评论

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