Java内存分配:栈和堆的区别

6

我觉得我很菜,因为我要问这个问题——当我将以下Set传递到我的方法并将其指向一个新的HashSet时,为什么它仍然是EmptySet?是因为局部变量在堆栈上分配,所以当我退出方法时,我的new就被清除了吗?我该如何实现功能等效性呢?

import java.util.HashSet;
import java.util.Set;

public class TestMethods {

    public static void main(final String[] args) {

        final Set<Integer> foo = java.util.Collections.emptySet();
        test(foo);

    }

    public static void test(Set<Integer> mySet) {

        mySet = new HashSet<Integer>();

    }

}
6个回答

8
Java按值传递引用,将mySet视为foo引用的副本。在 void test(Set<Integer> mySet)中,mySet变量只是该函数内的局部变量,因此将其设置为其他内容不会影响main调用者。

mySet确实引用(或“指向”)与主函数main中的foo变量相同的Set。

如果要更改主函数中的引用,可以执行以下操作:

foo = test(); //foo can't be final now though
 public static Set<Integer>  test() {
   return new HashSet<Integer>();
}

8
...是因为局部变量在栈上分配,所以当我退出方法时,我的新变量就被清除了吗?
不是。这是由于Java的参数传递语义。
Java参数是按值传递的,但对于对象或数组类型,您传递的值是对象/数组引用。当您创建并将一个新的set对象分配给mySet时,您只是设置本地变量/参数。由于Java使用按值传递,这对main方法中的foo变量没有影响。
进入test方法时,您有两个指向在main方法中创建的HashSet实例的引用副本;一个在foo中,一个在mySet中。然后,您的代码将mySet中的引用替换为对新创建的HashSet的引用,但是这个新引用不会传递回调用者。(您可以更改代码将其显式地返回...例如作为test方法的结果。)
好的,但是如果我在我的方法调用中添加或执行其他操作,那么分配就会被保留。为什么?
这是因为当您使用foo或mySet中的引用调用实例方法时,该方法在引用所指向的对象(HashSet)上执行。假设这两个引用指向同一个对象,则"分配将被保留"。或者更准确地说,您可以通过对其他引用执行操作来观察对对象的一个引用执行操作的效果。
只需记住Java方法调用会复制对象的引用,而不是对象本身。
顺便说一句,您将无法向由Collections.emptySet()返回的集合添加元素。该集合对象是不可变的。调用(例如)add会抛出异常。

好的,然而——如果我在我的方法调用中执行添加或其他操作,那么该分配将被保留。为什么会这样? - Amir Afghani

1

你的'foo'指向了一个空集合,进入test()调用时,test调用没有修改那个对象,所以在从那里返回时它仍然是一个空集合。

在test()方法内部,'mySet'只是一个本地引用,它引用了进入时原始集合(foo),当你将一个新的HashSet分配给该引用时,你失去了对原始集合的引用。但这些影响都完全局限于test()方法内部,因为Java只是给test()提供了对原始集合引用的副本。

现在,在test()中,由于你有对原始对象的引用,你可以修改该对象。例如,你可以向该集合添加元素。但你不能改变调用函数中的引用,你只能改变它所引用的内容。因此,你不能用不同的集合替换一个集合,如果你想要一个HashSet,你必须在main()中创建一个新的HashSet。


0
import java.util.HashSet;
import java.util.Set;

public class TestMethods {

    public static void main(final String[] args) {

        final Set<Integer> foo = java.util.Collections.emptySet();
        test(foo);

    }

    public static void test(Set<Integer> mySet) {
        // here mySet points to the same object as foo in main
        mySet = new HashSet<Integer>();
        // mySet now points to a new object created by your HashSet constructor call,
        // any subsequent operations on mySet are no longer associated with foo, because
        // they are no longer referencing the same object
    }
}

我不确定我是否理解这个问题,您是在寻找返回值吗?如何实现功能等效?
    public static Set<Integer> test(Set<Integer> mySet) {
        for(Integer i : mySet){
            // do something??
        }
        mySet = new HashSet<Integer>();
        return mySet;
    }

现在,如果你将foo分配为test返回的内容,你就有了“功能等效物”?

0

不确定我理解问题了。在test方法中,您正在实例化一个新集并将其分配给本地变量mySetmySet将不再引用Main中的foo所引用的相同集合。

当您从该方法返回时,foo仍然引用原始的emptySet(),而在方法中创建的HashSet将被标记为垃圾回收。


0
你应该阅读这本书:
《Java SCJP认证程序员指南:全面入门(第三版)》

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