Я всегда думал, что Java была по ссылке .
Тем не менее, я видел пару постов в блоге (например, этот блог ), которые утверждают, что это не так.
Я не думаю, что понимаю разницу, которую они проводят.
Какое объяснение?
Java всегда передается по значению, без исключений, никогда .
Так почему же это может смущать любого, кто верит, что Java передается по ссылке, или думает, что у него есть пример того, как Java действует как передача по ссылке? Ключевым моментом является то, что Java никогда не обеспечивает прямого доступа к значениям самих объектов ни при каких обстоятельствах. Единственный доступ к объектам - через ссылку на этот объект. Поскольку доступ к объектам Java всегда осуществляется через ссылку, а не напрямую, обычно говорят о полях, переменных и аргументах методов как об объектах , когда педантично они являются только ссылками на объекты .Путаница проистекает из этого (строго говоря, неверного) изменения в номенклатуре.
Итак, при вызове метода
- Для аргументов примитива (
int
,long
и т. Д.) Передача по значению является фактическим значением примитива (например, 3). - Для объектов передача по значению является значением ссылки на объект .
Так что если у вас есть doSomething(foo)
и public void doSomething(Foo foo) { .. }
два Foos скопировали ссылки, которые указывают на одни и те же объекты.
Естественно, передача по значению ссылки на объект очень похожа на передачу объекта по ссылке (и практически не отличается от нее).
Java передает ссылки по значению.
Таким образом, вы не можете изменить ссылку, которая будет передана.
По сути, переназначение параметров объекта не влияет на аргумент, например,
private void foo(Object bar) {
bar = null;
}
public static void main(String[] args) {
String baz = "Hah!";
foo(baz);
System.out.println(baz);
}
распечатает "Hah!"
вместо null
. Причина, по которой это работает, заключается в том, что bar
это копия значения baz
, которое является просто ссылкой на "Hah!"
. Если бы это была сама фактическая ссылка, то foo
переопределил baz
бы null
.
Java передает ссылки на объекты по значению.
Java всегда передается по значению . К сожалению, когда мы передаем значение объекта, мы передаем ссылку на него. Это сбивает с толку новичков.
Это выглядит так:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
// we pass the object to foo
foo(aDog);
// aDog variable is still pointing to the "Max" dog when foo(...) returns
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// change d inside of foo() to point to a new Dog instance "Fifi"
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
В приведенном выше примере aDog.getName()
все равно вернется "Max"
. Значение в aDog
пределах main
не изменяется в функции foo
с Dog
"Fifi"
как ссылка на объект передается по значению. Если бы он был передан по ссылке, то после вызова функции aDog.getName()
in main
возвращается ."Fifi"
foo
Точно так же:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
foo(aDog);
// when foo(...) returns, the name of the dog has been changed to "Fifi"
aDog.getName().equals("Fifi"); // true
// but it is still the same dog:
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// this changes the name of d to be "Fifi"
d.setName("Fifi");
}
В приведенном выше примере Fifi
это имя собаки после вызова, foo(aDog)
потому что имя объекта было установлено внутри foo(...)
. Любые операции , которые foo
выполняют на d
таковы , что для всех практических целей, они выполняются на aDog
, но это не возможно изменить значение переменного aDog
сам.
Короче говоря, у объектов Java есть некоторые очень специфические свойства.
В общем, Java , имеет примитивные типы ( int
, bool
, char
, double
и т.д.), которые передаются непосредственно по значению. Тогда у Java есть объекты (все, что происходит от java.lang.Object
). Объекты на самом деле всегда обрабатываются с помощью ссылки (ссылка - это указатель, который вы не можете коснуться). Это означает, что, по сути, объекты передаются по ссылке, так как ссылки обычно не интересны. Это, однако, означает, что вы не можете изменить объект, на который указывает объект, так как сама ссылка передается по значению.
Это звучит странно и сбивает с толку? Давайте рассмотрим, как C реализует передачу по ссылке и передачу по значению. В С соглашением по умолчанию является передача по значению. void foo(int x)
передает int по значению. void foo(int *x)
это функция , которая не желает , чтобы int a
, но указатель на междунар: foo(&a)
. Можно использовать это с &
оператором для передачи адреса переменной.
Отнесите это на C ++, и у нас есть ссылки. Ссылки в основном (в этом контексте) являются синтаксическим сахаром, который скрывает указатель части уравнения: void foo(int &x)
вызывается foo(a)
, когда сам компилятор знает, что это ссылка, и a
должен быть передан адрес не ссылки . В Java все переменные, ссылающиеся на объекты, на самом деле относятся к ссылочному типу, фактически вызывая вызов по ссылке для большинства целей и задач без детального контроля (и сложности), предоставляемого, например, C ++.
Различие, или, может быть, просто то, что я помню, как у меня было такое же впечатление, как у оригинального плаката, заключается в следующем: Java всегда передается по значению. Все объекты (в Java, все, кроме примитивов) в Java являются ссылками. Эти ссылки передаются по значению.
Как уже упоминали многие люди, Java всегда передается по значению
Вот еще один пример, который поможет вам понять разницу ( классический пример обмена ):
public class Test {
public static void main(String[] args) {
Integer a = new Integer(2);
Integer b = new Integer(3);
System.out.println("Before: a = " + a + ", b = " + b);
swap(a,b);
System.out.println("After: a = " + a + ", b = " + b);
}
public static swap(Integer iA, Integer iB) {
Integer tmp = iA;
iA = iB;
iB = tmp;
}
}
Печать:
До: а = 2, б = 3
После: а = 2, б = 3
Это происходит потому, что iA и iB являются новыми локальными ссылочными переменными, которые имеют одинаковое значение переданных ссылок (они указывают на a и b соответственно). Таким образом, попытка изменить ссылки iA или iB изменится только в локальной области, а не вне этого метода.
Я всегда думаю об этом как о «проходе копией». Это копия значения, будь то примитив или ссылка. Если это примитив, то это копия битов, являющихся значением, а если это объект, то это копия ссылки.
public class PassByCopy{
public static void changeName(Dog d){
d.name = "Fido";
}
public static void main(String[] args){
Dog d = new Dog("Maxx");
System.out.println("name= "+ d.name);
changeName(d);
System.out.println("name= "+ d.name);
}
}
class Dog{
public String name;
public Dog(String s){
this.name = s;
}
}
вывод Java PassByCopy:
имя = макс
имя = фидо
Примитивные классы-обертки и строки неизменны, поэтому любой пример, использующий эти типы, не будет работать так же, как другие типы / объекты.
Я создал ветку, посвященную этим вопросам для любых языков программирования здесь .
Ява также упоминается . Вот краткое резюме:
- Java передает его параметры по значению
- «по значению» это единственный способ в Java передать параметр методу
- использование методов из объекта, заданного в качестве параметра, изменит объект, так как ссылки указывают на исходные объекты. (если этот метод сам изменяет некоторые значения)
Я только заметил, что вы ссылались на мою статью .
Спецификация Java говорит, что все в Java передается по значению. В Java нет такого понятия, как «передача по ссылке».
Ключом к пониманию этого является то, что
Dog myDog;
это не собака; это на самом деле указатель на собаку.
Что это значит, когда у вас есть
Dog myDog = new Dog("Rover");
foo(myDog);
вы по существу передаете адрес созданного Dog
объекта foo
методу.
(Я говорю по сути, потому что указатели Java не являются прямыми адресами, но проще всего так думать о них)
Предположим, что Dog
объект находится по адресу памяти 42. Это означает, что мы передаем 42 методу.
если метод был определен как
public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
давайте посмотрим на то, что происходит.
- для параметра
someDog
установлено значение 42 - на линии "ААА"
someDog
следует кDog
нему указывает (Dog
объект по адресу 42)- что
Dog
(тот, по адресу 42) попросили изменить его имя на Макс
- на линии "ВВВ"
- новый
Dog
создан. Допустим, он по адресу 74 - мы назначаем параметр
someDog
74
- новый
- на линии "CCC"
- SomeDog следует на
Dog
это указывает (Dog
объект по адресу 74) - что
Dog
(тот по адресу 74) попросили изменить его имя на Rowlf
- SomeDog следует на
- тогда мы вернемся
Теперь давайте подумаем о том, что происходит вне метода:
Изменился myDog
?
Там ключ.
Учитывая, что myDog
это указатель , а не факт Dog
, ответ НЕТ. myDog
по-прежнему имеет значение 42; он по-прежнему указывает на оригинал Dog
(но обратите внимание, что из-за строки «AAA» его имя теперь «Макс» - все тот же Dog; myDog
значение не изменилось.)
Совершенно верно следовать за адресом и менять то, что в конце; это не меняет переменную, однако.
Java работает точно так же, как C. Вы можете назначить указатель, передать указатель методу, следовать указателю в методе и изменить данные, на которые он указывал. Однако вы не можете изменить, куда указывает этот указатель.
В C ++, Ada, Pascal и других языках, которые поддерживают передачу по ссылке, вы действительно можете изменить переданную переменную.
Если бы у Java была семантика передачи по ссылке, то foo
метод, который мы определили выше, изменился бы там, куда myDog
указывал, когда он был назначен someDog
в строке BBB.
Представьте, что ссылочные параметры являются псевдонимами для передаваемой переменной. Когда этот псевдоним назначен, переменная также передается.
Чтобы показать контраст, сравните следующие фрагменты C ++ и Java :
В C ++: Примечание: плохой код - утечки памяти! Но это демонстрирует суть.
void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
val = 7; // Modifies the copy
ref = 7; // Modifies the original variable
obj.SetName("obj"); // Modifies the copy of Dog passed
objRef.SetName("objRef"); // Modifies the original Dog passed
objPtr->SetName("objPtr"); // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to
// by the original pointer passed.
objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}
int main()
{
int a = 0;
int b = 0;
Dog d0 = Dog("d0");
Dog d1 = Dog("d1");
Dog *d2 = new Dog("d2");
Dog *d3 = new Dog("d3");
cppMethod(a, b, d0, d1, d2, d3);
// a is still set to 0
// b is now set to 7
// d0 still have name "d0"
// d1 now has name "objRef"
// d2 now has name "objPtr"
// d3 now has name "newObjPtrRef"
}
В Java
public static void javaMethod(int val, Dog objPtr)
{
val = 7; // Modifies the copy
objPtr.SetName("objPtr") // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
}
public static void main()
{
int a = 0;
Dog d0 = new Dog("d0");
javaMethod(a, d0);
// a is still set to 0
// d0 now has name "objPtr"
}
Java имеет только два типа передачи: по значению для встроенных типов и по значению указателя для типов объектов.
Dog **objPtrPtr
к примеру C ++, чтобы мы могли изменить то, на что «указывает» указатель. Это немного сложно понять, но Java всегда копирует значение - суть в том, что обычно это ссылка. Поэтому вы в конечном итоге с тем же объектом, не думая об этом ...
Суть дела в том, что слово « ссылка» в выражении «передача по ссылке» означает нечто совершенно отличное от обычного значения слова « ссылка» в Java.
Обычно в Java ссылка означает ссылку на объект . Но технические термины, передаваемые по ссылке / значению из теории языка программирования, говорят о ссылке на ячейку памяти, содержащую переменную , что является чем-то совершенно другим.
Вы никогда не можете перейти по ссылке в Java, и один из очевидных способов - это когда вы хотите вернуть более одного значения из вызова метода. Рассмотрим следующий фрагмент кода на C ++:
void getValues(int& arg1, int& arg2) {
arg1 = 1;
arg2 = 2;
}
void caller() {
int x;
int y;
getValues(x, y);
cout << "Result: " << x << " " << y << endl;
}
Иногда вы хотите использовать тот же шаблон в Java, но не можете; по крайней мере, не напрямую. Вместо этого вы можете сделать что-то вроде этого:
void getValues(int[] arg1, int[] arg2) {
arg1[0] = 1;
arg2[0] = 2;
}
void caller() {
int[] x = new int[1];
int[] y = new int[1];
getValues(x, y);
System.out.println("Result: " + x[0] + " " + y[0]);
}
Как было объяснено в предыдущих ответах, в Java вы передаете указатель на массив как значение в getValues
. Этого достаточно, потому что метод затем модифицирует элемент массива, и по соглашению вы ожидаете, что элемент 0 будет содержать возвращаемое значение. Очевидно, что вы можете сделать это другими способами, такими как структурирование вашего кода, так что в этом нет необходимости, или создание класса, который может содержать возвращаемое значение или разрешить его установку. Но простой шаблон, доступный вам в C ++ выше, недоступен в Java.
Насколько я знаю, Java знает только вызов по значению. Это означает, что для примитивных типов данных вы будете работать с копией, а для объектов - с копией ссылки на объекты. Однако я думаю, что есть некоторые подводные камни; например, это не будет работать:
public static void swap(StringBuffer s1, StringBuffer s2) {
StringBuffer temp = s1;
s1 = s2;
s2 = temp;
}
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = new StringBuffer("World");
swap(s1, s2);
System.out.println(s1);
System.out.println(s2);
}
Это заполнит Hello World, а не World Hello, потому что в функции swap вы используете copys, которые не влияют на ссылки в основном. Но если ваши объекты не являются неизменяемыми, вы можете изменить это, например:
public static void appendWorld(StringBuffer s1) {
s1.append(" World");
}
public static void main(String[] args) {
StringBuffer s = new StringBuffer("Hello");
appendWorld(s);
System.out.println(s);
}
Это заполнит Hello World в командной строке. Если вы измените StringBuffer на String, он выдаст просто Hello, потому что String неизменен. Например:
public static void appendWorld(String s){
s = s+" World";
}
public static void main(String[] args) {
String s = new String("Hello");
appendWorld(s);
System.out.println(s);
}
Тем не менее, вы можете создать обёртку для String, такую, которая позволила бы использовать её со строками:
class StringWrapper {
public String value;
public StringWrapper(String value) {
this.value = value;
}
}
public static void appendWorld(StringWrapper s){
s.value = s.value +" World";
}
public static void main(String[] args) {
StringWrapper s = new StringWrapper("Hello");
appendWorld(s);
System.out.println(s.value);
}
редактировать: я считаю, что это также причина для использования StringBuffer, когда дело доходит до «добавления» двух строк, потому что вы можете изменить исходный объект, что вы не можете с неизменными объектами, такими как String.
swap(a, b)
которая (1) меняет местами a
и b
из POV вызывающей стороны, (2) не зависит от типа в той степени, в которой это позволяет статическая типизация (то есть использование ее с другим типом не требует ничего, кроме изменения объявленных типов a
и b
) и (3) не требует, чтобы вызывающая сторона явно передавала указатель или имя, тогда язык поддерживает передачу по ссылке. Несколько исправлений к некоторым постам.
C не поддерживает передачу по ссылке. ВСЕГДА передается по значению. C ++ поддерживает передачу по ссылке, но не используется по умолчанию и довольно опасен.
Неважно, какое значение имеет Java: примитив или адрес (грубо) объекта, оно ВСЕГДА передается по значению.
Если объект Java «ведет себя» так, как его передают по ссылке, это свойство изменчивости и не имеет абсолютно никакого отношения к механизмам передачи.
Я не уверен, почему это так сбивает с толку, возможно, потому, что так много «программистов» на Java не обучены формально и поэтому не понимают, что на самом деле происходит в памяти?
Java копирует ссылку по значению. Таким образом, если вы измените его на что-то другое (например, используя new
), ссылка не изменится вне метода. Для нативных типов оно всегда передается по значению.
Посмотрите на этот код. Этот код не скинет NullPointerException
... Он напечатает "Vinay"
public class Main {
public static void main(String[] args) {
String temp = "Vinay";
print(temp);
System.err.println(temp);
}
private static void print(String temp) {
temp = null;
}
}
Если Java передается по ссылке, то она должна быть выброшена, NullPointerException
поскольку для ссылки задано значение Null.
Я не могу поверить, что никто еще не упомянул Барбару Лисков. Когда она разработала CLU в 1974 году, она столкнулась с той же проблемой терминологии, и она изобрела термин вызов путем разделения (также известный как вызов посредством совместного использования объекта и вызов по объекту ) для этого конкретного случая «вызов по значению, где значение равно ссылка".
По моему мнению, «передача по значению» - ужасный способ описать два одинаковых, но разных события. Я думаю, они должны были спросить меня сначала.
С помощью примитивов мы передаем фактическое значение примитива в метод (или конструктор), будь то целое число «5», символ «c» или что у вас есть. Это действительное значение становится его собственным локальным примитивом. Но с объектами все, что мы делаем, это даем тому же объекту дополнительную ссылку (локальную ссылку), так что теперь у нас есть две ссылки, указывающие на один и тот же объект.
Надеюсь, это простое объяснение поможет.
Это даст вам некоторое представление о том, как на самом деле работает Java, до такой степени, что в вашем следующем обсуждении передачи Java по ссылке или по значению вы просто улыбнетесь :-)
Шаг первый, пожалуйста, удалите из памяти это слово, которое начинается с 'p' "_ _ _ _ _ _ _", особенно если вы пришли из других языков программирования. Java и 'p' не могут быть записаны в одной книге, на форуме или даже в txt.
Шаг второй Помните, что когда вы передаете объект в метод, вы передаете ссылку на объект, а не сам объект.
- Ученик : Магистр, означает ли это, что Java передается по ссылке?
- Мастер : Кузнечик, №
Теперь подумайте, что делает / является ссылкой / переменной объекта:
- Переменная содержит биты, которые сообщают JVM, как добраться до указанного объекта в памяти (Heap).
- При передаче аргументов методу вы НЕ передаете ссылочную переменную, а копируете биты в ссылочной переменной . Как то так: 3bad086a. 3bad086a представляет способ добраться до пропущенного объекта.
- Итак, вы просто передаете 3bad086a, что это значение ссылки.
- Вы передаете значение ссылки, а не саму ссылку (и не объект).
- Это значение фактически копируется и передается методу .
В следующем (пожалуйста, не пытайтесь скомпилировать / выполнить это ...):
1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7. anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }
Что происходит?
- Переменная person создается в строке # 1 и в начале имеет значение null.
- Новый объект Person создается в строке # 2, сохраняется в памяти, а переменной person дается ссылка на объект Person. То есть его адрес. Допустим, 3bad086a.
- Переменная person, содержащая адрес объекта, передается функции в строке # 3.
- В строке № 4 вы можете прослушать звук тишины
- Проверьте комментарий в строке № 5
- Локальная переменная метода - anotherReferenceToTheSamePersonObject - создается, а затем появляется волшебство в строке # 6:
- Переменная / эталонное лицо копируется побитно и передается в anotherReferenceToTheSamePersonObject внутри функции.
- Новые экземпляры Person не создаются.
- И " person ", и " anotherReferenceToTheSamePersonObject " содержат одно и то же значение 3bad086a.
- Не пытайтесь это сделать, но person == anotherReferenceToTheSamePersonObject будет истинным.
- Обе переменные имеют ИДЕНТИЧНЫЕ КОПИИ ссылки, и они обе ссылаются на один и тот же объект Person, ОДИН ЖЕ объект в куче и НЕ КОПИЮ.
Одна картинка стоит тысячи слов:
Обратите внимание, что стрелки anotherReferenceToTheSamePersonObject направлены к объекту, а не к переменной person!
Если вы не получили его, просто поверьте мне и помните, что лучше сказать, что Java передается по значению . Ну, перейдите по ссылке . Ну что ж, еще лучше передать значение переменной! ;)
Теперь не стесняйтесь ненавидеть меня, но обратите внимание, что, учитывая это, нет разницы между передачей примитивных типов данных и объектов при обсуждении аргументов метода.
Вы всегда передаете копию битов значения ссылки!
- Если это примитивный тип данных, эти биты будут содержать значение самого примитивного типа данных.
- Если это объект, биты будут содержать значение адреса, который сообщает JVM, как добраться до объекта.
Java передается по значению, потому что внутри метода вы можете изменять ссылочный объект столько раз, сколько захотите, но как бы вы ни старались, вы никогда не сможете изменить переданную переменную, которая будет продолжать ссылаться (не p _ _ _) _ _ _ _) один и тот же объект, несмотря ни на что!
Приведенная выше функция changeName никогда не сможет изменить фактическое содержимое (значения битов) переданной ссылки. Другими словами, changeName не может заставить человека обратиться к другому объекту.
Конечно, вы можете сократить его и просто сказать, что Java передается по значению!
public void foo(Car car){ ... }
, car
является локальной foo
и содержит расположение кучи Объекта? Так что, если я изменю car
значение на car = new Car()
, оно будет указывать на другой объект в куче? и если я изменю значение car
свойства на car.Color = "Red"
, Объект в указанной куче car
будет изменен. Кроме того, то же самое в C #? Ответьте, пожалуйста! Спасибо! System.out.println(person.getName());
что появится? "Том" или "Джерри"? Это последнее, что поможет мне преодолеть эту путаницу. Все передается по значению. Примитивы и ссылки на объекты. Но объекты могут быть изменены, если их интерфейс позволяет это.
Когда вы передаете объект методу, вы передаете ссылку, и объект может быть изменен реализацией метода.
void bithday(Person p) {
p.age++;
}
Ссылка на сам объект передается по значению: вы можете переназначить параметр, но изменение не отражается обратно:
void renameToJon(Person p) {
p = new Person("Jon"); // this will not work
}
jack = new Person("Jack");
renameToJon(jack);
sysout(jack); // jack is unchanged
По сути, «р» является ссылкой (указатель на объект) и не может быть изменен.
Примитивные типы передаются по значению. Ссылка на объект также может считаться примитивным типом.
Напомним, что все передается по значению.
Это действительно довольно просто:
Для переменной примитивного типа (например. int
, boolean
, char
И т.д ...), когда вы используете его имя для аргумента метода, вы передаете значение содержатся в нем ( 5
, true
или 'c'
). Это значение копируется, и переменная сохраняет свое значение даже после вызова метода.
Для переменной ссылочного типа (например. String
, Object
И т.д ...), когда вы используете его имя для аргумента метода, вы передаете значение , содержащиеся в нем ( на эталонное значение , что «точки» к объекту ). Это ссылочное значение копируется, а переменная сохраняет свое значение даже после вызова метода. Ссылочная переменная продолжает указывать на один и тот же объект.
В любом случае, вы всегда передаете вещи по значению.
Сравните это с C ++, где у вас может быть метод для взятия int&
, или в C #, где вы могли бы взять с собой ref int
(хотя, в этом случае, вы также должны использовать ref
модификатор при передаче имени переменной методу.)
String
S неизменны в Java. Foo
на «Автомобиль # 1234», а также копии Foo
с Bar
, тогда как Foo
и Bar
будет держать «Автомобиль # 1234». Поговорка Foo.SetColor(Colors.Blue)
покрасит «Автомобиль № 1234» в синий цвет. Другое описание, которое я использую для таких мест хранения, - это «разнородные ссылки на объекты», поскольку код, который передает такую ссылку на объект, не может контролировать, как получатель может поделиться ею. Java всегда передает аргументы по значению , а не по ссылке.
Позвольте мне объяснить это на примере :
public class Main {
public static void main(String[] args) {
Foo f = new Foo("f");
changeReference(f); // It won't change the reference!
modifyReference(f); // It will modify the object that the reference variable "f" refers to!
}
public static void changeReference(Foo a) {
Foo b = new Foo("b");
a = b;
}
public static void modifyReference(Foo c) {
c.setAttribute("c");
}
}
Я объясню это по шагам:
Объявить ссылку с именем
f
типаFoo
и назначить ей новый объект типаFoo
с атрибутом"f"
.Foo f = new Foo("f");
Со стороны метода объявляется ссылка на тип
Foo
с именем,a
и она изначально присваиваетсяnull
.public static void changeReference(Foo a)
Когда вы вызываете метод
changeReference
, ссылкеa
будет присвоен объект, который передается в качестве аргумента.changeReference(f);
Объявить ссылку с именем
b
типаFoo
и назначить ей новый объект типаFoo
с атрибутом"b"
.Foo b = new Foo("b");
a = b
создает новое назначение для ссылкиa
, а неf
для объекта, чьим атрибутом является"b"
.При вызове
modifyReference(Foo c)
метода создается ссылка,c
которой присваивается объект с атрибутом"f"
.c.setAttribute("c");
изменит атрибут объекта, на которыйc
указывает ссылка , и тот же объект, на которыйf
указывает ссылка .
Надеюсь, теперь вы понимаете, как передача объектов в качестве аргументов работает в Java :)
a
указывает на тот же объект, что и f
(и никогда не получает свою собственную копию объекта, на который f
указывает), любые изменения объекта, сделанные с помощью, a
должны измениться так же f
хорошо (поскольку они оба работают с одним и тем же объектом ), поэтому в какой-то момент a
должна получить собственную копию объекта, на которую f
указывает. Нет, это не передача по ссылке.
Java передается по значению в соответствии со спецификацией языка Java:
Когда метод или конструктор вызывается (§15.12), значения фактических выражений аргумента инициализируют вновь созданные переменные параметра , каждый из объявленного типа, перед выполнением тела метода или конструктора. Идентификатор, который появляется в DeclaratorId, может использоваться как простое имя в теле метода или конструктора для ссылки на формальный параметр .
Java передается по постоянной ссылке, где передается копия ссылки, что означает, что это в основном передача по значению. Вы можете изменить содержимое ссылки, если класс изменчив, но вы не можете изменить саму ссылку. Другими словами, адрес не может быть изменен, поскольку он передается по значению, но содержимое, на которое указывает адрес, может быть изменено. В случае неизменяемых классов содержание ссылки также не может быть изменено.
final.
Java всегда передается по значению, параметры являются копиями передаваемых переменных, все объекты определяются с помощью ссылки, а ссылка - это переменная, которая хранит адрес памяти, где находится объект в памяти.
Проверьте комментарии, чтобы понять, что происходит при исполнении; следуйте за числами, поскольку они показывают поток выполнения ..
class Example
{
public static void test (Cat ref)
{
// 3 - <ref> is a copy of the reference <a>
// both currently reference Grumpy
System.out.println(ref.getName());
// 4 - now <ref> references a new <Cat> object named "Nyan"
ref = new Cat("Nyan");
// 5 - this should print "Nyan"
System.out.println( ref.getName() );
}
public static void main (String [] args)
{
// 1 - a is a <Cat> reference that references a Cat object in memory with name "Grumpy"
Cat a = new Cat("Grumpy");
// 2 - call to function test which takes a <Cat> reference
test (a);
// 6 - function call ends, and <ref> life-time ends
// "Nyan" object has no references and the Garbage
// Collector will remove it from memory when invoked
// 7 - this should print "Grumpy"
System.out.println(a.getName());
}
}
В попытке добавить еще больше к этому, я подумал, что включу раздел «Учебное пособие SCJP» по этой теме. Это из руководства, предназначенного для прохождения теста Sun / Oracle на поведение Java, поэтому это хороший источник для использования в этом обсуждении.
Передача переменных в методы (цель 7.3)
7.3. Определите влияние на ссылки на объекты и значения примитивов, когда они передаются в методы, которые выполняют присваивания или другие модифицирующие операции над параметрами.
Методы могут быть объявлены для получения примитивов и / или ссылок на объекты. Вы должны знать, как (или если) переменная вызывающего может быть затронута вызываемым методом. Разница между ссылкой на объект и примитивными переменными при передаче в методы огромна и важна. Чтобы понять этот раздел, вам нужно освоить раздел заданий, описанный в первой части этой главы.
Передача переменных ссылки на объект
Когда вы передаете переменную объекта в метод, вы должны иметь в виду, что вы передаете ссылку на объект, а не на сам объект. Помните, что ссылочная переменная содержит биты, которые представляют (для базовой ВМ) способ добраться до определенного объекта в памяти (в куче). Что еще более важно, вы должны помнить, что вы даже не передаете фактическую ссылочную переменную, а скорее копию ссылочной переменной. Копия переменной означает, что вы получаете копию битов в этой переменной, поэтому, когда вы передаете ссылочную переменную, вы передаете копию битов, представляющих, как добраться до определенного объекта. Другими словами, и вызывающая сторона, и вызываемый метод теперь будут иметь идентичные копии ссылки, и, таким образом, оба будут ссылаться на один и тот же точный (а не копию) объект в куче.
Для этого примера мы будем использовать класс Dimension из пакета java.awt:
1. import java.awt.Dimension;
2. class ReferenceTest {
3. public static void main (String [] args) {
4. Dimension d = new Dimension(5,10);
5. ReferenceTest rt = new ReferenceTest();
6. System.out.println("Before modify() d.height = " + d.height);
7. rt.modify(d);
8. System.out.println("After modify() d.height = "
9. }
10.
11.
12.
13. }
14. }
Когда мы запустим этот класс, мы увидим, что метод modify () действительно смог изменить исходный (и единственный) объект Dimension, созданный в строке 4.
C: \ Java Projects \ Reference> java ReferenceTest Перед изменением () d.height = 10 тусклый = 11 После изменения () d.height = 11
Обратите внимание, что когда объект Dimension в строке 4 передается методу modify (), любые изменения в объекте, происходящие внутри метода, вносятся в объект, ссылка на который была передана. В предыдущем примере ссылочные переменные d и dim указывают на один и тот же объект.
Использует ли Java семантику передачи по значению?
Если Java передает объекты путем передачи ссылочной переменной, означает ли это, что Java использует передачу по ссылке для объектов? Не совсем, хотя вы часто будете слышать и читать, что это так. Java фактически передается по значению для всех переменных, работающих в одной виртуальной машине. Передача по значению означает передачу по переменной. А это значит, передача-копия-переменной! (Это слово снова скопировано!)
Не имеет значения, передаете ли вы примитивные или ссылочные переменные, вы всегда передаете копию битов в переменной. Таким образом, для простой переменной вы передаете копию битов, представляющих значение. Например, если вы передаете переменную int со значением 3, вы передаете копию битов, представляющих 3. Затем вызываемый метод получает свою собственную копию значения, чтобы делать с ним то, что ему нравится.
И если вы передаете переменную ссылки на объект, вы передаете копию битов, представляющих ссылку на объект. Затем вызываемый метод получает свою собственную копию ссылочной переменной, чтобы делать с ней то, что ему нравится. Но поскольку две идентичные ссылочные переменные ссылаются на один и тот же объект, если вызываемый метод изменяет объект (например, путем вызова методов установки), вызывающая сторона увидит, что объект, на который ссылается исходная переменная вызывающей стороны, также изменился. В следующем разделе мы рассмотрим, как меняется картина, когда мы говорим о примитивах.
Суть в передаче по значению: вызываемый метод не может изменить переменную вызывающего, хотя для ссылочных переменных объекта вызываемый метод может изменить объект, на который ссылается переменная. Какая разница между изменением переменной и изменением объекта? Для ссылок на объекты это означает, что вызываемый метод не может переназначить исходную ссылочную переменную вызывающей стороны и заставить ее ссылаться на другой объект или значение 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 ссылаться на другой объект!
Передача примитивных переменных
Давайте посмотрим, что происходит, когда примитивная переменная передается методу:
class ReferenceTest {
public static void main (String [] args) {
int a = 1;
ReferenceTest rt = new ReferenceTest();
System.out.println("Before modify() a = " + a);
rt.modify(a);
System.out.println("After modify() a = " + a);
}
void modify(int number) {
number = number + 1;
System.out.println("number = " + number);
}
}
В этой простой программе переменная a передается методу modify (), который увеличивает значение переменной на 1. Полученный результат выглядит следующим образом:
Before modify() a = 1
number = 2
After modify() a = 1
Обратите внимание, что a не изменилось после того, как оно было передано методу. Помните, это была копия, которая была передана методу. Когда примитивная переменная передается методу, она передается по значению, что означает «передача-копирование-бит-в-переменной».
У Java есть только передача по значению. Очень простой пример, чтобы подтвердить это.
public void test() {
MyClass obj = null;
init(obj);
//After calling init method, obj still points to null
//this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
objVar = new MyClass();
}
obj
( null
) было передано init
, а не ссылка на obj
.
call-by-value
иcall-by-reference
отличается очень важным образом . (Лично я предпочитаю использоватьcall-by-object-sharing
эти дни болееcall-by-value[-of-the-reference]
, так как это описывает семантику на высоком уровне и не создает конфликтаcall-by-value
, который является базовой реализацией.)swap(x,y)
тест .