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()
в 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 Spec сказано, что все в Java является передачей по значению. В Java нет такой вещи, как "pass-by-reference".
Ключом к пониманию этого является то, что что-то вроде
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
установлен в значение 42someDog
применяется к Dog
, который указывает (объект Dog
по адресу 42)Dog
(тот, на адрес 42) попросил изменить его имя на MaxDog
. Скажем, он по адресу 74someDog
равным 74Dog
, который указывает (объект Dog
по адресу 74)Dog
(тот, на адрес 74), попросил изменить его имя на RowlfТеперь подумайте о том, что происходит вне метода:
Изменил ли myDog
?
Вот ключ.
Имея в виду, что myDog
является указателем, а не фактическим Dog
, ответ НЕТ. myDog
все еще имеет значение 42; он по-прежнему указывает на оригинал Dog
(но обратите внимание, что из-за строки "AAA" его имя теперь "Макс" - все тот же символ Dog; myDog
не изменился.)
Совершенно верно, чтобы следить за адресом и изменять то, что в конце его; который не изменяет переменную, однако.
Java работает точно так же, как C. Вы можете назначить указатель, передать указатель методу, следовать указателю в методе и изменить данные, на которые указали. Тем не менее, вы не можете изменить, куда указывает этот указатель.
В С++, Ada, Pascal и других языках, поддерживающих pass-by-reference, вы действительно можете изменить переданную переменную.
Если Java имеет семантику сквозной ссылки, метод foo
, который мы определили выше, изменился бы там, где myDog
указывал, когда он назначал someDog
в строке BBB.
Подумайте о ссылочных параметрах как псевдонимах для переданной переменной. Когда этот псевдоним назначается, так же как и переменная, которая была передана.
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
Aswell (так как они оба работать с тем же объектом), поэтому в какой-то момент a
должен получить свою собственную копию объекта, на который указывает f
.
Это даст вам представление о том, как Java действительно работает до такой степени, что в следующем обсуждении о передаче Java по ссылке или передаче по значению вы просто будете улыбаться: -)
Шаг один, пожалуйста, удалите из своего разума это слово, которое начинается с "p" "_ _ _ _ _ _ _", особенно если вы исходите из других языков программирования. Java и "p" не могут быть записаны в одной книге, форуме или даже в txt.
Шаг 2 помните, что при передаче объекта в метод вы передаете ссылку Object, а не сам объект.
Теперь подумайте о том, какая ссылка/переменная Object имеет /:
В следующем (пожалуйста, не пытайтесь скомпилировать/выполнить это...):
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. }
Что происходит?
Изображение стоит тысячи слов:
Обратите внимание, что стрелки anotherReferenceToTheSamePersonObject направлены к объекту, а не к переменной!
Если вы этого не поняли, просто доверьтесь мне и помните, что лучше сказать, что Java передается по значению. Ну, передайте ссылочное значение. О, хорошо, еще лучше pass-by-copy-of-the-variable-value!;)
Теперь не стесняйтесь ненавидеть меня, но обратите внимание, что при использовании этого нет разницы между передачей примитивных типов данных и объектов при обсуждении аргументов метода.
Вы всегда передаете копию бит значения ссылки!
Java - это пропускная способность, потому что внутри метода вы можете изменять ссылочный объект столько, сколько хотите, но как бы вы ни старались, вы никогда не сможете изменить переданную переменную, которая будет продолжать ссылаться (не p _ _ _ _ _ _ _) тот же объект независимо от того, что!
Вышеуказанная функция changeName никогда не сможет изменить фактическое содержимое (битовые значения) переданной ссылки. В другом слове changeName не может заставить Person лицо ссылаться на другой объект.
Конечно, вы можете сократить его и просто сказать, что Java - это пропускная способность!
public void foo(Car car){ ... }
, car
является локальным по отношению к foo
и содержит местоположение кучи объекта? Так что, если я меняю car
значение «s на car = new Car()
, он будет указывать на другой объект в куче? и если я изменю значение свойства car
на car.Color = "Red"
, объект в куче, указанной car
будет изменен. Кроме того, то же самое в C #? Ответьте, пожалуйста! Спасибо!
Java всегда проходит по значению, без исключений, когда-либо.
Итак, как же все это можно смутить, и считают, что Java проходит по ссылке, или думает, что у них есть пример Java, действующий как pass by reference? Ключевым моментом является то, что Java никогда обеспечивает прямой доступ к значениям самих объектов при любых обстоятельствах. Единственный доступ к объектам - это ссылка на этот объект. Поскольку объекты Java всегда обращаются через ссылку, а не напрямую, обычно говорят о том, что поля, переменные и аргументы метода являются объектами, когда педантично они являются только ссылками на объекты. Путаница проистекает из этого (строго говоря, неправильного) изменения в номенклатуре.
Итак, при вызове метода
int
, long
и т.д.) пропуск по значению является фактическим значением примитива (например, 3).Итак, если у вас есть doSomething(foo)
и public void doSomething(Foo foo) { .. }
, два Foos скопировали ссылки, которые указывают на одни и те же объекты.
Естественно, что передача по значению ссылки на объект очень похожа на (и на практике неотличима от), передавая объект по ссылке.
Java передает ссылки по значению.
Таким образом, вы не можете изменить ссылку, которая будет передана.
Мне кажется, что спорить о "pass-by-reference vs pass-by-value" не очень полезно.
Если вы скажете: "Java - это pass-by-whatever (reference/value)", в любом случае вы не предоставляете полный ответ. Вот некоторая дополнительная информация, которая, надеюсь, поможет понять, что происходит в памяти.
Курс Crash в стеке/куче прежде чем мы перейдем к реализации Java: Ценности идут в стопку и складываются аккуратно, как стопка тарелок в столовой. Память в куче (также известная как динамическая память) беспорядочна и дезорганизована. JVM просто находит место, где только может, и освобождает его, поскольку переменные, которые его используют, больше не нужны.
Хорошо. Во-первых, локальные примитивы идут в стек. Итак, этот код:
int x = 3;
float y = 101.1f;
boolean amIAwesome = true;
приводит к следующему:
Когда вы объявляете и создаете экземпляр объекта. Фактический объект переходит в кучу. Что происходит в стеке? Адрес объекта в куче. Программисты на C++ назвали бы это указателем, но некоторые разработчики Java против слова "указатель". Без разницы. Просто знайте, что адрес объекта идет в стек.
Так же:
int problems = 99;
String name = "Jay-Z";
Массив - это объект, поэтому он также находится в куче. А как насчет объектов в массиве? Они получают свое собственное пространство кучи, и адрес каждого объекта входит в массив.
JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");
Итак, что передается, когда вы вызываете метод? Если вы передаете объект, то, что вы фактически передаете, является адресом объекта. Некоторые могут сказать "значение" адреса, а некоторые говорят, что это просто ссылка на объект. Это генезис священной войны между "ссылочными" и "ценностными" сторонниками. То, что вы называете, не так важно, как вы понимаете, что то, что передается, - это адрес объекта.
private static void shout(String name){
System.out.println("There goes " + name + "!");
}
public static void main(String[] args){
String hisName = "John J. Jingleheimerschmitz";
String myName = hisName;
shout(myName);
}
Создается одна строка, а пространство для нее выделяется в куче, а адрес строки сохраняется в стеке и присваивается идентификатор hisName
, так как адрес второй строки такой же, как и первый, не создается новая String и не выделяется новое место кучи, но в стек создается новый идентификатор. Затем мы вызываем shout()
: создается новый стек стека и создается новый идентификатор name
и назначается адрес уже существующей строки.
Итак, значение, ссылка? Вы говорите "картофель".
Чтобы показать контраст, сравните следующие C++ и Java фрагменты:
В С++: Примечание. Плохой код - утечка памяти! Но это демонстрирует точку.
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 передает ссылки на объекты по значению.
В принципе, переназначение параметров объекта не влияет на аргумент, например,
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
.
Не могу поверить, что никто еще не упомянул Барбару Лисков. Когда она разработала CLU в 1974 году, она столкнулась с этой же проблемой терминологии, и она придумала термин "вызов" путем совместного использования (также называемого вызовом путем совместного использования объектов и вызова по объекту) для этого конкретного случая "вызов по значению, где значение ссылку".
Суть в том, что ссылка слова в выражении "пройти по ссылке" означает нечто совершенно отличное от обычного значения ссылки на слова в Java.
Обычно в Java ссылка означает ссылку на объект. Но технические термины, передаваемые по ссылке/значению из теории языка программирования, говорят о ссылке на ячейку памяти, в которой находится переменная, что совершенно другое.
В java все ссылается, поэтому, когда у вас есть что-то вроде: Point pnt1 = new Point(0,0);
Java выполняет следующие действия:
Java не передает аргументы метода по ссылке; он передает их по значению. Я буду использовать пример из этого сайта:
public static void tricky(Point arg1, Point arg2) {
arg1.x = 100;
arg1.y = 100;
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
}
public static void main(String [] args) {
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y);
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y);
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
}
Поток программы:
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
Создание двух разных объектов Point с двумя связанными ссылками.
System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y);
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");
Как ожидалось, выход будет:
X1: 0 Y1: 0
X2: 0 Y2: 0
В этой строке "pass-by-value" переходит в игру...
tricky(pnt1,pnt2); public void tricky(Point arg1, Point arg2);
Ссылки pnt1
и pnt2
являются переданы значением для сложного метода, что означает, что теперь ваши ссылки pnt1
и pnt2
имеют свои copies
с именем arg1
и arg2
.So pnt1
и arg1
указывает на тот же объект. (То же самое для pnt2
и arg2
)
В методе tricky
:
arg1.x = 100;
arg1.y = 100;
Далее в методе tricky
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
Здесь вы сначала создаете новую ссылку temp
Point, которая укажет на то же место, что и ссылка arg1
. Затем вы перемещаете ссылку arg1
, чтобы указать на то же место, что и ссылка arg2
.
Наконец arg2
будет указывать на то же место, что и temp
.
Отсюда область применения метода tricky
исчезла, и вы больше не имеете доступа к ссылкам: arg1
, arg2
, temp
. Но важно отметить, что все, что вы делаете с этими ссылками, когда они находятся в жизни, будет постоянно влиять на объект, на который они указывают.
Итак, после выполнения метода tricky
, когда вы вернетесь в main
, у вас возникнет такая ситуация:
Итак, теперь полное выполнение программы будет:
X1: 0 Y1: 0
X2: 0 Y2: 0
X1: 100 Y1: 100
X2: 0 Y2: 0
arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;
Итак, поскольку arg1 теперь содержит ссылку на pnt2, а arg2 держит теперь ссылку на pnt1, значит, его печать X1: 2 Y1: 2 X2: 1 Y2: 1
Получив внешний вид окна, просмотрите сборку или некоторое управление памятью низкого уровня. На уровне ЦП ссылка на что-либо сразу становится значением, если она записывается в память или в один из регистров CPU. (Вот почему указатель является хорошим определением. Это значение, которое имеет цель в то же время).
Данные в памяти имеют Местоположение, и в этом месте есть значение (байт, слово, что угодно). В Assembly мы имеем удобное решение для предоставления Name для определенного Location (aka variable), но при компиляции кода ассемблер просто заменяет Name > с назначенным местоположением так же, как ваш браузер заменяет имена доменов IP-адресами.
В основе ядра технически невозможно передать ссылку на что-либо на любом языке, не представляя его (когда оно сразу становится значением).
Предположим, что у нас есть переменная Foo, ее Местоположение находится в 47-м байте в памяти, а ее Значение равно 5. У нас есть другая переменная Ref2Foo strong > , который находится в памяти 223 байта, а его значение будет 47. Этот Ref2Foo может быть технической переменной, явно не созданной программой. Если вы просто посмотрите на 5 и 47 без какой-либо другой информации, вы увидите только две Значения.
Если вы используете их в качестве ссылок, то для достижения 5
нам нужно путешествовать:
(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223] -> 47
(Foo)[47] -> 5
Вот как работают прыжки-таблицы.
Если мы хотим вызвать метод/функцию/процедуру с помощью значения Foo, существует несколько возможных способов передать переменную методу в зависимости от режима language и нескольких способов его вызова:
В каждом случае выше значение - копия существующего значения - было создано, теперь до метода приема обрабатывается. Когда вы пишете "Foo" внутри метода, он либо считывается из EAX, либо автоматически разыменовывается или дважды разыменован, процесс зависит от того, как работает язык и/или какой тип Foo диктует, Это скрыто от разработчика, пока она не оборвет процесс разыменования. Таким образом, ссылка представляет собой значение, когда оно представлено, потому что ссылка - это значение, которое необходимо обработать (на уровне языка).
Теперь мы прошли Foo к методу:
Foo = 9
), это влияет только на локальную область, поскольку у вас есть копия значения. Изнутри метода мы даже не можем определить, где находится память оригинального Foo.Foo = 11
), это может изменить Foo глобально (зависит от языка, то есть Java или как Pascal procedure findMin(x, y, z: integer;
var m : integer);
). Однако, если язык позволяет обойти процесс разыменования, вы можете изменить 47
, скажем, на 49
. В этот момент Foo, похоже, был изменен, если вы его прочитали, потому что вы изменили на него локальный указатель. И если вы должны изменить этот Foo внутри метода (Foo = 12
), вы, вероятно, будете FUBAR для выполнения программы (aka. Segfault), потому что вы будете писать в другую память, чем ожидалось, вы даже можете изменить область, которая предназначена для выполнения исполняемой программы и записи на нее будет изменен код запуска (Foo теперь не в 47
). BUT Значение Foo 47
не изменилось глобально, а только один внутри метода, потому что 47
также был копией метода.223
внутри метода, он создает тот же хаос, что и в 3. или 4. (указатель, указывающий на теперь плохое значение, которое снова используется как указатель) но это все еще локальная проблема, так как 223 был скопирован. Однако, если вы можете разыменовать Ref2Foo
(то есть 223
), достигните и измените указанное значение 47
, скажем, на 49
, это повлияет на Foo глобально, потому что в этом случае методы получили копию 223
, но ссылочный 47
существует только один раз, и изменение этого значения на 49
приведет к неправильному значению каждого двойного разыменования Ref2Foo
.Отбрасывая незначительные детали, даже языки, которые передают по ссылке, передают значения функциям, но эти функции знают, что они должны использовать его для разыменования. Этот параметр pass-the-reference-as просто скрыт от программиста, потому что он практически бесполезен, а терминология - только по ссылке.
Строгое значение pass-by-value также бесполезно, это означало бы, что массив 100 Мбайт должен быть скопирован каждый раз, когда мы вызываем метод с массивом в качестве аргумента, поэтому Java не может быть строго переданным по значению. Каждый язык передавал бы ссылку на этот огромный массив (как значение) и использовал механизм copy-on-write, если этот массив может быть локально изменен внутри метода или позволяет использовать метод (как Java) для изменения массива глобально (от представление вызывающего абонента), а несколько языков позволяют изменять значение самой ссылки.
Итак, вкратце и в собственной терминологии Java, Java имеет значение pass-by-value, где значение может быть: либо реальное значение, либо значение, которое представляет собой представление a ссылка.
Java всегда проходит по значению, а не по ссылке
Прежде всего, нам нужно понять, что проходит по значению и передает по ссылке.
Передача по значению означает, что вы делаете копию в памяти фактического значения параметра, которое передается. Это копия содержимого фактического параметра.
Передача по ссылке (также называемая pass by address) означает, что сохраняется копия адреса фактического параметра.
Иногда Java может дать иллюзию прохождения по ссылке. Посмотрите, как это работает, используя приведенный ниже пример:
public class PassByValue {
public static void main(String[] args) {
Test t = new Test();
t.name = "initialvalue";
new PassByValue().changeValue(t);
System.out.println(t.name);
}
public void changeValue(Test f) {
f.name = "changevalue";
}
}
class Test {
String name;
}
Результатом этой программы является:
changevalue
Дайте понять шаг за шагом:
Test t = new Test();
Как мы все знаем, он создаст объект в куче и вернет исходное значение обратно в t. Например, предположим, что значение t равно 0x100234
(мы не знаем фактического внутреннего значения JVM, это просто пример).
new PassByValue().changeValue(t);
При прохождении ссылочного т к функции не будет непосредственно передавать фактическое значение задания теста объекта, но это создаст копию т, а затем передать его в функцию. Поскольку он передается по значению, он передает копию переменной, а не фактическую ссылку на нее. Поскольку мы сказали, что значение t 0x100234
, оба t и f будут иметь одинаковое значение и, следовательно, они будут указывать на один и тот же объект.
Если вы измените что-либо в функции с помощью ссылки f, оно изменит существующее содержимое объекта. Вот почему мы получили значение changevalue
, которое обновляется в функции.
Чтобы понять это более четко, рассмотрим следующий пример:
public class PassByValue {
public static void main(String[] args) {
Test t = new Test();
t.name = "initialvalue";
new PassByValue().changeRefence(t);
System.out.println(t.name);
}
public void changeRefence(Test f) {
f = null;
}
}
class Test {
String name;
}
Это NullPointerException
? Нет, потому что он только передает копию справки. В случае прохождения по ссылке он мог бы вызвать NullPointerException
, как показано ниже:
Надеюсь, это поможет.
Нет, он не проходит по ссылке.
Java передается по значению в соответствии со спецификацией Java Language:
Когда вызывается метод или конструктор (§15.12), значения фактических выражений аргументов инициализируют вновь созданные переменные параметра, каждый из объявленного типа, перед выполнением тела метода или конструктор. Идентификатор, который появляется в DeclaratorId, может использоваться как простое имя в теле метода или конструктора для ссылки на формальный параметр .
Java - это вызов по значению.
Как это работает
Вы всегда передаете копию бит значения ссылки!
Если это примитивный тип данных, эти биты содержат значение самого примитивного типа данных. Поэтому, если мы изменим значение заголовка внутри метода, то оно не отражает изменения вне.
Если это тип данных объекта, например Foo foo = new Foo(), то в этом случае копия адреса объекта проходит как ярлык файла, предположим, что у нас есть текстовый файл abc.txt на C:\desktop и предположим, что мы делаем ярлык тот же файл и поместите это внутри C:\desktop\abc-shortcut, поэтому, когда вы получаете доступ к файлу из C:\desktop\abc.txt и пишите 'Stack Overflow' и закрываете файл, и снова вы открываете файл из ярлыка, тогда вы write 'является крупнейшим онлайн-сообществом для программистов, чтобы узнать, что тогда полное изменение файла будет "Qaru - это самое большое онлайн-сообщество для программистов, чтобы учиться", что означает, что не имеет значения, откуда вы открываете файл, каждый раз, когда мы обращались к нему тот же файл, здесь мы можем считать Foo в качестве файла и предположим, что foo хранится на адресе 123hd7h (исходный адрес, например C:\desktop\abc.txt) и 234jdid (скопированный адрес, такой как C:\desktop\abc-shortcut, который фактически содержит исходный адрес файла внутри). Поэтому для лучшего понимания создайте ярлык и почувствуйте...
Насколько я знаю, 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 вы используете копии, которые не влияют на ссылки в main. Но если ваши объекты не являются неизменными, вы можете его изменить, например:
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);
}
edit: я считаю, что это также причина использования StringBuffer, когда дело доходит до "добавления" двух строк, потому что вы можете модифицировать исходный объект, который не может быть с неизменяемыми объектами, такими как String.
swap(a, b)
которая (1) меняет местами a
и b
из POV вызывающей стороны, (2) не зависит от типа в той степени, в которой позволяет статическая типизация (то есть использование ее с другим типом больше ничего не требует чем изменение объявленных типов a
и b
), и (3) не требует, чтобы вызывающая сторона явно передавала указатель или имя, тогда язык поддерживает передачу по ссылке.
Позвольте мне попытаться объяснить свое понимание с помощью четырех примеров. Java передается по значению, а не по ссылке
/**
Передача по значению
В Java все параметры передаются по значению, т.е. назначение аргумента метода не отображается вызывающему.
*/
Пример 1:
public class PassByValueString {
public static void main(String[] args) {
new PassByValueString().caller();
}
public void caller() {
String value = "Nikhil";
boolean valueflag = false;
String output = method(value, valueflag);
/*
* 'output' is insignificant in this example. we are more interested in
* 'value' and 'valueflag'
*/
System.out.println("output : " + output);
System.out.println("value : " + value);
System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {
value = "Anand";
valueflag = true;
return "output";
}
}
Результат
output : output
value : Nikhil
valueflag : false
Пример 2:
/** * * Пройти по значению * */
public class PassByValueNewString {
public static void main(String[] args) {
new PassByValueNewString().caller();
}
public void caller() {
String value = new String("Nikhil");
boolean valueflag = false;
String output = method(value, valueflag);
/*
* 'output' is insignificant in this example. we are more interested in
* 'value' and 'valueflag'
*/
System.out.println("output : " + output);
System.out.println("value : " + value);
System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {
value = "Anand";
valueflag = true;
return "output";
}
}
Результат
output : output
value : Nikhil
valueflag : false
Пример 3:
/** У этого "Pass By Value" есть ощущение "Pass By Reference"
Некоторые люди говорят, что примитивные типы и "String" являются "pass by value" и объекты "проходят по ссылке".
Но из этого примера мы можем понять, что это infact pass только по значению, имея в виду, что здесь мы передаем ссылку как значение. т.е.: ссылка передается по значению. Вот почему они могут меняться, и все же это верно после локального масштаба. Но мы не можем изменить фактическую ссылку за пределами исходной области. что это означает, демонстрируется в следующем примере PassByValueObjectCase2.
*/
public class PassByValueObjectCase1 {
private class Student {
int id;
String name;
public Student() {
}
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
new PassByValueObjectCase1().caller();
}
public void caller() {
Student student = new Student(10, "Nikhil");
String output = method(student);
/*
* 'output' is insignificant in this example. we are more interested in
* 'student'
*/
System.out.println("output : " + output);
System.out.println("student : " + student);
}
public String method(Student student) {
student.setName("Anand");
return "output";
}
}
Результат
output : output
student : Student [id=10, name=Anand]
Пример 4:
/**
В дополнение к тому, что упоминалось в примере 3 (PassByValueObjectCase1.java), мы не можем изменить фактическую ссылку за пределами исходной области. "
Примечание. Я не вставляю код для private class Student
. Определение класса для Student
такое же, как в примере 3.
*/
public class PassByValueObjectCase2 {
public static void main(String[] args) {
new PassByValueObjectCase2().caller();
}
public void caller() {
// student has the actual reference to a Student object created
// can we change this actual reference outside the local scope? Let see
Student student = new Student(10, "Nikhil");
String output = method(student);
/*
* 'output' is insignificant in this example. we are more interested in
* 'student'
*/
System.out.println("output : " + output);
System.out.println("student : " + student); // Will it print Nikhil or Anand?
}
public String method(Student student) {
student = new Student(20, "Anand");
return "output";
}
}
Результат
output : output
student : Student [id=10, name=Nikhil]
Вы никогда не можете передавать по ссылке в Java, и один из способов, который является очевидным, - это когда вы хотите вернуть более одного значения из вызова метода. Рассмотрим следующий бит кода в С++:
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 будет содержать возвращаемое значение. Очевидно, вы можете сделать это другими способами, такими как структурирование вашего кода, чтобы это не было необходимо, или создание класса, который может содержать возвращаемое значение или разрешить его установку. Но простой шаблон, доступный вам в С++ выше, недоступен в Java.
Я думал, что внес свой ответ, чтобы добавить более подробную информацию из Спецификаций.
Во-первых, В чем разница между передачей по ссылке или передачей по значению?
Передача по ссылке означает, что параметр вызываемых функций будет то же, что и переданный аргумент вызывающих (не значение, а тождество - сама переменная).
Передача по значению означает, что параметр вызываемых функций будет копией переданный аргумент вызывающих.
Или из wikipedia, на тему передачи по ссылке
В оценке по запросу (также называемой pass-by-reference), функция получает неявную ссылку на переменная, используемая как аргумент, а не копия ее значения. Эта обычно означает, что функция может изменять (т.е. назначать) переменная, используемая в качестве аргумента, - то, что будет видно ее вызывающей стороне.
И по вопросу пропущенного значения
В вызове по значению вычисляется выражение аргумента, а результирующее значение привязано к соответствующей переменной в функции [...]. Если функция или процедура могут присваивать значения ее параметрам назначается только локальная копия [...].
Во-вторых, нам нужно знать, что использует Java в своих методах. Спецификация языка Java содержит
При вызове метода или конструктора (§15.12), значения выражения фактического аргумента инициализируют вновь созданный параметр переменные, каждый из объявленного типа, перед выполнением тела метод или конструктор.
Таким образом, он присваивает (или связывает) значение аргумента соответствующей переменной параметра.
Каково значение аргумента?
Давайте рассмотрим ссылочные типы, Спецификация виртуальной машины Java сообщает
Существует три типа ссылочных типов : типы классов, типы массивов, и типы интерфейсов. Их значения - это ссылки на динамически созданные экземпляры классов, массивы или экземпляры классов или массивы, которые реализовать интерфейсы соответственно.
В Спецификация языка Java также указано
Опорные значения (часто просто ссылки) являются указателями на эти объекты и специальной нулевой ссылкой, которая ссылается на отсутствие объекта.
Значение аргумента (некоторого ссылочного типа) является указателем на объект. Обратите внимание, что переменная, вызов метода с типом возвращаемого типа ссылочного типа и выражение создания экземпляра (new ...
) разрешают значение ссылочного типа.
Итак,
public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));
все связывают значение ссылки с экземпляром String
с вновь созданным параметром метода param
. Это именно то, что описывает определение pass-by-value. Таким образом, Java - это пропускная способность.
Тот факт, что вы можете следовать ссылке для вызова метода или доступа к полю ссылочного объекта, совершенно не имеет отношения к разговору. Определение pass-by-reference было
Это обычно означает, что функция может изменять (т.е. назначать) переменная, используемая в качестве аргумента, - то, что будет видно ее вызывающей стороне.
В Java изменение переменной означает ее перераспределение. В Java, если вы переназначили переменную в методе, она останется незамеченной для вызывающего. Изменение объекта, на которое ссылается переменная, - это совсем другое понятие.
Примитивные значения также определены в Спецификации виртуальной машины Java, здесь. Значение типа является соответствующим значением интеграла или с плавающей запятой, соответствующим образом закодированным (8, 16, 32, 64 и т.д.).
Различие, или, может быть, только то, как я помню, поскольку я имел такое же впечатление, что и исходный плакат: Java всегда проходит по значению. Все объекты (в Java, все, кроме примитивов) в Java - это ссылки. Эти ссылки передаются по значению.
В Java передаются только ссылки и передаются по значению:
Аргументы Java передаются по значению (ссылка копируется при использовании метода):
В случае примитивных типов поведение Java просто: Значение копируется в другом экземпляре примитивного типа.
В случае объектов это одно и то же: Переменные объекта - это указатели (ведра), содержащие только объекты адрес, которые были созданы с использованием "нового" ключевого слова и скопированы как примитивные типы.
Поведение может отличаться от поведения примитивов: поскольку скопированная объектная переменная содержит один и тот же адрес (к одному и тому же объекту) Объект content/members все еще может быть изменен в рамках метода и более поздний доступ снаружи, что дает иллюзию, что сам объект (содержащий) был передан по ссылке.
"String" Объекты кажутся идеальным встречным примером для городской легенды, говорящей, что "Объекты передаются по ссылке":
Фактически, в рамках метода, который вы никогда не сможете, обновите значение строки, переданной как аргумент:
Объект String содержит символы с объявленным массивом final, которые нельзя изменить. Только адрес объекта может быть заменен другим, используя "новый". Использование "нового" для обновления переменной не позволит объекту получить доступ извне, поскольку переменная была первоначально передана по значению и скопирована.
Как уже упоминалось ранее, Java всегда имеет значение pass-by-value
Вот еще один пример, который поможет вам понять разницу (классический пример подкачки):
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;
}
}
Отпечатки:
До: a = 2, b = 3
После: a = 2, b = 3
Это происходит потому, что iA и iB являются новыми локальными ссылочными переменными, которые имеют одинаковое значение переданных ссылок (они указывают на a и b соответственно). Таким образом, попытка изменить ссылки iA или iB будет меняться только в локальной области, а не вне этого метода.
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
.
Я всегда думаю об этом как "проход по копии". Это копия значения, будь она примитивной или справочной. Если это примитив, это копия битов, которые являются значением, и если это объект, это копия ссылки.
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:
name= Maxx
name= Fido
Примитивные классы-оболочки и строки неизменяемы, поэтому любой пример с использованием этих типов не будет работать так же, как другие типы/объекты.
Несколько поправок к некоторым сообщениям.
C не поддерживает передачу по ссылке. Он ВСЕГДА проходит по значению. С++ поддерживает передачу по ссылке, но не является по умолчанию и довольно опасен.
Не имеет значения, какое значение находится в Java: примитивный или адрес (грубо) объекта, он ВСЕГДА передается по значению.
Если объект Java ведет себя так, как будто он передается по ссылке, это свойство изменчивости и не имеет абсолютно никакого отношения к передающим механизмам.
Я не уверен, почему это так запутанно, возможно, потому, что так много "программистов" Java официально не обучены и, следовательно, не понимают, что действительно происходит в памяти?
Я создал поток, посвященный этим типам вопросов для любых языков программирования здесь.
Также упоминается Java. Вот краткое резюме:
Короче говоря, Java объекты имеют некоторые очень специфические свойства.
В общем, Java имеет примитивные типы (int
, bool
, char
, double
и т.д.), которые передаются непосредственно по значению. Тогда у Java есть объекты (все, что происходит от java.lang.Object
). Объекты на самом деле всегда обрабатываются посредством ссылки (ссылка является указателем, который вы не можете коснуться). Это означает, что по сути объекты передаются по ссылке, так как ссылки обычно не интересны. Тем не менее, это означает, что вы не можете изменить, на какой объект указывается, поскольку сама ссылка передается по значению.
Это звучит странно и запутанно? Давайте рассмотрим, как C реализует передачу по ссылке и передает значение. В C по умолчанию принято передать значение. void foo(int x)
передает значение int по значению. void foo(int *x)
- это функция, которая не хочет int a
, а указатель на int: foo(&a)
. Это можно использовать с оператором &
для передачи адреса переменной.
Возьмем это на С++, и у нас есть ссылки. Ссылки в основном (в этом контексте) синтаксического сахара, которые скрывают указательную часть уравнения: void foo(int &x)
вызывается foo(a)
, где сам компилятор знает, что это ссылка и адрес без ссылки a
должен быть принят. В Java все переменные, относящиеся к объектам, фактически относятся к ссылочному типу, фактически вызывая вызов по ссылке для большинства целей и целей без мелкозернистого управления (и сложности), предоставляемого, например, С++.
Java передает параметры по VALUE и по значению ТОЛЬКО.
Сокращение длинного рассказа:
Для тех, кто приходит из С#: НЕТ параметра "out".
Для тех, кто приходит из PASCAL: НЕТ параметра "var" .
Это означает, что вы не можете изменить ссылку из самого объекта, но вы всегда можете изменить свойства объекта.
Обходным путем является использование параметра StringBuilder
вместо String
. И вы всегда можете использовать массивы!
you can't change the reference from the object itself
В течение всех ответов мы видим, что Java передается по значению или, скорее, как @Gevorg писал: "pass-by-copy-of-the-variable-value", и это идея, которую мы должны иметь в виду все время.
Я сосредотачиваюсь на примерах, которые помогли мне понять эту идею, и это скорее добавление к предыдущим ответам.
Из [1] В Java вы всегда передаете аргументы копией; то есть вы всегда создаете новый экземпляр значения внутри функции. Но есть определенные виды поведения, которые могут заставить вас думать, что вы проходите по ссылке.
Передача по копии: когда переменная передается методу/функции, создается копия (иногда мы слышим, что при передаче примитивов вы делаете копии).
Передача по ссылке: когда переменная передается методу/функции, код в методе/функции работает с исходной переменной (вы все еще передаете копию, но ссылки на значения внутри сложного объекта являются частью обеих версий переменной, как оригинала, так и версии внутри функции. Сложные объекты сами копируются, но внутренние ссылки сохраняются)
Пример из [ref 1]
void incrementValue(int inFunction){
inFunction ++;
System.out.println("In function: " + inFunction);
}
int original = 10;
System.out.print("Original before: " + original);
incrementValue(original);
System.out.println("Original after: " + original);
We see in the console:
> Original before: 10
> In Function: 11
> Original after: 10 (NO CHANGE)
Пример из [ref 2]
хорошо показывает механизм смотреть максимум 5 минут
Пример из [ref 1] (помните, что массив является объектом)
void incrementValu(int[] inFuncion){
inFunction[0]++;
System.out.println("In Function: " + inFunction[0]);
}
int[] arOriginal = {10, 20, 30};
System.out.println("Original before: " + arOriginal[0]);
incrementValue(arOriginal[]);
System.out.println("Original before: " + arOriginal[0]);
We see in the console:
>Original before: 10
>In Function: 11
>Original before: 11 (CHANGE)
Сложные объекты сами копируются, но внутренние ссылки сохраняются.
Пример из [ref 3]
package com.pritesh.programs;
class Rectangle {
int length;
int width;
Rectangle(int l, int b) {
length = l;
width = b;
}
void area(Rectangle r1) {
int areaOfRectangle = r1.length * r1.width;
System.out.println("Area of Rectangle : "
+ areaOfRectangle);
}
}
class RectangleDemo {
public static void main(String args[]) {
Rectangle r1 = new Rectangle(10, 20);
r1.area(r1);
}
}
Площадь прямоугольника равна 200, а длина = 10 и ширина = 20
Последняя вещь. Я хотел бы поделиться этим моментом лекции: Распределение памяти который я нашел очень полезным в понимании передачи Java по значению или, скорее, "pass-by-copy-of-the-variable-value", как написал @Gevorg.
Это действительно довольно, довольно просто:
Для переменной примитивного типа (например, int
, boolean
, char
и т.д.)), когда вы используете его имя для аргумента метода, вы передаете значение, содержащееся в нем (5
, true
или 'c'
). Это значение получает "скопировано", и переменная сохраняет свое значение даже после вызова метода.
Для переменной ссылочного типа (например, String
, Object
и т.д.)), когда вы используете свое имя для аргумента метода, вы передаете значение, содержащееся в нем (ссылка значение, которое "указывает" на объект). Это опорное значение получает "скопированы", а переменная сохраняет свое значение даже после вызова метода. Контрольная переменная сохраняет "указание" на тот же объект.
В любом случае, вы всегда передаете материал по значению.
Сравните это, чтобы сказать С++, где вы можете иметь метод для принятия int&
или на С#, где вы могли бы взять ref int
(хотя в этом случае вам также необходимо использовать модификатор ref
при передаче имени переменной в метод.)
String
S неизменны в Java.
Foo
в «Car # 1234» и скопировать Foo
в Bar
, то и Foo
и Bar
будут содержать «Car # 1234». Foo.SetColor(Colors.Blue)
раскрасит «Автомобиль № 1234» в синий цвет. Другое описание, которое я использую для таких мест хранения, - «разнородные ссылки на объекты», поскольку код, который передает такую ссылку на объект, не может контролировать, как получатель может поделиться им.
Java копирует ссылку по значению. Поэтому, если вы измените его на что-то еще (например, используя new
), ссылка не изменится вне метода. Для нативных типов он всегда передается по значению.
Это лучший способ ответить на вопрос...
Во-первых, мы должны понимать, что в Java поведение передачи параметров...
public void foo(Object param)
{
// some code in foo...
}
public void bar()
{
Object obj = new Object();
foo(obj);
}
точно так же, как...
public void bar()
{
Object obj = new Object();
Object param = obj;
// some code in foo...
}
не учитывая расположение стеков, которые не имеют отношения к этому обсуждению.
Итак, на самом деле, то, что мы ищем в Java, это как работает назначение переменных. Я нашел это в документах:
Одним из наиболее распространенных операторов, с которыми вы столкнетесь, является простой оператор присваивания "=" [...], который присваивает значение справа операнду слева:
int cadence = 0;
int speed = 0;
int gear = 1;Этот оператор может также использоваться на объектах для назначения ссылок на объекты [...]
Понятно, как этот оператор действует двумя различными способами: назначать значения и назначать ссылки. Последнее, когда это объект... первое, когда это не объект, то есть когда это примитив. Но так, можем ли мы понять, что параметры функций Java могут быть по значению и по ссылке?
Истина в коде. Давай попробуем это:
public class AssignmentEvaluation
{
static public class MyInteger
{
public int value = 0;
}
static public void main(String[] args)
{
System.out.println("Assignment operator evaluation using two MyInteger objects named height and width\n");
MyInteger height = new MyInteger();
MyInteger width = new MyInteger();
System.out.println("[1] Assign distinct integers to height and width values");
height.value = 9;
width.value = 1;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things! \n");
System.out.println("[2] Assign to height value the width value");
height.value = width.value;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[3] Assign to height value an integer other than width value");
height.value = 9;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things yet! \n");
System.out.println("[4] Assign to height the width object");
height = width;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[5] Assign to height value an integer other than width value");
height.value = 9;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are the same thing now! \n");
System.out.println("[6] Assign to height a new MyInteger and an integer other than width value");
height = new MyInteger();
height.value = 1;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things again! \n");
}
}
Это результат моего бега:
Assignment operator evaluation using two MyInteger objects named height and width [1] Assign distinct integers to height and width values -> height is 9 and width is 1, we are different things! [2] Assign to height value the width value -> height is 1 and width is 1, are we the same thing now? [3] Assign to height value an integer other than width value -> height is 9 and width is 1, we are different things yet! [4] Assign to height the width object -> height is 1 and width is 1, are we the same thing now? [5] Assign to height value an integer other than width value -> height is 9 and width is 9, we are the same thing now! [6] Assign to height a new MyInteger and an integer other than width value -> height is 1 and width is 9, we are different things again!
В [2] мы имеем разные объекты и присваиваем одно значение переменной другому. Но после присвоения нового значения в [3] объекты имели разные значения, что означает, что в [2] назначенное значение было копией примитивной переменной, обычно называемой передачей по значению, в противном случае значения выводились в [3]. ] должно быть таким же.
В [4] у нас все еще есть отдельные объекты и мы присваиваем один объект другому. И после присвоения нового значения в [5] объекты имели те же значения, что означает, что в [4] назначенный объект не был копией другого, который следует называть передачей по ссылке. Но, если мы посмотрим внимательно в [6], мы не можем быть уверены, что ни одна копия не была сделана...?????
Мы не можем быть так уверены, потому что в [6] объекты были одинаковыми, тогда мы присвоили новый объект одному из них, и после этого объекты имели разные значения! Как они могут различаться сейчас, если они были одинаковыми? Они должны быть одинаковыми и здесь! ?????
Нам нужно запомнить документы, чтобы понять, что происходит:
Этот оператор также может использоваться на объектах для назначения ссылок на объекты
Таким образом, наши две переменные хранили ссылки... наши переменные имели одну и ту же ссылку после [4] и разные ссылки после [6]... если такая возможность возможна, это означает, что присвоение объектов выполняется путем копирования объекта ссылка, в противном случае, если это не копия ссылки, напечатанное значение переменных в [6] должно быть одинаковым. Таким образом, объекты (ссылки), как и примитивы, копируются в переменные посредством присваивания, что люди обычно называют передачей по значению. Это единственный обход Java.
Java передается по постоянной ссылке, где передается копия ссылки, что означает, что это в основном пропуск по значению. Вы можете изменить содержание ссылки, если класс изменен, но вы не можете изменить саму ссылку. Другими словами, адрес не может быть изменен, поскольку он передается по значению, но содержимое, на которое указывает адрес, может быть изменено. В случае неизменяемых классов содержимое ссылки также не может быть изменено.
Java, несомненно, без сомнения, является "pass by value". Кроме того, поскольку Java (в основном) объектно-ориентированная и объекты работают со ссылками, легко запутаться и подумать о том, что она "проходит по ссылке"
Передача по значению означает, что вы передаете значение методу, и если метод изменяет переданное значение, реальный объект не изменяется. С другой стороны, передача по ссылке означает, что ссылка передается методу, и если метод изменяет ее, передаваемый объект также изменяется.
В Java, как правило, когда мы передаем объект методу, мы в основном передаем ссылку объекта как -значение, потому что это работает Java; он работает со ссылками и адресами, поскольку объект находится в куче.
Но чтобы проверить, действительно ли он передается по значению или передается по ссылке, вы можете использовать примитивный тип и ссылки:
@Test
public void sampleTest(){
int i = 5;
incrementBy100(i);
System.out.println("passed ==> "+ i);
Integer j = new Integer(5);
incrementBy100(j);
System.out.println("passed ==> "+ j);
}
/**
* @param i
*/
private void incrementBy100(int i) {
i += 100;
System.out.println("incremented = "+ i);
}
Вывод:
incremented = 105
passed ==> 5
incremented = 105
passed ==> 5
Итак, в обоих случаях все, что происходит внутри метода, не изменяет реальный объект, потому что значение этого объекта передано, а не ссылка на сам объект.
Но когда вы передаете пользовательский объект методу, а метод и меняет его, он также изменит реальный объект, потому что даже когда вы передавали объект, вы передавали ему ссылку как значение для метода. Попробуем еще один пример:
@Test
public void sampleTest2(){
Person person = new Person(24, "John");
System.out.println(person);
alterPerson(person);
System.out.println(person);
}
/**
* @param person
*/
private void alterPerson(Person person) {
person.setAge(45);
Person altered = person;
altered.setName("Tom");
}
private static class Person{
private int age;
private String name;
public Person(int age, String name) {
this.age=age;
this.name =name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Person [age=");
builder.append(age);
builder.append(", name=");
builder.append(name);
builder.append("]");
return builder.toString();
}
}
В этом случае вывод:
Person [age=24, name=John]
Person [age=45, name=Tom]
Java всегда использует вызов по значению. Это означает, что метод получает копию всех значений параметров.
Рассмотрим следующие 3 ситуации:
public static void increment(int x) { x++; }
int a = 3;
increment(a);
x скопирует значение a и будет увеличивать x, a остается тем же самым
public static void increment(Person p) { p.age++; }
Person pers = new Person(20); // age = 20
increment(pers);
р будет копировать опорное значение и чел будет увеличивать поле возраста, переменные ссылки на тот же объект так что возраст изменяется
public static void swap(Person p1, Person p2) {
Person temp = p1;
p1 = p2;
p2 = temp;
}
Person pers1 = new Person(10);
Person pers2 = new Person(20);
swap(pers1, pers2);
после вызова swap p1, p2 копирует опорные значения из pers1 и pers2, меняются со значениями, поэтому pers1 и pers2 остаются теми же
Итак. вы можете изменить только поля объектов в методе, передавая копию эталонного значения этому объекту.
Одной из самых больших путаниц в языке программирования Java является ли Java Pass by Value или Pass by Reference.
Прежде всего, мы должны понимать, что понимается под передачей по значению или по ссылке.
Pass by Value: Значения параметров метода копируются в другую переменную, а затем скопированный объект передается, поэтому его вызов передается по значению.
Передача по ссылке: Алиас или ссылка на фактический параметр передаются методу, поэтому его вызов передается по ссылке.
Допустим, у нас есть класс Balloon, как показано ниже.
public class Balloon {
private String color;
public Balloon(){}
public Balloon(String c){
this.color=c;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
И у нас есть простая программа с общим методом для обмена двумя объектами, класс выглядит следующим образом.
public class Test {
public static void main(String[] args) {
Balloon red = new Balloon("Red"); //memory reference 50
Balloon blue = new Balloon("Blue"); //memory reference 100
swap(red, blue);
System.out.println("red color="+red.getColor());
System.out.println("blue color="+blue.getColor());
foo(blue);
System.out.println("blue color="+blue.getColor());
}
private static void foo(Balloon balloon) { //baloon=100
balloon.setColor("Red"); //baloon=100
balloon = new Balloon("Green"); //baloon=200
balloon.setColor("Blue"); //baloon = 200
}
//Generic swap method
public static void swap(Object o1, Object o2){
Object temp = o1;
o1=o2;
o2=temp;
}
}
Когда мы выполняем вышеуказанную программу, получаем следующий вывод.
red color=Red
blue color=Blue
blue color=Red
Если вы посмотрите на первые две строки вывода, ясно, что метод swap не работал. Это связано с тем, что Java передается по значению, этот метод swap() может использоваться с любым языком программирования, чтобы проверить, прошел ли его пропуск по значению или передается по ссылке.
Позволяет анализировать выполнение программы шаг за шагом.
Balloon red = new Balloon("Red");
Balloon blue = new Balloon("Blue");
Когда мы используем новый оператор для создания экземпляра класса, экземпляр создается, а переменная содержит ссылочное расположение памяти, в которой сохраняется объект. В нашем примере предположим, что "красный" указывает на 50, а "синий" указывает на 100, и это расположение памяти обоих объектов Balloon.
Теперь, когда мы вызываем метод swap(), создаются две новые переменные o1 и o2, указывающие соответственно на 50 и 100.
Итак, ниже фрагмент кода объясняет, что произошло при выполнении метода swap().
public static void swap(Object o1, Object o2){ //o1=50, o2=100
Object temp = o1; //temp=50, o1=50, o2=100
o1=o2; //temp=50, o1=100, o2=100
o2=temp; //temp=50, o1=100, o2=50
} //method terminated
Обратите внимание, что мы меняем значения o1 и o2, но они являются копиями "красных" и "синих" опорных мест, поэтому на самом деле нет изменений в значениях "красный" и "синий" и, следовательно, выход.
Если вы это поняли, вы можете легко понять причину путаницы. Поскольку переменные являются только ссылкой на объекты, мы путаем, что мы передаем ссылку, поэтому Java передается по ссылке. Однако мы передаем копию ссылки и, следовательно, ее пропуск по значению. Надеюсь, теперь он очистит все сомнения.
Теперь проанализируйте выполнение foo().
private static void foo(Balloon balloon) { //baloon=100
balloon.setColor("Red"); //baloon=100
balloon = new Balloon("Green"); //baloon=200
balloon.setColor("Blue"); //baloon = 200
}
Первая строка является важной, когда мы вызываем метод, который метод вызывается в Object в ссылочном местоположении. В этот момент баллон указывает на 100, и, следовательно, его цвет меняется на красный.
В следующей строке ссылка баллона изменяется на 200, и любые другие выполненные методы происходят на объекте в ячейке 200 памяти и не оказывают никакого влияния на объект в ячейке памяти 100. Это объясняет третью строку нашего выхода программы печать синего цвета = красный.
Надеюсь, что выше объяснения ясны все сомнения, просто помните, что переменные являются ссылками или указателями, и его копия передается методам, поэтому Java всегда передается по значению. Было бы более понятно, когда вы узнаете о памяти кучи и стека и где хранятся разные объекты и ссылки.
Сколько долгих ответов. Позвольте мне дать простой:
Короче говоря, вы не можете изменять значение любого переданного параметра, но вы можете вызывать методы или изменять атрибуты переданного объекта.
В отличие от некоторых других языков, Java не позволяет вам выбирать pass-by-value или передавать by-reference-все аргументы передаются по значению. Вызов метода может передавать два типа значений в метод-копии примитивных значений (например, значения int и double) и копии ссылок на объекты.
Когда метод изменяет параметр примитивного типа, изменения в параметре не влияют на исходное значение аргумента в вызывающем методе.
Когда дело доходит до объектов, объекты сами не могут быть переданы методам. Поэтому мы передаем адрес объекта, который хранится в ссылочной переменной.
Как Java создает и хранит объекты. Когда мы создаем объект, мы сохраняем адрес объекта в ссылочной переменной. "Ввод сканера" - это тип и ссылочная переменная, "=" - оператор присваивания, "новый" запрашивает требуемое количество пространства из системы. Конструктор справа от ключевого слова new, который создает объект, называется неявным ключевым словом new. Адрес созданного объекта (результат правой переменной, который является выражением) присваивается левой переменной (которая является ссылочной переменной с именем и указанным типом) с использованием оператора присваивания. "new Account()" называется выражением создания экземпляра класса.
Хотя ссылка на объект передается по значению, метод все равно может взаимодействовать с ссылочным объектом, вызывая его общедоступные методы, используя копию ссылки на объекты. Поскольку ссылка, хранящаяся в параметре, является копией ссылки, которая была передана в качестве аргумента, параметр вызываемого метода и аргумент в вызывающем методе относятся к одному и тому же объекту в памяти.
Передача ссылок на массивы, а не на объекты массива, имеет смысл по соображениям производительности. Поскольку все в Java передается по значению, если объекты массива переданы, копия каждого элемента будет передана. Для больших массивов это будет тратить время и потреблять значительную память для копий элементов.
На изображении ниже вы можете увидеть, что у нас есть две контрольные переменные (они называются указателями в C/C++. И я думаю, что этот термин упрощает понимание этой функции.) В основном методе. Примитивные и опорные переменные хранятся в памяти стека (слева на рисунках ниже). Эти ссылочные переменные "точка" (как это называют программисты C/C++) или ссылки на массивы a и b, которые являются объектами (значения, которые эти ссылочные переменные содержат, являются адресами объектов) в кучевой памяти (справа на рисунках ниже).
Если мы передадим значение ссылочной переменной array1 в качестве аргумента методу reverseArray, в методе создается эталонная переменная, и эта ссылочная переменная начинает указывать на тот же массив (a).
reverseArray(array1);
...
public void reverseArray(Int[] array1)
{
...
}
Итак, если мы скажем
array1[0] = 5;
в методе reverseArray он внесет изменения в массив a.
У нас есть другая ссылочная переменная в методе reverseArray (array2), которая указывает на массив c. Если бы мы сказали
array1 = array2;
в методе reverseArray, тогда эталонная переменная array1 в методе reverseArray перестанет указывать на массив a и начнет указывать на массив c (пунктирная линия на втором изображении).
Если мы вернем значение ссылочной переменной array2 в качестве возвращаемого значения метода reverseArray и присвоим это значение ссылочной переменной array1 в основном методе, array1 в main начнет указывать на массив c.
Вы можете назначить значение array2 в main также для array1. array1 начнет указывать на b.
Когда я говорю pass by value, это означает, что каждый раз, когда вызывающий вызывал вызываемые аргументы (т.е. данные, передаваемые другой функции) копируется и помещается в формальных параметрах (вызываемые локальные переменные для приема ввода). Java позволяет передавать данные из одной функции в другую функцию только в среде с пропускной способностью.
Важным моментом было бы знать, что даже язык C строго передается только значением:
т.е.: данные копируются от вызывающего абонента к вызываемому абоненту, и более того, операция, выполняемая вызываемым пользователем, находится в том же месте памяти и
то, что мы передаем им, является адресом того места, которое мы получаем от оператора (&), и идентификатор, используемый в формальных параметрах, объявляется как указательная переменная (*), с помощью которой мы можем попасть в ячейку памяти для доступа к данным в нем.
Следовательно, формальный параметр - не что иное, как простые псевдонимы для этого местоположения. И любые изменения, сделанные в этом местоположении, видны там, где когда-либо существовала область видимости (которая идентифицирует это местоположение).
В Java нет понятия указателей (т.е. ничего не называется переменной указателя), хотя мы можем рассматривать ссылочную переменную как указатель технически в java, которую мы называем ее как дескриптор. Причина, по которой мы вызываем указатель на адрес в качестве дескриптора в java, состоит в том, что переменная указателя способна выполнять не только разглашение, но и множественное разыменование
например: int *p;
в P означает p указывает на целое число
и int **p;
в C означает, что p является указателем на указатель на целое число
у нас нет этого средства на Java, поэтому его абсолютно корректно и технически законно сказать это как дескриптор, также существуют правила для арифметики указателей в C. Это позволяет выполнять арифметическую операцию с указателями с ограничениями на нее.
В C мы называем такой механизм передачи адреса и получения его с переменными-указателями как pass by reference, поскольку мы передаем их адреса и получаем их как переменную-указатель в формальном параметре, но на уровне компилятора этот адрес копируется в переменную-указатель ( поскольку данные здесь являются адресом, а затем его данными), поэтому мы можем быть на 100% уверены, что C строго передается по значению (поскольку мы передаем только данные)
(и если мы передаем данные непосредственно в C, мы будем называть это как пропуск по значению.)
В java, когда мы делаем то же самое, мы делаем это с помощью ручек; так как они не называются переменными указателя, как в (как обсуждалось выше), хотя мы передаем ссылки, которые мы не можем сказать, его пропуск по ссылке, поскольку мы не собираем это с переменной указателя в Java.
Следовательно, Java строго использует механизм передачи по значению
Знание основных краеугольных камней должно быть процитированным,
Когда ссылка объекта передается методу, сама ссылка передается с использованием вызова по значению. Однако, поскольку значение переданный относится к объекту, копия этого значения будет по-прежнему ссылаться на тот же объект, на который ссылается его соответствующий аргумент.
Java: руководство для новичков, шестое издание, Герберт Шильдт
Посмотрите на этот код. Этот код не будет бросать 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.
Простой тест, чтобы проверить, поддерживает ли язык pass-by-reference, просто написать традиционный обмен. Можете ли вы написать традиционный метод/функцию swap (a, b) в Java?
Традиционный метод или функция свопинга принимает два аргумента и свопирует их так, что переменные, переданные в функцию, изменяются вне функции. Его основная структура выглядит как
(Non-Java) Основная структура функции свопинга
swap(Type arg1, Type arg2) {
Type temp = arg1;
arg1 = arg2;
arg2 = temp;
}
Если вы можете написать такой метод/функцию на своем языке, чтобы вызов
Type var1 = ...;
Type var2 = ...;
swap(var1,var2);
фактически переключает значения переменных var1 и var2, язык поддерживает pass-by-reference. Но Java не позволяет такую вещь, поскольку она поддерживает передачу только значений, а не указателей или ссылок.
Я понимаю, что это на самом деле очень просто:
Java всегда проходит по значению, но когда переменная ссылается на объект (включая массивы), "значение" является ссылкой на объект.
Изменение значения переменной никогда не изменяет базовый примитив или объект, оно просто указывает переменную на новый примитив или объект.
Однако изменение свойства объекта, на которое ссылается переменная, изменяет базовый объект.
Термин вызов по значению означает, что метод получает только то значение, которое предоставляет вызывающая сторона. Напротив, вызов по ссылке означает, что метод получает местоположение (адрес) переменной, которую предоставляет вызывающая сторона.
Язык программирования Java всегда использует вызов по значению. Это означает, что метод получает копию всех значений параметров. В частности, даже если метод может изменить объект, на который ссылается переменная вызывающего, он не может изменить саму переменную.
В приведенном ниже println(sb_actual)
вызов println(sb_actual)
печатает "Foo", а не "Bar", потому что присвоение sb_formal
в addFoo()
не изменяет переменную вызывающего абонента sb_actual
.
void addFoo(StringBuffer sb_formal) {
sb_formal.append("Foo"); // Modifies the object created by the caller.
sb_formal = new StringBuffer(); // Creates a new object.
sb_formal.append("Bar"); // Modifies the new object.
}
void caller() {
StringBuffer sb_actual = new StringBuffer();
addFoo(sb_actual);
System.out.println(sb_actual); // sb_actual still refers to the original object here.
}
Теперь люди любят бесконечно препираться о том, является ли "пройти по ссылке" правильным способом описать, что такое Java et al. действительно делаю. Дело в том, что:
В моей книге, которая называется прохождение по ссылке.
- Брайан Би - Какие языки программирования проходят по ссылке?
В попытке добавить еще больше к этому, я подумал, что я бы включил раздел "Учебное руководство 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() действительно смог изменить оригинальный (и только) размерный объект, созданный в строке 4.
C:\Java Projects\Reference>java ReferenceTest Before modify() d.height = 10 dim = 11 After modify() d.height = 11
Обратите внимание, когда объект Dimension в строке 4 передается методу modify(), любые изменения объекта, которые происходят внутри метода, производятся объекту, ссылка на который была передана. В предыдущем примере ссылочные переменные d и dim оба указывают на один и тот же объект.
Использует ли Java семантику Pass-By-Value?
Если Java передает объекты, передавая вместо них ссылочную переменную, означает ли это, что Java использует pass-by-reference для объектов? Не совсем, хотя вы часто слышите и читаете, что он делает. Java фактически передается по значению для всех переменных, работающих в одной виртуальной машине. Пошаговое значение означает pass-by-variable-value. И это означает, что вы переходите к переменной! (Там это слово копируется снова!)
Не имеет значения, передаете ли вы примитивные или ссылочные переменные, вы всегда передаете копию бит в переменной. Итак, для примитивной переменной вы передаете копию битов, представляющих значение. Например, если вы передаете переменную int со значением 3, вы передаете копию битов, представляющих 3. Затем вызываемый метод получает свою собственную копию значения, чтобы делать с ней то, что ему нравится.
И если вы передаете ссылочную переменную объекта, вы передаете копию битов, представляющих ссылку на объект. Вызываемый метод затем получает свою собственную копию ссылочной переменной, чтобы делать с ней то, что ей нравится. Но поскольку две идентичные ссылочные переменные относятся к одному и тому же объекту, если вызываемый метод изменяет объект (например, вызывая методы setter), вызывающий может увидеть, что объект, к которому ссылается исходная переменная вызывающего, также был изменен. В следующем разделе мы рассмотрим, как меняется изображение, когда мы говорим о примитивах.
Нижняя строка по значению pass-by-value: вызываемый метод не может изменить переменную вызывающего, хотя для ссылочных переменных объекта вызываемый метод может изменить объект, на который ссылается переменная. Какая разница между изменением переменной и изменением объекта? Для ссылок на объекты это означает, что вызываемый метод не может переназначить исходную ссылочную переменную вызывающего и сделать ее ссылкой на другой объект или 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 не изменилось после его передачи методу. Помните, что это была копия того, что было передано методу. Когда примитивная переменная передается методу, она передается по значению, что означает "передача по кодам-of-the-bits-in-the-variable".
В java аргументы всегда передаются по значению независимо от исходного типа переменной. Каждый раз, когда вызывается метод, происходит следующее:
Внутри метода вы можете изменять данные объекта, на который ссылаетесь, но вы не можете изменить саму ссылку.
Этот учебник объясняет, почему Java всегда передается по значению.
Существует очень простой способ понять это. Позволяет передавать С++ по ссылке.
#include <iostream>
using namespace std;
class Foo {
private:
int x;
public:
Foo(int val) {x = val;}
void foo()
{
cout<<x<<endl;
}
};
void bar(Foo& ref)
{
ref.foo();
ref = *(new Foo(99));
ref.foo();
}
int main()
{
Foo f = Foo(1);
f.foo();
bar(f);
f.foo();
return 0;
}
Каков результат?
1 1 99 99
Итак, после того, как bar() присвоил новое значение переданной "ссылке", он фактически изменил ту, которая была передана из самой основной, объясняя последний вызов f.foo() из основной печати 99.
Теперь давайте посмотрим, что говорит java.
public class Ref {
private static class Foo {
private int x;
private Foo(int x) {
this.x = x;
}
private void foo() {
System.out.println(x);
}
}
private static void bar(Foo f) {
f.foo();
f = new Foo(99);
f.foo();
}
public static void main(String[] args) {
Foo f = new Foo(1);
System.out.println(f.x);
bar(f);
System.out.println(f.x);
}
}
В нем говорится:
1 1 99 1
Voilà, ссылка Foo в main, которая была передана в bar, по-прежнему не изменилась!
В этом примере ясно показано, что java не совпадает с С++, когда мы говорим "pass by reference". По сути, java передает "ссылки" как "значения" в функции, то есть java передается по значению.
Foo(99)
выходит из области видимости, но вы ссылаетесь на нее в своем основном методе?
Это немного сложно понять, но Java всегда копирует значение - точка есть, обычно это значение является ссылкой. Поэтому вы получаете тот же объект, не думая об этом...
Существует голубой, 120sq-ft "Крошечный дом", который в настоящее время припаркован в 1234 Main St с красивым ухоженным газоном и цветником.
Арендодатель с местной фирмой нанимается и велел сохранить листинг для этого дома.
Позвольте назвать этого риэлтора "Боб". Привет, Боб.
Боб держит свой Листинг, который он называет tinyHouseAt1234Main
, обновляется с помощью веб-камеры, которая позволяет ему замечать любые изменения в реальном доме в реальном времени. Он также подсчитывает, сколько людей спрашивали о листинге.
Боб целое число viewTally
для дома составляет 42 сегодня.
Всякий раз, когда кто-то хочет получить информацию о синем Крошечном Доме в 1234 Main St, они спрашивают Боба.
Боб просматривает свой листинг tinyHouseAt1234Main
и рассказывает им об этом - цвет, красивый газон, кровать для лофта и туалет для компостирования и т.д. Затем он добавляет свое расследование к своему viewTally
. Он не говорит им настоящего физического адреса, потому что фирма Боба специализируется на крошечных домах, которые могут быть перемещены в любое время. Талли теперь 43.
В другой фирме риэлторы могут прямо заявить, что их листинг "указывает" на дом в 1234 Main St, обозначая это небольшим *
рядом с ним, поскольку они в основном касаются домов, которые редко когда-либо перемещаются (хотя, вероятно, там причины для этого). Боб фирма не беспокоится об этом.
Теперь, конечно, Боб физически не уходит и не помещает фактический дом в грузовик, чтобы показывать его клиентам напрямую - это было бы непрактичным и смехотворной тратой ресурсов. Передача полной копии его листа с таблицами - это одно, а прохождение всего дома все время дорого и смешно.
(Кроме того, фирма Боба также не 3D печатает новые и уникальные копии перечисленного дома каждый раз, когда кто-то спрашивает об этом. Это то, что делают выскочка, так называемая веб-фирма и ее спиноффы - что дорого и медленнее, и люди часто получают две фирмы в замешательстве, но они довольно популярны).
В некоторых других, более старых фирмах, расположенных ближе к морю, риэлтор, такой как Боб, может даже не существовать, чтобы управлять Листами. Клиенты могут вместо этого обратиться к Rolodex "Annie" (&
для краткости) для прямого адреса дома. Вместо того, чтобы читать данные, указанные в ссылке, из списка, такого как Боб, клиенты вместо этого получают домашний адрес от Энни (&
) и переходят непосредственно к 1234 Main St, иногда даже не представляя, что они могут там найти.
Однажды фирма Боб начинает предлагать новую автоматизированную услугу, которая нуждается в листинге для дома, в котором заинтересована клиент.
Ну, человек с этой информацией - Боб, поэтому у клиента есть Боб, который вызывает эту услугу и отправляет ей копию списка.
jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally)
Боб отправляет...
Служба в конце вызывает этот листинг houseToLookAt
, но на самом деле то, что он получает, является точной копией списка Боба, с теми же самыми VALUE в ней, которые относятся к дому в 1234 Main St.
У этой новой службы также есть своя внутренняя оценка того, сколько людей просмотрели листинг. Служба принимает Боба из профессиональной любезности, но это все равно, и все равно перезаписывает его со своей собственной локальной копией. Это на сегодняшний день составляет 1, а Боб все еще 43.
Фирмы-риэлторы называют это "пропуском", поскольку Боб передает текущее значение своего viewTally
и его листинга tinyHouseAt1234Main
. Он фактически не проходит по всему физическому дому, потому что это непрактично. И он не передаст реальный физический адрес, как Энни (&
).
Но он передает копию значения ссылки, которую он имеет в доме. Кажется, это глупое педантичное различие в чем-то, но то, как работает его фирма... ..............
Новый автоматизированный сервис, не являющийся функциональным и ориентированным на математику, как некоторые другие модные финансовые и научные фирмы, может иметь непредвиденные побочные эффекты...
После предоставления объекта листинга он позволяет клиентам фактически перекрасить дом REAL на 1234 Main St, используя удаленный флот робота-поворота! Он позволяет клиентам управлять бульдозером-роботом, чтобы НАПРЯЖАТЬ копать клумбу! Это безумие!!!
Услуга также позволяет клиентам полностью перенаправлять houseToLookAt
в другой дом по другому адресу без привлечения Боба или его листинга. Внезапно они могли смотреть на 4321 Elm St. вместо этого, у которого нет никакой связи с записью Боба (к счастью, они не могут больше наносить урон).
Боб наблюдает за всем этим на своей веб-камере в реальном времени.
Смирившись с тяжелым трудом своей единственной ответственной работы, он рассказывает клиентам о новой уродливой работе с краской и о внезапном отсутствии ажиотажа. В конце концов, его листинг все еще для главной улицы 1234 года. Новая услуга houseToLookAt
не может изменить это. Боб докладывает подробности своего tinyHouseAt1234Main
точно и послушно, как всегда, до тех пор, пока его не уволят, или дом полностью разрушен Ничем.
Действительно, единственное, что служба НЕ МОЖЕТ сделать со своей копией houseToLookAt
первоначального листинга Боба, - это изменить адрес от 1234 Main St. на какой-то другой адрес или на пустоту ничтожества или на какой-то случайный тип объекта, подобного утконосу. Боб Листинг все еще всегда указывает на 1234 Main St, что бы он ни стоил. Он передает текущее значение, как всегда.
Этот странный побочный эффект передачи списка в новую автоматизированную услугу запутан для людей, которые спрашивают о том, как это работает. Действительно, какая разница между способностью дистанционно управлять роботами, которые изменяют состояние дома в 1234 Main, по сравнению с тем, что физически происходит там и разрушает хаос, потому что Энни дала вам адрес?
Кажется, что это какой-то странный семантический аргумент, если вы вообще заботитесь о том, что состояние дома в листинге копируется и передается, верно?
Я имею в виду, если бы вы занимались тем, что собирали дома и физически перемещали их по другим адресам (не таким, как мобильные или крошечные дома, где такая ожидаемая функция платформы), или вы обращались, переименовывали, и перетасовать целые кварталы, как какой-то сумасшедший безумный босс низкого уровня, ТОГДА, возможно, вам будет больше нужно пропустить вокруг этих конкретных адресных ссылок, а не просто копии последнего значения деталей дома...
передано по ссылке: вызывающий и вызываемый используют одну и ту же переменную для параметра.
передано по значению: вызывающий и вызываемый имеют две независимые переменные с одинаковым значением.
Пример использования примитивного типа данных:
public class PassByValuePrimitive {
public static void main(String[] args) {
int i=5;
System.out.println(i); //prints 5
change(i);
System.out.println(i); //prints 5
}
private static void change(int i) {
System.out.println(i); //prints 5
i=10;
System.out.println(i); //prints 10
}
}
Пример использования объекта:
public class PassByValueObject {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("prem");
list.add("raj");
new PassByValueObject().change(list);
System.out.println(list); // prints [prem, raj, ram]
}
private void change(List list) {
System.out.println(list.get(0)); // prem
list.add("ram");
list=null;
System.out.println(list.add("bheem")); //gets NullPointerException
}
}
Java передается по значению.
В этой теме уже есть отличные ответы. Так или иначе, я никогда не пропускал значение/ссылку относительно примитивных типов данных и по отношению к объектам. Поэтому я проверил это для моего удовлетворения и ясности с помощью следующего фрагмента кода; может помочь кому-то искать подобную ясность:
class Test {
public static void main (String[] args) throws java.lang.Exception
{
// Primitive type
System.out.println("Primitve:");
int a = 5;
primitiveFunc(a);
System.out.println("Three: " + a); //5
//Object
System.out.println("Object:");
DummyObject dummyObject = new DummyObject();
System.out.println("One: " + dummyObject.getObj()); //555
objectFunc(dummyObject);
System.out.println("Four: " + dummyObject.getObj()); //666 (555 if line in method uncommented.)
}
private static void primitiveFunc(int b) {
System.out.println("One: " + b); //5
b = 10;
System.out.println("Two:" + b); //10
}
private static void objectFunc(DummyObject b) {
System.out.println("Two: " + b.getObj()); //555
//b = new DummyObject();
b.setObj(666);
System.out.println("Three:" + b.getObj()); //666
}
}
class DummyObject {
private int obj = 555;
public int getObj() { return obj; }
public void setObj(int num) { obj = num; }
}
Если строка b = new DummyObject()
раскоментирована, изменения, сделанные после этого, производятся на новом объекте, новом экземпляре. Следовательно, это не отражается в том месте, откуда вызывается метод. Однако в противном случае изменение отражается, поскольку изменения производятся только по "ссылке" объекта, т.е. B указывает на тот же объект dummy.
Иллюстрации в одном из ответов в этом потоке (https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) могут помочь получить более глубокое понимание.
В отличие от некоторых других языков, Java не позволяет вам выбирать пошаговые значения или пошаговые ссылки
все аргументы передаются по значению.
Вызов метода может передавать два types of values
методу
Objects themselves cannot be passed to methods
. Когда метод изменяет параметр примитивного типа, изменения в параметре не влияют на исходное значение аргумента в вызывающем методе.
Это также относится к параметрам ссылочного типа. Если вы изменяете параметр ссылочного типа, чтобы он ссылался на другой объект, только параметр ссылается на новый объект - ссылка, хранящаяся в переменной вызывающих, по-прежнему относится к исходному объекту.
Ссылки: Java Как программировать (ранние объекты), десятое издание
Простая программа
import java.io.*;
class Aclass
{
public int a;
}
public class test
{
public static void foo_obj(Aclass obj)
{
obj.a=5;
}
public static void foo_int(int a)
{
a=3;
}
public static void main(String args[])
{
//test passing an object
Aclass ob = new Aclass();
ob.a=0;
foo_obj(ob);
System.out.println(ob.a);//prints 5
//test passing an integer
int i=0;
foo_int(i);
System.out.println(i);//prints 0
}
}
С точки зрения программиста C/С++, java использует pass by value, поэтому для примитивных типов данных (int, char и т.д.) изменения в функции не отражаются в вызывающей функции. Но когда вы передаете объект и в функции вы меняете своих членов данных или вызываете функции-члены, которые могут изменять состояние объекта, вызывающая функция получит изменения.
Самый короткий ответ:)
В С# это выполняется с помощью ключевых слов "out" и "ref".
Передача по ссылке: переменная передается таким образом, что переназначение внутри метода отражается даже вне метода.
Здесь следует пример передачи по ссылке (С#). Эта функция не существует в java.
class Example
{
static void InitArray(out int[] arr)
{
arr = new int[5] { 1, 2, 3, 4, 5 };
}
static void Main()
{
int[] someArray;
InitArray(out someArray);
// This is true !
boolean isTrue = (someArray[0] == 1);
}
}
См. также: Библиотека MSDN (С#): передача массивов по ссылке и из
См. также: Библиотека MSDN (С#): передача по значению и по ссылке
someArray = InitArray(someArray)
если у нас есть это: static int [] InitArray( int[] arr){ ... return ...}
int[] array1; int[] array2; InnitArrays(out array1, out array2);
при условии, что вы создаете метод static void InitArray(out int[] a1, out int[] a2){...}
Все передается по значению. Примитивы и ссылки на объекты. Но объекты могут быть изменены, если их интерфейс позволяет это.
Когда вы передаете объект методу, вы передаете ссылку, и объект может быть изменен реализацией метода.
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
Как следствие, "p" является ссылкой (указатель на объект) и не может быть изменен.
Примитивные типы передаются по значению. Ссылка на объект также может считаться примитивным типом.
Чтобы повторить, все передается по значению.
По-моему, "пройти по значению" - это ужасный способ синхронно описать два похожих, но разных события. Думаю, они должны были спросить меня в первую очередь.
С примитивами мы передаем действительное значение примитива в метод (или конструктор), будь то целое число "5", символ "c" или что у вас есть. Это фактическое значение затем становится его собственным местным примитивным. Но с объектами все, что мы делаем, дает тот же объект дополнительную ссылку (локальную ссылку), так что теперь у нас есть две ссылки, указывающие на один и тот же объект.
Надеюсь, это простое объяснение поможет.
Java передает параметры по значению, но для переменных объекта значения являются, по существу, ссылками на объекты. Поскольку массивы являются объектами, следующий код показывает разницу.
public static void dummyIncrease(int[] x, int y)
{
x[0]++;
y++;
}
public static void main(String[] args)
{
int[] arr = {3, 4, 5};
int b = 1;
dummyIncrease(arr, b);
// arr[0] is 4, but b is still 1
}
main()
arr +---+ +---+---+---+
| # | ----> | 3 | 4 | 5 |
+---+ +---+---+---+
b +---+ ^
| 1 | |
+---+ |
|
dummyIncrease() |
x +---+ |
| # | ------------+
+---+
y +---+
| 1 |
+---+
Я попытался упростить приведенные выше примеры, сохранив только суть проблемы. Позвольте мне представить это как историю, которую легко запомнить и применить правильно. История такова: У тебя есть собака, Джимми, чей хвост длиной 12 дюймов. Вы оставляете его у ветеринара в течение нескольких недель, пока вы едете за границу.
Ветеринар не любит длинный хвост Джимми, поэтому он хочет сократить его наполовину. Но, будучи хорошим ветеринаром, он знает, что он не имеет права калечить других собак. Поэтому он сначала делает клон собаки (с новым ключевым словом) и режет хвост клона. Когда собака наконец вернется к вам, у нее есть оригинальный 12-дюймовый хвост в такте. Счастливый конец!
В следующий раз, когда вы путешествуете, вы берете собаку, невольно, злобному ветеринару. Он также ненавидит длинные хвосты, поэтому он разрезает его на жалкие 2 дюйма. Но он делает это с вашим дорогим Джимми, а не с клоном. Когда вы вернетесь, вы потрясены, увидев, как Джимми трогательно помахал 2-дюймовым заглушкой.
Мораль истории: когда вы передаете своего питомца, вы отдаете целую и свободную опека над домашним животным ветеринару. Он волен играть с ним какой-то хаос. Передача по значению, по ссылке, указателем - это всего лишь технические споры. Если ветеринар сначала не клонирует его, он заканчивает калечить оригинальную собаку.
public class Doggie {
public static void main(String...args) {
System.out.println("At the owner home:");
Dog d = new Dog(12);
d.wag();
goodVet(d);
System.out.println("With the owner again:)");
d.wag();
badVet(d);
System.out.println("With the owner again(:");
d.wag();
}
public static void goodVet (Dog dog) {
System.out.println("At the good vet:");
dog.wag();
dog = new Dog(12); // create a clone
dog.cutTail(6); // cut the clone tail
dog.wag();
}
public static void badVet (Dog dog) {
System.out.println("At the bad vet:");
dog.wag();
dog.cutTail(2); // cut the original dog tail
dog.wag();
}
}
class Dog {
int tailLength;
public Dog(int originalLength) {
this.tailLength = originalLength;
}
public void cutTail (int newLength) {
this.tailLength = newLength;
}
public void wag() {
System.out.println("Wagging my " +tailLength +" inch tail");
}
}
Output:
At the owner home:
Wagging my 12 inch tail
At the good vet:
Wagging my 12 inch tail
Wagging my 6 inch tail
With the owner again:)
Wagging my 12 inch tail
At the bad vet:
Wagging my 12 inch tail
Wagging my 2 inch tail
With the owner again(:
Wagging my 2 inch tail
Понять это в 2 шага:
Вы не можете изменить ссылку на сам объект, но вы можете работать с этим переданным параметром в качестве ссылки на объект.
Если вы хотите изменить значение за ссылкой, вы будете объявлять новую переменную в стеке с тем же именем "d". Посмотрите на ссылки со знаком @
, и вы узнаете, что ссылка была изменена.
public static void foo(Dog d) {
d.Name = "belly";
System.out.println(d); //Reference: Dog@1540e19d
d = new Dog("wuffwuff");
System.out.println(d); //Dog@677327b6
}
public static void main(String[] args) throws Exception{
Dog lisa = new Dog("Lisa");
foo(lisa);
System.out.println(lisa.Name); //belly
}
Г-н @Скотт Стейнфилд написал отличный ответ. Вот класс, который вы проверили бы что он имел в виду:
public class Dog {
String dog ;
static int x_static;
int y_not_static;
public String getName()
{
return this.dog;
}
public Dog(String dog)
{
this.dog = dog;
}
public void setName(String name)
{
this.dog = name;
}
public static void foo(Dog someDog)
{
x_static = 1;
// y_not_static = 2; // not possible !!
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
public static void main(String args[])
{
Dog myDog = new Dog("Rover");
foo(myDog);
System.out.println(myDog.getName());
}
}
Итак, мы переходим от main() к собаке с именем Rover
, затем присваиваем новый адрес указателю, который мы проходили, но в конце имя собаки не Rover
, а не Fifi
и, конечно, не Rowlf
, но Max
.
Java всегда имеет значение pass-by-value, параметры - это копии переданных переменных, все объекты определяются с помощью ссылки, а reference - это переменная, которая хранит адрес памяти, в котором находится объект.
Проверьте комментарии, чтобы понять, что происходит при выполнении; следуйте номерам, пока они показывают ход выполнения.
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());
}
}
Кажется, что все вызывается по значению в java, поскольку я попытался понять следующую программу
Класс-S
class S{
String name="alam";
public void setName(String n){
this.name=n;
}}
Класс-образец
public class Sample{
public static void main(String args[]){
S s=new S();
S t=new S();
System.out.println(s.name);
System.out.println(t.name);
t.setName("taleev");
System.out.println(t.name);
System.out.println(s.name);
s.setName("Harry");
System.out.println(t.name);
System.out.println(s.name);
}}
Выход
Alam
Alam
taleev
Alam
taleev
Юарры
Как мы определили класс S с именем переменной экземпляра со значением taleev, поэтому для все объекты, которые мы инициализируем из него, будут иметь переменную name со значением taleev, но если мы изменим значение имени любого объекта, то оно изменит имя только этой копии класса (Object) не для каждого класса, поэтому после этого, когда мы делаем System.out.println(s.name), он печатает taleev, мы не можем изменить имя, которое мы имеем и значение, которое мы изменяем, - это значение объекта, а не значение переменной экземпляра, поэтому, когда мы определяем переменную экземпляра, мы не можем ее изменить.
Итак, я так думаю, что это показывает, что java имеет дело с значениями только с ссылками
Распределение памяти для примитивных переменных можно понять с помощью this
Java манипулирует объектами по ссылке, и все переменные объекта являются ссылками. Однако Java не передает аргументы метода по ссылке; он передает их по значению.
Возьмите метод badSwap(), например:
public void badSwap(int var1, int
var2{ int temp = var1; var1 = var2; var2 =
temp; }
Когда функция badSwap() вернется, переменные, переданные как аргументы, по-прежнему будут иметь свои исходные значения. Метод также будет терпеть неудачу, если мы изменим тип аргументов от int до Object, так как Java также передает ссылки на объекты по значению. Теперь вот где это становится сложно:
public void tricky(Point arg1, Point arg2)
{ arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; }
public static void main(String [] args) {
Point pnt1 = new Point(0,0); Point pnt2
= new Point(0,0); System.out.println("X:
" + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y:
" +pnt2.y); System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); }
Если мы выполним этот метод main(), мы увидим следующий вывод:
X: 0 Y: 0 X: 0 Y: 0 X: 100 Y: 100 X: 0 Y: 0
Метод успешно изменяет значение pnt1, хотя он передается по значению; однако, смена pnt1 и pnt2 терпит неудачу! Это основной источник путаницы. В методе themain() pnt1 и pnt2 являются не более чем объектными ссылками. Когда вы passpnt1 и pnt2 для метода tricky(), Java передает ссылки по значению так же, как и любой другой параметр. Это означает, что ссылки, переданные методу, фактически являются копиями исходных ссылок. На рисунке 1 ниже показаны две ссылки, указывающие на один и тот же объект после того, как Java передает объект методу.
Java копирует и передает ссылку по значению, а не объекту. Таким образом, манипуляция с методом будет изменять объекты, поскольку ссылки указывают на исходные объекты. Но поскольку ссылки являются копиями, свопы сбой. Как показано на рисунке 2, метод ссылается на swap, но не на исходные ссылки. К сожалению, после вызова метода у вас остались только исходные ссылки, которые не были отпущены. Для того, чтобы своп мог преуспеть за пределами вызова метода, нам нужно поменять исходные ссылки, а не копии.
Много путаницы вокруг этой проблемы возникает из-за того, что Java попыталась переопределить значение "Pass by value" и "Pass by reference". Важно понимать, что это "Отраслевые условия" и не могут быть правильно поняты вне этого контекста. Они предназначены для того, чтобы помочь вам, как вы кодируете и ценны для понимания, поэтому пусть сначала перейдет то, что они означают.
Хорошее описание обоих можно найти здесь.
Передача по значению Значение, полученное функцией, является копией объекта, который использует вызывающий. Он полностью уникален для функции, и все, что вы делаете с этим объектом, будет видно только внутри функции.
Передача по ссылке Значение, полученное функцией, является ссылкой на объект, который использует вызывающий. Все, что функция делает с объектом, к которому относится значение, будет видно вызывающей стороне, и она будет работать с этими изменениями с этой точки.
Как видно из этих определений, факт, что ссылка передается по значению, не имеет значения. Если бы мы согласились с этим определением, то эти термины станут бессмысленными, и все языки во всем мире будут проходить только по значению.
Независимо от того, как вы передаете ссылку, она может быть передана только по значению. Дело не в этом. Дело в том, что вы передали ссылку на свой собственный объект на функцию, а не на ее копию. Тот факт, что вы можете выбросить ссылку, которую вы получили, не имеет значения. Опять же, если мы приняли это определение, эти термины становятся бессмысленными, и каждый всегда проходит по значению.
И нет, С++ специальный синтаксис "передать по ссылке" не является исключительным определением pass by reference. Это просто синтаксис удобства, который должен сделать так, что вам не нужно использовать синтаксис указателя после передачи указателя. Он все еще передает указатель, компилятор просто скрывает этот факт от вас. Он также по-прежнему передает этот указатель BY VALUE, компилятор просто скрывает это от вас.
Итак, с этим пониманием, мы можем смотреть на Java и видеть, что на самом деле есть и то, и другое. Все примитивные типы Java всегда передаются по значению, потому что вы получаете копию объекта вызывающего объекта и не можете изменять свою копию. Все ссылочные типы Java всегда передаются по ссылке, потому что вы получаете ссылку на объект-вызывающий объект и можете напрямую изменять свой объект.
Тот факт, что вы не можете изменить ссылку вызывающего абонента, не имеет ничего общего с передачей по ссылке и является истинным на каждом языке, который поддерживает передачу по ссылке.
Java передает все по значению!
//создаем объект, передавая имя и возраст:
PersonClass variable1 = new PersonClass("Mary", 32);
PersonClass variable2;
//И переменная2, и переменная1 теперь ссылаются на один и тот же объект
variable2 = variable1;
PersonClass variable3 = new PersonClass("Andre", 45);
//variable1 теперь указывает на variable3
variable1 = variable3;
//ЧТО ВЫХОДИТ ЭТО?
System.out.println(variable2);
System.out.println(variable1);
Mary 32
Andre 45
если вы могли бы понять этот пример, мы сделали. в противном случае, пожалуйста, посетите эту веб-страницу для подробного объяснения:
В ссылке для Java используется обходной путь. Позвольте мне объяснить в этом примере:
public class Yo {
public static void foo(int x){
System.out.println(x); //out 2
x = x+2;
System.out.println(x); // out 4
}
public static void foo(int[] x){
System.out.println(x[0]); //1
x[0] = x[0]+2;
System.out.println(x[0]); //3
}
public static void main(String[] args) {
int t = 2;
foo(t);
System.out.println(t); // out 2 (t did not change in foo)
int[] tab = new int[]{1};
foo(tab);
System.out.println(tab[0]); // out 3 (tab[0] did change in foo)
}}
Надеюсь, это поможет!
Нижняя строка для пропущенного значения: вызываемый метод не может изменить вызывающий переменная, хотя для ссылочных переменных объекта вызываемый метод может изменить объект, на который ссылается переменная. Какая разница между изменением переменной и изменение объекта? Для ссылок на объекты это означает, что вызываемый метод не может переназначить исходную ссылочную переменную вызывающего и сделать ее ссылкой на другой объект, или null.
Я взял этот код и объяснение из книги по сертификации Java и внес некоторые незначительные изменения.
Я думаю, это
хорошая иллюстрация к переходу по значению объекта. В приведенном ниже коде,
переназначение g не переназначает f! В конце метода bar() два объекта Foo
были созданы, причем одна из них ссылается на локальную переменную f и одну, на которую ссылается
локальная (аргументная) переменная g.
Поскольку метод doStuff() имеет копию ссылочной переменной, у него есть способ получить к исходному объекту Foo, например, для вызова метод setName(). Но метод doStuff() не имеет возможности добраться до контрольная переменная f. Поэтому doStuff() может изменять значения внутри объекта f to, но doStuff() не может изменить фактическое содержимое (битовая диаграмма) f. В других слова, doStuff() может изменять состояние объекта, к которому относится f, но он не может сделайте f ссылкой на другой объект!
package test.abc;
public class TestObject {
/**
* @param args
*/
public static void main(String[] args) {
bar();
}
static void bar() {
Foo f = new Foo();
System.out.println("Object reference for f: " + f);
f.setName("James");
doStuff(f);
System.out.println(f.getName());
//Can change the state of an object variable in f, but can't change the object reference for f.
//You still have 2 foo objects.
System.out.println("Object reference for f: " + f);
}
static void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
System.out.println("Object reference for g: " + g);
}
}
package test.abc;
public class Foo {
public String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Обратите внимание, что ссылка на объект не изменилась на выходе консоли ниже:
Консольный выход:
Ссылка на объект для f: test.abc.Foo@62f72617
Ссылка на объект для g: test.abc.Foo@4fe5e2c3
Boo Ссылка на объект для f: test.abc.Foo@62f72617
Java передается только по значению. нет пропусков по ссылке, например, вы можете увидеть следующий пример.
package com.asok.cop.example.task;
public class Example {
int data = 50;
void change(int data) {
data = data + 100;// changes will be in the local variable
System.out.println("after add " + data);
}
public static void main(String args[]) {
Example op = new Example();
System.out.println("before change " + op.data);
op.change(500);
System.out.println("after change " + op.data);
}
}
Вывод:
before change 50
after add 600
after change 50
void changeName(Person person, String newName){ person.name = newName; }
который действительно изменяет исходный объект. Если бы это было действительно передачей по значению, разве мы не ожидали бы, что данный аргумент person
будет копией исходного объекта?
void changePerson(Person person){ person = new Person(); }
ссылка вызывающего абонента на объект person останется неизменной. Сами объекты передаются по значению, но изменения могут повлиять на их членов. Чтобы быть истинной передачей по ссылке, мы должны были бы иметь возможность переназначить аргумент новому объекту и отразить изменение в вызывающей стороне.
Java манипулирует объектами по ссылке, и все переменные объекта являются ссылками. Однако Java не передает аргументы метода по ссылке; он передает их по значению.
Я сделал эту маленькую диаграмму, которая показывает, как данные создаются и передаются
Примечание. Примитивные значения передаются как значения, первой ссылкой на это значение является аргумент метода.
Это означает:
myObject
внутри функцииmyObject
, внутри функции, потому что point
не является myObject
point
и myObject
являются ссылками, разными ссылками, однако эти ссылки указывают на одну и ту же new Point(0,0)
Когда C передает структуру данных по ссылке, указатель на структуру данных существует где-то, но он неизменен. В коде
void foo(Struct& x)
x всегда означает ту же ячейку памяти внутри метода. Он устанавливается, когда вызывается метод, и вы не можете заставить его указывать на какой-либо другой объект Struct где-то. В Java аналогом передается ссылка объекта в качестве конечного параметра.
void foo(final Struct x)
Присвоение значения непосредственно x будет ошибкой. Java не может просто скопировать все поля одной структуры в другую при назначении.
Прохождение регулярного параметра (без окончательного) - это некая странная конструкция для C, но ближайшая конструкция может быть
void foo(Struct *x)
Здесь x - указатель, и, хотя вы можете использовать его для изменения структуры, сам указатель передается по значению (вы можете присвоить ему другое значение, это изменение не будет распространяться обратно на вызывающий код). Вам также нужен другой синтаксис для доступа к полям структуры (*x).field
или x->field
, а не x.field
, как в Java, но это всего лишь подробная информация о языке.
Из-за этого программист C, который видит ссылку Java как указатель, обычно говорит, что этот указатель был передан по значению.
В СООТВЕТСТВИИ С ТЕРМИНОЛОГИЕЙ С++:
(Хотя Java полностью передается по значению, во втором случае он передает ссылку на объект, и в этом случае значение объекта, если оно изменено, отражается в основной функции, и поэтому я назвал его "Передача по ссылке" в соответствии с Терминология С++.) Если вы родом из С++, тогда Java передается по значению для примитивных типов и их классов Wrapper, таких как int, Integer, bool, Boolean, т.е., Если вы передадите значение этих типов данных, в исходной функции изменений не будет. Для всех других типов данных java просто передает их, и если какие-либо изменения сделаны, это изменение можно увидеть в исходной функции (его можно назвать pass by reference в соответствии с терминологией С++)
Язык программирования Java передает аргументы только по значению, т.е. вы не можете изменить значение аргумента в вызывающем методе изнутри вызываемый метод.
Однако, когда экземпляр объекта передается в качестве аргумента в метод, значение аргумента не является самим объектом, а ссылкой на объект. Вы можете изменить содержимое объекта в вызываемом методе, но а не ссылку на объект.
Для многих людей это выглядит как пошаговая ссылка, и поведенчески много общего с передачей по ссылке. Однако есть две причины это неточно.
Во-первых, способность изменять вещь передавалась метод применяется только к объектам, а не к примитивным значениям.
Во-вторых, фактический значение, связанное с переменной типа объекта, является ссылкой на объект, а не сам объект. Это важное различие в других путей и, если они будут понятны, полностью поддерживает тот факт, что язык программирования Java передает аргументы по значению.
The following code example illustrates this point:
1 public class PassTest {
2
3 // Methods to change the current values
4 public static void changeInt(int value) {
5 value = 55;
6 }
7 public static void changeObjectRef(MyDate ref) {
8 ref = new MyDate(1, 1, 2000);
9 }
10 public static void changeObjectAttr(MyDate ref) {
11 ref.setDay(4);
12 }
13
14 public static void main(String args[]) {
15 MyDate date;
16 int val;
17
18 // Assign the int
19 val = 11;
20 // Try to change it
21 changeInt(val);
22 // What is the current value?
23 System.out.println("Int value is: " + val);
24
25 // Assign the date
26 date = new MyDate(22, 7, 1964);
27 // Try to change it
28 changeObjectRef(date);
29 // What is the current value?
30 System.out.println("MyDate: " + date);
31
32 // Now change the day attribute
33 // through the object reference
34 changeObjectAttr(date);
35 // What is the current value?
36 System.out.println("MyDate: " + date);
37 }
38 }
This code outputs the following:
java PassTest
Int value is: 11
MyDate: 22-7-1964
MyDate: 4-7-1964
The MyDate object is not changed by the changeObjectRef method;
however, the changeObjectAttr method changes the day attribute of the
MyDate object.
Пожалуйста, перейдите по этой ссылке видео. Если ваши сомнения не проясняются, вы можете прийти и понизить. У меня было такое же замешательство, я много гуглял и где-то узнал эту ссылку. Который сделал все очень ясно.
Уже есть отличные ответы, которые охватывают это. Я хотел внести небольшой вклад, поделившись очень простым примером (который будет скомпилирован), сравнивающим поведение между передачей по ссылке в c++ и передачей по значению в Java.
Несколько моментов:
c++ пройти по ссылочному примеру:
using namespace std;
#include <iostream>
void change (char *&str){ // the '&' makes this a reference parameter
str = NULL;
}
int main()
{
char *str = "not Null";
change(str);
cout<<"str is " << str; // ==>str is <null>
}
Java передает "Java-ссылку" на примере значения
public class ValueDemo{
public void change (String str){
str = null;
}
public static void main(String []args){
ValueDemo vd = new ValueDemo();
String str = "not null";
vd.change(str);
System.out.println("str is " + str); // ==> str is not null
}
}
И последнее замечание: каждый раз, когда я сталкиваюсь с этим вопросом по stackoverflow, меня бесит бесконечно, что на этот вопрос нет единого ответа, как будто это не решенный вопрос. Pass-by-reference и Pass-by-value - это стандартные отраслевые термины, которые имеют определенные значения. В этой теме есть несколько приемлемых ответов, которые четко объясняют, почему Java является передачей по значению. Пользователь, который разместил этот вопрос, ничего не опубликовал в stackoverflow с 3 сентября 2008 года и, вероятно, никогда не выберет приемлемый ответ. Как бы сильно я не любил stackoverflow, я вижу в этом огромную слабость.
Просто: Java передается по копиям.
Если вы передаете сложный объект (как собака, как, по-видимому, это обычный пример), это то же самое, что передать ссылку. Если вы передадите поле сложного объекта (например, имя собаки), то оно совпадает с передачей значения (изменение его не изменит собаку/родительское поле).
Обратите внимание, что Объект, который не является "сложным" (не похожим на нашу собаку), как Integer или String, передается по значению. Поэтому, даже если они являются объектами со всем, что вы можете сделать на них, они не могут быть модифицированы внутри метода... Да, это просто разобрано и не имеет никакого смысла...
Пример (не собака):
public class HelloWorld {
private Integer i;
private String s;
private Boolean b;
public static void main(String[] args) {
HelloWorld h = new HelloWorld();
h.s = "Bill";
h.ModMe(h.s);
h.i = 2;
h.ModMe(h.i);
h.b = true;
h.ModMe(h.b);
System.out.println(h.s + " " + h.i + " " + h.b);
h.ModMe(h);
System.out.println(h.s + " " + h.i + " " + h.b);
String test = "TEST";
h.ModMe(test);
System.out.println(test);
}
public void ModMe(Object o) {
if (o instanceof HelloWorld) {
((HelloWorld) o).i = (int) Math.pow(((HelloWorld) o).i, 2);
((HelloWorld) o).b = !((HelloWorld) o).b;
((HelloWorld) o).s = ((HelloWorld) o).s.concat(" modded successfully");
} else if (o instanceof Integer) {
o = (Integer) o + (Integer) o;
} else if (o instanceof String) {
o = ((String) o).concat(" is modified.");
} else if (o instanceof Boolean) {
o = !(Boolean) o;
}
}
}
Если вы запустите, что объект h будет изменен, если вы используете весь объект в качестве аргумента, но не поля si или b, если вы используете их только как аргумент. если вы отлаживаете это, вы заметите, что для полей идентификатор объекта будет меняться, как только вы запустите строку, где значение будет изменено. Таким образом, он будет делать то же самое, что и "new Integer", "new String" и "new Boolean" автоматически.
В java фактически мы передаем аргументы как значение, ссылочный объект. Из-за этого срока некоторые люди вводят в заблуждение, что Java строго передается по ссылке.
В случае примитивных типов мы передаем только значения.
Но в случае объектов класса мы передаем копию адреса ссылочного объекта. Следовательно, любые изменения, внесенные нами в значение этого дублирующего адреса исходной ссылки, повлияют на всю программу.
Java передает ссылки на объекты по значению.
Итак, если какая-либо модификация будет сделана для объекта, к которому указывает опорный аргумент, он будет отражен обратно на исходный объект.
Но если ссылочный аргумент указывает на другой объект, то исходная ссылка укажет на исходный объект.
class Codechef
{
public static void main (String[] args) throws java.lang.Exception
{
// your code goes here
Person p = new Person();
p.age = 30;
System.out.println(p.age); //30
ss(p); //40
System.out.println(p.age); //40
yy(p); //20
System.out.println(p.age); //40
}
static void ss(Person p) {
// Change the value in object then it gets retained.
p.age = 40;
System.out.println(p.age);
}
static void yy(Person p) {
// Change the object itself it don't retain. So its pass by value
p = new Person();
p.age = 20;
System.out.println(p.age);
}
}
class Person {
int age = 10;
}
О/р
30 40 40 20 40
Таким образом, он всегда передается по значению, но если переменные экземпляра объекта изменяются, они сохраняются. Если сам объект изменен, java создает новый объект, чтобы он не сохранялся.
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Murat");
System.out.println(person);
passby(person);
System.out.println(person);
}
public static void passby(Person a){
//a= new Person(); //if you open this line you will see that reference value change so main reference not change
a.setName("Kemal");
System.out.println(a);
}
private static class Person {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return getName();
}
}
}
Мышление таким образом просто заставляет вещи звучать все более и более сложными. Просто думает, что ключевое слово new
создает целый новый объект, поэтому, когда вы передаете объект из переменной в другую переменную без ключевого слова new
, вы передаете один и тот же объект.
Предположим, что оператор = означает слово "есть" на английском языке:
Pokemon a = new Pokemon(); // Pokemon a is a new Pokemon
Pokemon b = a; //Pokemon b is pokemon a
Итак, если вы внесли какие-либо изменения в сторону b, влияет на a, так как b является a или вы можете считать b как псевдоним для.
Посмотрим, что произойдет, когда мы изменим операнды на примитивные переменные, такие как int, float и т.д.:
int x = 1; // x is 1
int y = x; // y is 1
y = 5 // y is 5
x является псевдонимом для 1, когда я говорю "x", это означает 1. Итак, изменения в y не будут влиять на x...
Другая аналогия:
Предположим, что Томми Спейд, Деннис Серд и Джеки Даймонд - друзья. Томми называет Джеки "Джеком", а Деннис называют Джеки "Диа". Пусть думают, что Джек и Ди - переменные. Независимо от того, Джеки и Дия - это одна и та же сущность (объект). Когда Томми ударил по лицу Джека, Деннис тоже может видеть синяк на лице Диа, так как Джек и Дия - это тот же [человек].
Примитивные значения просто примитивны. Он не имеет компонента, просто значения, поэтому вышеупомянутая аналогия не работает с данным типом данных.
Одним из основных понятий ООП является абстракция. Вам не нужно знать подробные сведения о том, как устройство работает для его использования, вам нужно только знать, как его использовать, а не как это работает. Если вы не хотите быть исследователем, все в порядке. Надеюсь, мой ответ поможет.
Короткий ответ. Java всегда pass-by-value. Когда мы говорим о передаче объектов, Java передает копию ссылки на "реальный" объект
public static void main(String args[]) {
Point p1 = new Point(1, 1); //This is pointing to a location in memory let say that the reference is x1=5
Point p2 = new Point(2, 2); //This is pointing to a location in memory let say that the reference is x2=6
// Next we need to pass something to the function, what is it and how we pass it?
// There are two references x1,x2 are going to be passed by value
// Meaning that if x1=5, x2=6 as we mentioned, we are passing 5,6. We are not passing x1,x2
// That a big difference. If we are passing x1 and x2, we can change values there inside the trickMe method.
// but we are passing only two numbers which are 5 and 6.
trickMe(p1, p2);
System.out.println(p1); // outputs java.awt.Point[x=1,y=1]
System.out.println(p2); // outputs java.awt.Point[x=2,y=2]
}
// Here we received two values which are 5,6.
// This method will create another two references which are x3,x4 for objects arg3,arg4
// arg3 and arg4 are actually same exact objects as p1,p2 with the same memory location.
// BUT we use x1,x2 to access p1,p2
// AND we use x3,x4 to access arg3,arg4
// x3 is corresponding to x1 and each one of them equal 5. Each one of them means that they are not the same obviously, but have equal values.
// x4 is corresponding to x2 and each one of them equal 6.
// Beautiful, now if we replace x3 with x4,, does this replace x1 with x2?
// NO THEY ARE TOTALLY INDEPENDENT. x3 and x4 are inside the trickMe Method. they have no acces to x1,x2 at all.
// So, no matter what I did to x3,x4 inside the trickMe method, x1 and x2 in the main method will not change at all.
// They will remain x1=5 and x2=6
public static void trickMe(Point arg3, Point arg4) {
// Now we have a reference to arg3 which is x3 and it is equal to 5 and a reference to arg4 which is x4 and equal to 6, right?
// Great! When we say arg3=arg4, we means that make the reference of arg3 which is x3 equals to the reference of arg4 which is x4.
// Meaning, from this point,, we will make x3=6 instead of 5 and of course x4 is still 6 as it was.
// We didn't touch niether x1 nor x2, we also can't thouch them inside trickMe method.
// We only have access to x3 and x4.
// That why swapping methods always fail in java if we just put the equal sign "=" to swap.
// We really have two memory locations which represents two, but with 4 different refernces.
// Two are in the main method, and another two are in this method
arg3 = arg4;
}
Очень просто! Переменная будет передана по значению, если она является примитивом, и по ссылке, если она является объектом.
Все это
В JAVA все собственные типы передаются только по значению. И все остальные типы, включая объекты, классы Wrapper и т.д., Проходят по ссылке.
С родными типами вы также можете использовать те же функции, что и pass by reference, путем преобразования переменных --- > переменных класса (статические).
TL; DR: Минимальный пример:
package foobar;
import java.util.ArrayList;
public class FooBar {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>(); // An object.
list1.add(1);
ArrayList<Integer> list2 = new ArrayList<>(); // Another object.
list2.add(2);
int x = 42; // A primitive (same with Integer).
sideEffects(list1, list2, x);
System.out.println(list1); // Output: [1] (unchanged)
System.out.println(list2); // Output: [2, 3] (changed !)
System.out.println(x); // Output: 42 (not changed)
}
private static void sideEffects(ArrayList<Integer> list1, ArrayList<Integer> list2, int x) {
list1 = list2;
list1.add(3);
x = 21;
}
}
Чтобы упростить задачу:
addr - адрес ячейки памяти или ссылка.
Java pass-by-reference, потому что:
a; // => addrA(valueA);
function (a) {// => newAddrA(addrA(valueA));
// change "a in function" affects the change of the value "a outside function"
// because the newAddrA points to addrA with valueA.
b; // => addrB(valueB)
a = b; // => newAddrA(addrB(valueB));
// change "a in function" does not affect the value of "a outside function"
// because now newAddrA points to addrB with valueB.
}
С++ pass-by-value, потому что:
a; // => addrA(valueA);
function (a) {// => newAddrA(valueCloneA);
// change "a in function" does not affect the value of "a outside function"
// outside the function, because the newAddrA with new value valueCloneA;
b; // => addrB(valueB);
a = b; // => newAddrA(addrB(valueB));
}
Вот пример прохождения по ссылке:
Test t=new Test();
Test x=t;
оба x
и t
относятся к тому же объекту Test
.
В отличие от этого, это показывает переход по значению:
int i=0;
int j=i;
i++;
//i=1;
//j=0;
Когда вы используете new
, вы создаете объект ref, но когда вы используете var, например int
, float
или boolean
, он будет передан по значению.
call-by-value
иcall-by-reference
отличается очень важным образом . (Лично я предпочитаю использовать совместное использованиеcall-by-object-sharing
время поcall-by-value[-of-the-reference]
, так как это описывает семантику на высоком уровне и не создает конфликта сcall-by-value
, которое является базовой реализацией.)