Распределение памяти Java в стеке и куче

Я чувствую себя новичком, задающим этот вопрос, но почему, когда я передаю приведенный ниже набор в свой метод и указываю его на новый 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>();

    }

}

person Amir Afghani    schedule 20.07.2010    source источник


Ответы (6)


Java передает ссылки по значению, думайте о mySet как о копии ссылки foo. В void test(Set<Integer> mySet) переменная mySet — это просто локальная переменная внутри этой функции, поэтому ее установка на что-то другое не влияет на вызывающую программу в main.

mySet ссылается (или "указывает", если хотите) на тот же набор, что и переменная foo в main.

Если вы хотите изменить ссылку в main, вы можете сделать, например:

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

... Это потому, что локальные переменные размещены в стеке, и поэтому мой новый сдувается, когда я выхожу из метода?

Нет. Это из-за семантики передачи аргументов Java.

Аргументы Java передаются "по значению", но в случае типа объекта или массива значение, которое вы передаете, является ссылкой на объект/массив. Когда вы создаете и назначаете новый объект набора mySet, вы просто устанавливаете локальную переменную/параметр. Поскольку Java использует передачу по значению, это не влияет на переменную foo в методе main.

Когда вы вводите метод test, у вас есть две копии ссылки на экземпляр HashSet, созданный в методе main; один в foo и один в mySet. Затем ваш код заменяет ссылку в mySet ссылкой на вновь созданный HashSet, но эта новая ссылка не передается обратно вызывающей стороне. (Вы можете изменить свой код, чтобы передать его обратно... например, как результат метода test. Но вы должны сделать это явно.)

ОК, однако, если бы я выполнил операцию добавления или какую-либо другую операцию в вызове моего метода, это распределение было бы сохранено. Почему это?

Это связано с тем, что когда вы вызываете метод экземпляра, используя ссылку в foo или mySet, этот метод выполняется для объекта (HashSet), на который ссылается ссылка. Предполагая, что две ссылки указывают на один и тот же объект, ваше «распределение будет сохранено». Или, точнее, вы можете наблюдать за эффектами операций над одной ссылкой на объект через операции над другими ссылками на тот же объект.

Просто помните, что метод Java вызывает копирование ссылок на объект, а не на сами объекты.

Кстати, вы не сможете добавлять элементы в набор, возвращаемый Collections.emptySet(). Этот заданный объект является неизменным. Вызов (например) add вызовет исключение.

person Stephen C    schedule 20.07.2010
comment
Хорошо, однако - если бы я выполнил add или какую-то другую операцию в вызове моего метода, это распределение было бы сохранено. Почему это? - person Amir Afghani; 20.07.2010

Ваш 'foo' относится к пустому набору, входящему в вызов test(), тестовый вызов не модифицировал объект этот, поэтому при возвращении оттуда он все еще остается пустым набором.

В методе test() mySet — это просто локальная ссылка, которая ссылается на исходный набор (foo) при входе, и когда вы присваиваете новый HashSet этой ссылке, вы теряете ссылку на исходный набор. . Но все эти эффекты полностью локальны для метода test(), потому что Java просто предоставила test() дубликат ссылки на исходный набор.

Теперь в test(), поскольку у вас есть ссылка на исходный объект, вы можете изменить этот объект. Например, вы можете добавить элементы в этот набор. Но вы не можете изменить ссылку в вызывающей функции, вы можете изменить только то, на что она ссылается. Таким образом, вы не можете заменить одну коллекцию другой, и если вам нужен HashSet в первую очередь, вам придется создать новый HashSet в main().

person JustJeff    schedule 20.07.2010

Не уверен, что понимаю вопрос. В методе test вы создаете новый набор и назначаете его локальной переменной mySet. mySet больше не будет ссылаться на тот же набор, что и foo в Main.

Когда вы возвращаетесь из метода, foo по-прежнему ссылается на исходный emptySet(), а HashSet, созданный в методе, будет помечен для сборки мусора.

person Gray    schedule 20.07.2010

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 тому, что возвращает тест, у вас будет «функциональный эквивалент»?

person Cambium    schedule 20.07.2010

вам следует прочитать эту книгу:

«Руководство программиста по сертификации Java SCJP: всесторонний учебник для начинающих (3-е издание)»

person Ankur Agarwal    schedule 27.10.2010