Выделение памяти Java в стеке против кучи - PullRequest
6 голосов
/ 20 июля 2010

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

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

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

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

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

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

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

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

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

8 голосов
/ 20 июля 2010

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

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

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

foo = test(); //foo can't be final now though
 public static Set<Integer>  test() {
   return new HashSet<Integer>();
}
1 голос
/ 20 июля 2010

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

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

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

0 голосов
/ 27 октября 2010

Вы должны прочитать эту книгу:

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

0 голосов
/ 20 июля 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 для какого теста возвращается, у вас есть «функциональный эквивалент»?

0 голосов
/ 20 июля 2010

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

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

...