Почему изменяется параметр ArrayList, а не параметр String?
public class StackOverFlow {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("A");
al.add("B");
markAsNull(al);
System.out.println("ArrayList elements are "+al);
String str = "Hello";
markStringAsNull(str);
System.out.println("str "+ str);
}
private static void markAsNull(ArrayList<String> str){
str.add("C");
str= null;
}
private static void markStringAsNull(String str){
str = str + "Append me";
str = null;
}
}
Это выводит:
ArrayList elements are [A, B, C]
str Hello
В случае ArrayList
добавленные элементы извлекаются. В случае String
вызов метода не влияет на передаваемую строку. Что именно делает JVM? Кто-нибудь может объяснить подробно?
5 ответов
В случае строковых объектов Arraylist добавленные элементы получают повторно. В случае String вызов метода не влияет на передаваемую строку.
Это происходит потому, что Java передается по значению и String
с неизменны
Когда вы звоните
markAsNull(ArrayList<String> str)
Новая ссылка по имени str
создан для того же ArrayList
указал al
, Когда ты add
элемент на str
он добавляется к тому же объекту. Позже вы положили str
в null
но объект имеет новые добавленные значения и указывается a1
,
Когда вы звоните
markStringAsNull(String str)
{
str = str + "Append me";
// ...
}
Линия str = str + "Append me";
создает новый String
объект, добавив данную строку и назначает ее str
, но опять же, это просто ссылка на фактическую строку, которая теперь указывает на вновь созданную строку. (из-за неизменности) и исходная строка не изменяется.
markXAsNull
методы устанавливают локальные ссылки на null
, Это не влияет на фактическое значение, хранящееся в этом месте. main
метод по-прежнему имеет свои собственные ссылки на значения и может вызывать println
используя те.
Кроме того, при выполнении конкатенации строк, toString()
вызывается для объекта, и именно поэтому ArrayList выводится в виде списка его значений в скобках.
Из книги: SCJP - Сертифицированный Sun программист для Java 6 Учебное пособие (Катти Сьерра - Берт Бейтс) Глава 3 Задача 7.3 - Передача переменных в методы
Java фактически передается по значению для всех переменных, работающих в одной виртуальной машине. Передача по значению означает передачу по переменной. И это означает, что переменная передается при копировании!
Суть в передаче по значению: вызываемый метод не может изменить переменную вызывающего, хотя для ссылочных переменных объекта вызываемый метод может изменить объект, на который ссылается переменная. Какая разница между изменением переменной и изменением объекта? Для ссылок на объекты это означает, что вызываемый метод не может переназначить исходную ссылочную переменную вызывающей стороны и заставить ее ссылаться на другой объект или значение NULL. Например, в следующем фрагменте кода
void bar() {
Foo f = new Foo();
doStuff(f);
}
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
}
переназначение g не переназначает f! В конце метода bar() были созданы два объекта Foo, на один ссылается локальная переменная f, а на другой локальная переменная (аргумент) g. Поскольку метод doStuff() имеет копию ссылочной переменной, он может получить доступ к исходному объекту Foo, например, вызвать метод setName(). Но метод doStuff() не может получить доступ к ссылочной переменной f. Таким образом, doStuff() может изменять значения внутри объекта, на который ссылается f, но doStuff() не может изменять фактическое содержимое (битовый шаблон) для f. Другими словами, doStuff() может изменить состояние объекта, на который ссылается f, но не может заставить f ссылаться на другой объект!
Java следует концепции проходного значения (в Java нет передачи по ссылке) . Поэтому, когда вы передаете строку в функцию, она отправляет "копию ссылки" этой строке в функцию. Таким образом, даже если вы установите для переменной значение null в функции, когда она возвращается к вызывающей стороне, она ссылается только на свое первоначальное значение. Вот почему оригинальная строка не имеет никакого эффекта.
В случае Arraylist, копия ссылки относится к оригинальному Arraylist (что также относится и к строке) . Но когда вы добавляете что-то в ArrayList, это относится к исходному объекту и, таким образом, вы можете увидеть эффект. (попробуйте в методе arraylist=new ArrayList(), ваш оригинальный arraylist останется без изменений) .
В случае строки, когда вы делаете
str= str + "abc";
Java создает новый объект String, который будет иметь ссылку на строку "xyzabc" (например, str= "xyz"), и "xyz" будет иметь право на сборку мусора. Но поскольку у "xyz" все еще есть переменная, которая ссылается на него, она (исходная строка) не будет собираться мусором. Но как только вызов функции заканчивается, xyzabc отправляется на сборку мусора.
Суть обсуждения в том, что если ссылка ссылается на тот же объект, вы можете вносить изменения в функцию, но когда вы пытаетесь изменить ссылку (str= str + "abc"), вы ссылаетесь на новый объект в методе. так что ваш оригинальный объект останется как есть.
В Java вы можете создать один объект, на который ссылаются несколько указателей. Вызов метода мутатора для любого указателя эффективно изменит единственный объект, таким образом, обновляя все остальные ссылки.
Но если вы вызываете операторы присваивания переменных для ссылки, будет изменен только этот указатель, поскольку он не выполняет какой-либо работы на стороне объекта (это лучшее, что я мог бы объяснить...).
Передача объекта в параметр будет эффективно копировать ссылку, в результате чего получится один объект с двумя указателями - один глобальный, а другой локальный.
Еще один момент, так как String
является неизменным, вы на самом деле получите новый объект, который отличается от оригинала (от того, что вы должны сказать, a = a + "a"
), поэтому он не будет изменять исходную строку.