Изменение переданных параметров в Java (вызов по ref, значению) - PullRequest
1 голос
/ 02 июля 2010

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

public void toCommand(Stringbuffer buf) {

  buf.append("blablabla");
}

Тогда вызывающая сторона этого метода использовала такую ​​функцию:

StringBuffer buf = new StringBuffer();
toCommand(buf);
String str = buf.toString();

Теперь я думал, что этот код даст str значение "", но на самом деле он даст ему значение из метода. Как это возможно? Я думал, что в Java все не работает так?

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

Я действительно потратил некоторое время на поиски этого, но моя интерпретация того, что говорят эти источники, заключается в том, что это не должно работать. Чего мне не хватает?

http://www.yoda.arachsys.com/java/passing.html
http://javadude.com/articles/passbyvalue.htm

Себастьян

Ответы [ 8 ]

11 голосов
/ 02 июля 2010

Java передается по значению. значение ссылки на объект - это объект ссылка , а не сам объект. И поэтому метод toCommand получает копию значения, которое является ссылкой на объект & mdash; тот же объект, на который ссылается вызывающая сторона.

Это точно так же, как когда вы ссылаетесь на объект из двух переменных:

StringBuffer buf1;
StringBuffer buf2;

buf1 = new StringBuffer();
buf2 = buf1; // Still ONE object; there are two references to it
buf1.append("Hi there");   
System.out.println(buf2.toString()); // "Hi there"

бесплатный ASCII art:

                  +--------------------+
    buf1--------->|                    |
                  | === The Object === |
                  |                    |
    buf2--------->|    Data:           |
                  |      * foo = "bar" |
                  |      * x = 27      |   
                  |                    |
                  +--------------------+

Еще один способ думать о том, что JVM имеет основной список всех объектов, проиндексированных по идентификатору. Мы создаем объект (buf1 = new StringBuffer();), а JVM назначает объекту идентификатор 42 и сохраняет этот идентификатор для нас в buf1. Всякий раз, когда мы используем buf1, JVM получает значение 42 из него, ищет объект в своем основном списке и использует объект. Когда мы делаем buf2 = buf1;, переменная buf2 получает копию значения 42, и поэтому, когда мы используем buf2, JVM видит ссылку на объект # 42 и использует тот же объект. Это не буквальное объяснение (хотя и со стратосферной точки зрения, и если вы читаете «JVM» как «JVM и диспетчер памяти и ОС», это не за миллион миль от него), но полезно для размышления о том, какие ссылки на объекты на самом деле есть.

С этим фоном вы можете видеть, как toCommand получает ссылку (42 или что-то еще), а не фактические StringBuffer данные объекта. И поэтому операции над ним ищут его в главном списке и изменяют его состояние (поскольку оно содержит информацию о состоянии и позволяет нам изменять его). Вызывающий видит изменения в состоянии объекта, потому что объект хранит состояние, ссылка просто указывает на объект.

В любом случае ... писать код наподобие этого на Java следует считать плохой практикой, верно?

Совсем нет, это нормальная практика. Было бы очень трудно использовать Java (или большинство других языков ООП) без этого. Объекты большие по сравнению с примитивами, такими как int и long, и поэтому их дорого обходить; ссылки на объекты имеют размер примитивов, поэтому их легко передавать. Кроме того, наличие копий вещей затрудняет взаимодействие различных частей системы. Наличие ссылок на общие объекты делает это довольно простым.

4 голосов
/ 02 июля 2010

StringBuffer является изменяемым .Метод toCommand() получает ссылку на эти объекты, которая передается по значению (ссылка), ссылка позволяет методу изменять изменяемый StringBuffer.

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

И я не понимаю, почему это должно быть плохой практикой.

2 голосов
/ 02 июля 2010

Это потому, что в методе при передаче объекта копия ссылки на объект передается по значению.Рассмотрим пример ниже:

    public class Test {

     public static void modifyBuff(StringBuffer b)   {
        b.append("foo");
     }

     public static void tryToNullifyBuff(StringBuffer b)   {
        b = null; // this will not affect the original reference since 
                  // the once passed (by value) is a copy
     }


     public static void main(String[] args) {
        StringBuffer buff = new StringBuffer();  // buff is a reference 
                                                 // to StringBuffer object

        modifyBuff(buff);

        System.out.println(buff); // will print "foo"

        tryToNullifyBuff(buff); // this has no effect on the original reference 'buff'

        System.out.println(buff); // will still print "foo" because a copy of 
                                  // reference buff is passed to tryToNullifyBuff() 
                                  // which is made to reference null 
                                 // inside the method leaving the 'buff' reference intact
     }
}

Это можно сделать с другими изменяемыми объектами, такими как классы Collection, например.И это совсем не плохая практика, на самом деле некоторые дизайны активно используют этот шаблон.

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

Просто в ответ на некоторые комментарии о SCJP (извините, у вас нет прав оставлять комментарии - извинения, если оставить ответ - не правильный способ сделать это).

В защитуЭкзамен SCJP, передача ссылок на объекты как параметров метода И различия между неизменяемыми объектами String и объектами StringBuffer / StringBuilder являются частью экзамена - см. Разделы 3.1 и 7.3 здесь:

http://www.javadeveloper.co.in/scjp/scjp-exam-objectives.html

Обатемы достаточно широко освещены в учебном пособии Кэти Сьерра / Берт Бейтс к экзамену (которое фактически является официальным учебным пособием к экзамену).

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

Когда вы передаете объект методу, в отличие от C ++, копируется только ссылка на объект.Поэтому, когда изменяемый объект передается методу, вы можете изменить его, и это изменение будет отражено в вызывающем методе.Если вы слишком увлекаетесь программированием на C / C ++, вы должны знать, что в java передача по ссылке (в языке C ++) является стандартным (и единственным) способом передачи аргументов в методы.

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

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

public void toCommand(Stringbuffer buf) {
    buf.append("blablabla"); // okay
    buf = new Stringbuffer(); // no "effect" outside this method
}
0 голосов
/ 02 июля 2010

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

 StringBuilder emailBuilder = new StringBuilder();
 createHeader(emailBuilder);
 createBody(emailBuilder);
 createFooter(emailBuilder);
 sendEmail(emailBuilder.toString());

Это, безусловно, может быть использовано для создания путаницы, а для общедоступного API следует добавить одну или две заметки в javadoc, если значение в переданной ссылке изменилось.

Еще один яркий пример из Java API:

Collections.sort(list);

И как уже объяснили другие, и просто для завершения ответа: значение ссылки StringBuffer (<- вместо этого используйте StringBuilder!) Передается в <code>toCommand, поэтому снаружи и внутри метода toCommand вы получаете доступ тот же экземпляр StringBuffer.

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

str имеет значение "blablabla", поскольку ссылка на экземпляр StringBuilder передается toCommand().

Здесь создан только один экземпляр StringBuffer - и вы передаете ссылку на него методу toCommand(). Поэтому любые методы, вызываемые в StringBuffer в методе toCommand(), вызываются в том же экземпляре StringBuffer в вызывающем методе.

...