2009-09-24 2 views
1

Я наткнулся на очень загадочную черту (?) Java.Java аргумент аргумента головоломка

кажется, что с помощью «нового» ключевого слова, чтобы заменить аргумент метод рода сдвиги, объект в другую область:

import java.util.ArrayList; 

public class Puzzle { 
    public static void main(String[] args) { 
     ArrayList<Integer> outer = new ArrayList<Integer>(); 
     outer.add(17); 
     Puzzle.change(outer); 
     outer.add(6); 
     System.out.println(outer); 

     // excpected output: 
     // [23] 
     // [23, 6] 
     // 
     // actual output: 
     // [23] 
     // [17, 7, 6] 
    } 

    public static void change(ArrayList<Integer> inner) { 
     inner.add(7); 
     inner = new ArrayList<Integer>(); 
     inner.add(23); 
     System.out.println(inner); 
    } 
} 

Может кто-нибудь объяснить эту странность? Я заметил такое же поведение с назначением.

+0

Это не головоломка, а базовая концепция. Google для «вызов по значению против вызова по ссылке». –

ответ

8

Это классическая подножка для Java начинающих. Частью проблемы является то, что нам не хватает общего языка для определения этого. Передаются ли параметры по значению? Объекты передаются по ссылке? В зависимости от того, с кем вы разговариваете, будут разные реакции на слова, проходящие по значению и проходящие по ссылке, хотя каждый хочет сказать то же самое.

Способ визуализации заключается в том, чтобы понять, что переменная java может быть одной из двух вещей. Он может содержать примитив или ссылку на объект. Подумайте об этой ссылке как о числе, указывающем на местоположение в памяти. Это не указатель в значении C++, поскольку он не указывает на определенную ячейку памяти, это скорее дескриптор, поскольку он позволяет JVM искать конкретную ячейку памяти, в которой объект может быть найден. Но вы можете себе это так:

ArrayList<Integer> outer = @1234; //The first reference where the ArrayList was created. 

Вы затем вызвать внутренний с параметрами:

Puzzle.change(@1234); 

Обратите внимание, что вы не сдадите внешнюю переменную, необходимо передать значение @ 1234. внешний не может быть изменен каким-либо образом, являясь параметром метода. Это то, что мы имеем в виду, когда говорим, что проходим по значению. Значение передается, но оно отключается от внешней переменной.

Внутри головоломки:

public static void change(ArrayList<Integer> inner) { // a new reference inner is created. 
    //inner starts out as @1234 
    inner.add(7); 
    //now inner becomes @5678 
    inner = new ArrayList<Integer>(); 
    //The object @5678 is changed. 
    inner.add(23); 
    //And printed. 
    System.out.println(inner); 
} 

Но внешняя по-прежнему указывает на @ 1234, как метод не может изменить эту ситуацию, он никогда не имел внешнюю переменную, она только что его содержимое. Однако, поскольку метод изменения начался со ссылкой на @ 1234, объект в этом месте действительно может быть изменен методом и результатами, видимыми для любой другой ссылки на @ 1234.

После того, как метод изменения завершен, ничто не ссылается на объект @ 5678, поэтому оно становится пригодным для сбора мусора.

6

Это один из классических java-вопросов для начинающих.

Внутренний параметр передается по значению (для не примитивного объекта это ссылка). Если вы воздействуете на него, он воздействует на тот же объект, что и во внешнем коде, поэтому вы видите влияние внешнего метода.

Если вы замените объект новым объектом, это не то же самое. Когда вы работаете над новым, вы не меняете предыдущий, и вы не видите воздействия в своем внешнем методе.


Обновление: извините за ошибку, связанную с лексикой, которая вызвала много комментариев. Я исправил свой ответ сейчас. Я считаю, что это было сделано, но теперь это намного яснее.

+5

Фактически, он вызывается по значению, поэтому вы не можете изменить значение внешней ссылки. –

+0

Хм .. ладно. Поэтому в моем случае JRE создает две отдельные ссылки на один объект, но переназначение ссылок не заменяет исходный объект в памяти. Я думаю, что эталонная копия хороша только для передачи сообщений. – user178476

+0

«Внутренний параметр передается по ссылке». - Нет ... !! Java не поддерживает передачу по ссылке. Параметр «внутренний» передается по VALUE, но сам по себе является ссылкой - это ССЫЛКА, которая передается по VALUE. Это не то же самое, что передавать по ссылке. – Jesper

2

Ссылка outer все еще указывает на исходный массив ArrayList, который был создан в вашем основном методе. Только ссылка inner всегда указывает на новый ArrayList, созданный внутри метода change, и этот ArrayList никогда не ссылается за пределами change.

Ожидаемое поведение. Вам нужно будет вернуть ссылку на внутренний массив ArrayList, чтобы получить доступ к нему из области вызова.

1

Вы "фактический выход" имеет смысл. Вы переопределили внутреннее значение на новое значение в методе change(), и внешний вид больше не затрагивается. Попробуйте выполнить трассировку с помощью отладчика, и вы поймете, что происходит.

1

Вы не сменили область видимости, вы просто назначили новый ArrayList параметру inner. Попробуйте сделать inner a final параметр, чтобы предотвратить это.

+1

Ключевое слово 'final' будет препятствовать вам делать что-то глупое, но нет эквивалента ключевых слов 'ref', который даст «ожидаемый» вывод. –

0

«внутренний» - это ссылка на тот же объект, что и исходные (внешние) ссылки (поскольку Java использует метод передачи по ссылке для параметров объекта в методах, поэтому внутренний и внешний оба указывают на один и тот же объект), то внутри метода вы сделайте «внутреннюю» ссылку новым объектом.

Важными вещами в Java являются то, что это не изменяет то, что «внешний» ссылается в основном методе. Как только вызов метода заканчивается, «внутреннее» перестает быть в сфере видимости и в конечном итоге будет мусором. Объект, на который указывает «внешний», все еще находится в области видимости (поскольку внешний объект все еще активен).

1

Насколько я понял, это не головоломка, Java поддерживает только передачу по значению, я имею в виду всегда копировать аргументы. Подробнее here.

6

В java вы можете думать обо всех переменных как указатели на реальные объекты.

Что происходит:

  1. Вы создаете список.
  2. добавить 17 к нему.
  3. отправьте список на другую функцию , у которой есть свой собственный указатель на список .
  4. добавить 7 в первый список.
  5. Создайте новый список и наведите указатель мыши на внутренний указатель .
  6. добавить 23 в новый список.
  7. распечатать новый список и вернуться.
  8. В исходной функции у вас есть есть указатель, указывающий на тот же объект, что и раньше.
  9. вы добавляете 6 в первый список.
  10. и распечатать первый список.
1

Встраивание метода Вы получаете следующий эквивалентный код:

import java.util.ArrayList; 

public class Puzzle { 
    public static void main(String[] args) { 
     ArrayList<Integer> outer = new ArrayList<Integer>(); 
     outer.add(17); 

     ArrayList<Integer> inner = outer; 
     inner.add(7); // inner refers to same array list as outer 

     inner = new ArrayList<Integer>(); // inner refers to new array list 
     inner.add(23); 
     System.out.println(inner); // new list is printed 

     outer.add(6); 
     System.out.println(outer); // outer list is printed 
} 

}