Является ли Java "передачей по ссылке" или "передачей по значению"?
Я всегда думал, что Java была передачей по ссылке.
Тем не менее, я видел пару постов в блоге (например, этот блог), которые утверждают, что это не так.
Я не думаю, что понимаю разницу, которую они проводят.
Какое объяснение?
111 ответов
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 говорит, что все в Java передается по значению. В Java нет такого понятия, как "передача по ссылке".
Ключом к пониманию этого является то, что
Dog myDog;
не собака; это на самом деле указатель на собаку.
Что это значит, когда у вас есть
Dog myDog = new Dog("Rover");
foo(myDog);
вы по существу передаете адрес созданного Dog
возражать против foo
метод.
(Я говорю по сути, потому что указатели Java не являются прямыми адресами, но проще всего так думать о них)
Предположим, Dog
объект находится по адресу памяти 42. Это означает, что мы передаем 42 методу.
если метод был определен как
public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
давайте посмотрим на то, что происходит.
- параметр
someDog
устанавливается на значение 42 - на линии "ААА"
someDog
сопровождаетсяDog
это указывает наDog
объект по адресу 42)- тот
Dog
(тот, кто по адресу 42) попросил сменить имя на Макс
- на линии "ВВВ"
- новый
Dog
создано. Допустим, он по адресу 74 - мы назначаем параметр
someDog
до 74
- новый
- на линии "CCC"
- SomeDog следует за
Dog
это указывает наDog
объект по адресу 74) - тот
Dog
(тот, кто по адресу 74) попросил сменить имя на Rowlf
- SomeDog следует за
- тогда мы вернемся
Теперь давайте подумаем о том, что происходит вне метода:
Сделал myDog
менять?
Там ключ.
Помня, что myDog
это указатель, а не фактический Dog
, ответ - нет. myDog
по-прежнему имеет значение 42; это все еще указывает на оригинал Dog
(но обратите внимание, что из-за строки "AAA" ее имя теперь "Макс" - все тот же Dog; myDog
Значение не изменилось.)
Совершенно верно следовать за адресом и изменять то, что в конце этого; однако это не меняет переменную.
Java работает точно так же, как C. Вы можете назначить указатель, передать указатель методу, следовать указателю в методе и изменить данные, на которые он указывал. Тем не менее, вы не можете изменить, где указатель указывает.
В C++, Ada, Pascal и других языках, которые поддерживают передачу по ссылке, вы действительно можете изменить переданную переменную.
Если у Java была семантика передачи по ссылке, foo
метод, который мы определили выше, изменился бы где myDog
указывал, когда он назначил someDog
на линии ВВВ.
Представьте, что ссылочные параметры являются псевдонимами для передаваемой переменной. Когда этот псевдоним назначен, переменная также передается.
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:)
Java всегда передается по значению, без исключений, никогда.
Так почему же это может смущать любого, кто верит, что Java передается по ссылке, или думает, что у него есть пример того, как Java действует как передача по ссылке? Ключевым моментом является то, что Java ни при каких обстоятельствах никогда не обеспечивает прямой доступ к значениям самих объектов. Единственный доступ к объектам - через ссылку на этот объект. Поскольку доступ к объектам Java всегда осуществляется через ссылку, а не напрямую, обычно говорят о полях, переменных и аргументах методов как об объектах, когда педантично они являются только ссылками на объекты. Путаница проистекает из этого (строго говоря, неверного) изменения в номенклатуре.
Итак, при вызове метода
- Для примитивных аргументов (
int
,long
и т. д.), передача по значению является фактическим значением примитива (например, 3). - Для объектов передача по значению является значением ссылки на объект.
Так что если у вас есть doSomething(foo)
а также public void doSomething(Foo foo) { .. }
два Foos скопировали ссылки, которые указывают на одни и те же объекты.
Естественно, передача по значению ссылки на объект очень похожа (и практически не различима на практике) на передачу объекта по ссылке.
Это даст вам некоторое представление о том, как на самом деле работает Java, до такой степени, что в вашем следующем обсуждении передачи Java по ссылке или по значению вы просто улыбнетесь:-)
Шаг первый, пожалуйста, удалите из памяти это слово, которое начинается с 'p' "_ _ _ _ _ _ _", особенно если вы пришли из других языков программирования. Java и 'p' не могут быть записаны в одной книге, на форуме или даже в txt.
Шаг второй Помните, что когда вы передаете Object в метод, вы передаете ссылку на Object, а не сам объект.
- Ученик: Магистр, означает ли это, что Java передается по ссылке?
- Мастер: Кузнечик, №
Теперь подумайте, что ссылка / переменная объекта делает / является:
- Переменная содержит биты, которые сообщают JVM, как добраться до указанного объекта в памяти (Heap).
- При передаче аргументов методу вы НЕ передаете ссылочную переменную, а копируете биты в ссылочной переменной. Как то так: 3bad086a. 3bad086a представляет способ добраться до пропущенного объекта.
- Таким образом, вы просто передаете 3bad086a, что это значение ссылки.
- Вы передаете значение ссылки, а не саму ссылку (и не объект).
- Это значение фактически копируется и передается методу.
В следующем (пожалуйста, не пытайтесь скомпилировать / выполнить это...):
1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7. anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }
Что просходит?
- Переменная person создается в строке #1 и в начале имеет значение null.
- Новый объект Person создается в строке #2, сохраняется в памяти, а переменной person дается ссылка на объект Person. То есть его адрес. Допустим, 3bad086a.
- Переменная person, содержащая адрес объекта, передается функции в строке #3.
- В строке № 4 вы можете прослушать звук тишины
- Проверьте комментарий в строке № 5
- Локальная переменная метода - anotherReferenceToTheSamePersonObject - создается, а затем в строке #6 появляется магия:
- Переменная / эталонное лицо копируется побитно и передается в anotherReferenceToTheSamePersonObject внутри функции.
- Новые экземпляры Person не создаются.
- И " person ", и " anotherReferenceToTheSamePersonObject " содержат одно и то же значение 3bad086a.
- Не пытайтесь это сделать, но person==anotherReferenceToTheSamePersonObject будет истинным.
- Обе переменные имеют ИДЕНТИЧНЫЕ КОПИИ ссылки, и они обе ссылаются на один и тот же объект Person, ОДИН ЖЕ объект в куче и НЕ КОПИЮ.
Одна картинка стоит тысячи слов:
Обратите внимание, что стрелки anotherReferenceToTheSamePersonObject направлены к объекту, а не к переменной person!
Если вы этого не получили, просто поверьте мне и помните, что лучше сказать, что Java передается по значению. Ну, перейдите по ссылке. Ну что ж, еще лучше передать значение переменной! ;)
Теперь не стесняйтесь ненавидеть меня, но обратите внимание, что с учетом этого нет никакой разницы между передачей примитивных типов данных и объектов при обсуждении аргументов метода.
Вы всегда передаете копию битов значения ссылки!
- Если это тип данных примитива, эти биты будут содержать значение самого типа данных примитива.
- Если это объект, биты будут содержать значение адреса, который сообщает JVM, как добраться до объекта.
Java передается по значению, потому что внутри метода вы можете изменять ссылочный объект столько раз, сколько захотите, но как бы вы ни старались, вы никогда не сможете изменить переданную переменную, которая будет продолжать ссылаться (не p _ _ _) _ _ _ _) один и тот же объект, несмотря ни на что!
Приведенная выше функция changeName никогда не сможет изменить фактическое содержимое (значения битов) переданной ссылки. Другими словами, changeName не может заставить человека ссылаться на другой объект.
Конечно, вы можете сократить его и просто сказать, что Java передается по значению!
Java передает ссылки по значению.
Таким образом, вы не можете изменить ссылку, которая будет передана.
Я чувствую, что спор о "передача по ссылке против передачи по значению" не является супер-полезным.
Если вы говорите: "Java передается по-любому (ссылка / значение)", в любом случае вы не дадите полного ответа. Вот некоторая дополнительная информация, которая, надеюсь, поможет понять, что происходит в памяти.
Ускоренный курс по стеку / куче, прежде чем мы перейдем к реализации 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
, поскольку адрес второй строки такой же, как и первая, новая строка не создается и не выделяется новое пространство кучи, но в стеке создается новый идентификатор. Тогда мы называем shout()
: создается новый кадр стека и новый идентификатор, name
создается и присваивается адрес уже существующей строки.
Итак, ценность, ссылка? Вы говорите "картофель".
По сути, переназначение параметров объекта не влияет на аргумент, например,
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
,
Чтобы показать контраст, сравните следующие фрагменты C++ и Java:
В C++: Примечание: плохой код - утечки памяти! Но это демонстрирует суть.
void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
val = 7; // Modifies the copy
ref = 7; // Modifies the original variable
obj.SetName("obj"); // Modifies the copy of Dog passed
objRef.SetName("objRef"); // Modifies the original Dog passed
objPtr->SetName("objPtr"); // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to
// by the original pointer passed.
objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}
int main()
{
int a = 0;
int b = 0;
Dog d0 = Dog("d0");
Dog d1 = Dog("d1");
Dog *d2 = new Dog("d2");
Dog *d3 = new Dog("d3");
cppMethod(a, b, d0, d1, d2, d3);
// a is still set to 0
// b is now set to 7
// d0 still have name "d0"
// d1 now has name "objRef"
// d2 now has name "objPtr"
// d3 now has name "newObjPtrRef"
}
В Java
public static void javaMethod(int val, Dog objPtr)
{
val = 7; // Modifies the copy
objPtr.SetName("objPtr") // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
}
public static void main()
{
int a = 0;
Dog d0 = new Dog("d0");
javaMethod(a, d0);
// a is still set to 0
// d0 now has name "objPtr"
}
Java имеет только два типа передачи: по значению для встроенных типов и по значению указателя для типов объектов.
Я не могу поверить, что никто еще не упомянул Барбару Лисков. Когда она разработала CLU в 1974 году, она столкнулась с той же проблемой терминологии и изобрела термин вызов путем разделения (также известный как вызов посредством совместного использования объекта и вызов по объекту) для этого конкретного случая "вызов по значению, где значение равно ссылка".
Суть дела в том, что слово " ссылка" в выражении "передача по ссылке" означает нечто совершенно отличное от обычного значения слова " ссылка" в Java.
Обычно в Java ссылка означает ссылку на объект. Но технические термины, передаваемые по ссылке / значению из теории языка программирования, говорят о ссылке на ячейку памяти, содержащую переменную, что является чем-то совершенно другим.
Уже есть отличные ответы, которые охватывают это. Я хотел внести небольшой вклад, поделившись очень простым примером (который будет скомпилирован), сравнивающим поведение между передачей по ссылке в C++ и передачей по значению в Java.
Несколько моментов:
- Термин ссылка перегружен. В Java это просто означает указатель. В термине Pass-by-reference это означает ссылку на исходную переменную, которая была передана.
- Java является Pass-by-value, но позволяет нам эмулировать pass be reference, передавая Java-ссылку (т.е. указатель) по значению. Это означает, что он передает копию ссылки Java.
- C++ допускает передачу по ссылке, объявляя ссылочный параметр с помощью символа "&" (который является тем же символом, который используется для обозначения "адреса переменной" как в C, так и в C++). Например, если мы передадим указатель, параметр и аргумент не просто указывают на один и тот же объект, но на то, что они являются одной и той же переменной. Если один из них настроен на другой адрес или на ноль, то и другой.
- В приведенном ниже примере C++ я передаю указатель на завершенную нулем строку по ссылке. И в приведенном ниже примере с Java я передаю ссылку на 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
}
}
И последнее замечание: каждый раз, когда я сталкиваюсь с этим вопросом по stackru, меня постоянно раздражает, что на этот вопрос не принято никакого ответа, как будто это не решенный вопрос. Pass-by-reference и Pass-by-value - это стандартные отраслевые термины, которые имеют определенные значения. В этой теме есть несколько приемлемых ответов, которые четко объясняют, почему Java является передачей по значению. Пользователь, разместивший этот вопрос, ничего не опубликовал в stackru с 3 сентября 2008 года и, вероятно, никогда не выберет приемлемый ответ. Как бы сильно я не любил stackru, я вижу в этом огромную слабость.
Java всегда передается по значению, а не по ссылке
Прежде всего, нам нужно понять, что такое передача по значению и передача по ссылке.
Передача по значению означает, что вы делаете копию в памяти фактического значения параметра, которое передается. Это копия содержимого фактического параметра.
Передача по ссылке (также называемая передачей по адресу) означает, что копия адреса фактического параметра сохраняется.
Иногда 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 в функцию она не будет напрямую передавать фактическое значение ссылки объекта test, но создаст копию 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 все ссылки, поэтому, когда у вас есть что-то вроде: Point pnt1 = new Point(0,0);
Java делает следующее:
- Создает новый объект Point
- Создает новую ссылку Point и инициализирует эту ссылку точкой (ссылкой) на ранее созданный объект Point.
- Отсюда, через жизнь объекта Point, вы получите доступ к этому объекту через ссылку pnt1. Таким образом, мы можем сказать, что в 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
В этой строке "передача по значению" входит в игру...
tricky(pnt1,pnt2); public void tricky(Point arg1, Point arg2);
Рекомендации pnt1
а также pnt2
передаются по значению в хитрый метод, что означает, что теперь ваши ссылки pnt1
а также pnt2
иметь их copies
названный arg1
а также arg2
.Так pnt1
а также arg1
указывает на тот же объект. (То же самое для pnt2
а также arg2
)
в tricky
метод:
arg1.x = 100;
arg1.y = 100;
Далее в tricky
метод
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
Здесь вы сначала создаете новый temp
Точка отсчета, которая будет указывать на то же место, как 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
Java это вызов по значению.
Как это устроено
Вы всегда передаете копию битов значения ссылки!
Если это примитивный тип данных, то эти биты содержат значение самого примитивного типа данных, поэтому, если мы изменим значение заголовка внутри метода, то это не будет отражать изменения снаружи.
Если это тип данных объекта, такой как Foo, foo=new Foo(), то в этом случае копия адреса объекта проходит как ярлык файла, предположим, что у нас есть текстовый файл abc.txt в C:\desktop, и предположим, что мы сделали ярлык тот же файл и поместите его в C:\desktop\abc-ярлык, чтобы при доступе к файлу из C:\desktop\abc.txt и записи "Переполнение стека" и закрытии файла снова открывался файл из ярлыка, затем вы напишите "самое большое онлайн-сообщество для изучения программистами", тогда общее изменение файла будет "Stack Overflow" - это самое большое онлайн-сообщество для обучения программистов ", что означает, что не имеет значения, откуда вы открываете файл, каждый раз, когда мы обращались к нему тот же файл, здесь мы можем принять Foo как файл и предположить, что foo хранится по адресу 123hd7h(исходный адрес, такой как C:\desktop\abc.txt), и 234jdid(скопированный адрес, такой как C:\desktop\abc-shortcut, который на самом деле содержит оригинальный адрес файла внутри) .. Так что для лучшего понимания сделайте ярлык файла и почувствуйте...
При представлении ссылка всегда является значением, независимо от того, какой язык вы используете.
Извлекая внешний вид коробки, давайте посмотрим на сборку или некоторое низкоуровневое управление памятью. На уровне ЦП ссылка на что-либо сразу становится значением, если оно записывается в память или в один из регистров ЦП. (Вот почему указатель является хорошим определением. Это значение, которое одновременно имеет цель).
Данные в памяти имеют местоположение, и в этом месте есть значение (байт, слово, что угодно). В Assembly у нас есть удобное решение для присвоения имени определенному местоположению (он же переменная), но при компиляции кода ассемблер просто заменяет имя указанным местоположением, как ваш браузер заменяет доменные имена на IP-адреса.
Вплоть до сути технически невозможно передать ссылку на что-либо на любом языке, не представляя его (когда это сразу становится значением).
Допустим, у нас есть переменная Foo, ее Location находится на 47-м байте в памяти, а ее значение равно 5. У нас есть другая переменная Ref2Foo, которая находится на 223-м байте в памяти, и ее значение будет 47. Этот Ref2Foo может быть технической переменной, явно не созданный программой. Если вы просто посмотрите на 5 и 47 без какой-либо другой информации, вы увидите только два значения. Если вы используете их в качестве ссылок, чтобы достичь 5
мы должны путешествовать:
(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223] -> 47
(Foo)[47] -> 5
Вот как работают таблицы переходов.
Если мы хотим вызвать метод / функцию / процедуру со значением Foo, существует несколько возможных способов передачи переменной в метод, в зависимости от языка и нескольких режимов вызова метода:
- 5 копируется в один из регистров процессора (т. Е. EAX).
- 5 получает PUSHd в стек.
- 47 копируется в один из регистров процессора
- 47 PUSHd в стек.
- 223 копируется в один из регистров ЦП.
- 223 получает PUSHd в стек.
В каждом случае выше значения - копия существующего значения - была создана, теперь это должен получить метод обработки. Когда вы пишете "Foo" внутри метода, он либо считывается из EAX, либо автоматически разыменовывается, либо разыменовывается дважды, процесс зависит от того, как работает язык и / или от того, что диктует тип Foo. Это скрыто от разработчика, пока она не обходит процесс разыменования. Таким образом, ссылка - это значение, когда оно представлено, потому что ссылка - это значение, которое должно быть обработано (на уровне языка).
Теперь мы передали Foo методу:
- в случае 1. и 2. если вы меняете Foo (
Foo = 9
) это влияет только на локальную область, так как у вас есть копия значения. Изнутри метода мы даже не можем определить, где в памяти находился оригинальный Foo. - в случаях 3. и 4. если вы используете языковые конструкции по умолчанию и меняете Foo (
Foo = 11
), это может изменить Foo глобально (зависит от языка, т.е. Java или как у Паскаляprocedure findMin(x, y, z: integer;
вар м: integer);
). Однако, если язык позволяет обойти процесс разыменования, вы можете изменить47
, Сказать49
, В этот момент кажется, что Foo изменился, если вы прочитали его, потому что вы изменили локальный указатель на него. И если бы вы изменили этот Foo внутри метода (Foo = 12
) вы, вероятно, FUBAR запустите выполнение программы (aka. segfault), потому что вы будете писать в память, отличную от ожидаемой, вы даже можете изменить область, предназначенную для хранения исполняемой программы, а запись в нее изменит исполняемый код (Foo сейчас не в47
). НО ценность Фу47
не изменился глобально, только один внутри метода, потому что47
была также копия к методу. - в случаях 5. и 6. если вы измените
223
внутри метода он создает тот же хаос, что и в 3. или 4. (указатель, указывающий на неверное значение, которое снова используется в качестве указателя), но это все еще локальная проблема, поскольку 223 была скопирована. Однако, если вы можете разыменоватьRef2Foo
(то есть223
), достичь и изменить указанное значение47
, Сказать49
, это повлияет на Foo глобально, потому что в этом случае методы получили копию223
но ссылка47
существует только один раз, и изменяя это на49
будет вести каждыйRef2Foo
двойная разыменование к неправильному значению.
Не обращая внимания на незначительные детали, даже языки, которые передают по ссылке, передадут значения в функции, но эти функции знают, что они должны использовать его для разыменования. Эта передача-ссылка-как-значение просто скрыта от программиста, потому что она практически бесполезна, а терминология - только передача по ссылке.
Строгая передача по значению также бесполезна, это будет означать, что 100-мегабайтный массив должен копироваться каждый раз, когда мы вызываем метод с массивом в качестве аргумента, поэтому Java не может строго передаваться по значению. Каждый язык передает ссылку на этот огромный массив (в качестве значения) и использует механизм копирования при записи, если этот массив может быть изменен локально внутри метода, или позволяет методу (как это делает Java) изменять массив глобально (из представление вызывающего абонента) и несколько языков позволяет изменять значение самой ссылки.
Короче говоря, и в собственной терминологии Java, Java является передачей по значению, где значением может быть: либо реальное значение, либо значение, представляющее ссылку.
В Java только ссылки передаются и передаются по значению:
Все аргументы Java передаются по значению (ссылка используется при использовании метода):
В случае примитивных типов поведение Java простое: значение копируется в другой экземпляр примитивного типа.
В случае объектов это тоже самое: переменные объекта - это указатели (сегменты), содержащие только адрес объекта, который был создан с использованием ключевого слова "new", и копируются как примитивные типы.
Поведение может отличаться от примитивных типов: поскольку скопированная объектная переменная содержит один и тот же адрес (к тому же объекту), содержимое / элементы объекта могут по-прежнему изменяться внутри метода, а затем доступ к нему снаружи, создавая иллюзию того, что (содержащий) объект Сам был передан по ссылке.
"Строковые" объекты кажутся идеальным контрпримером к городской легенде о том, что "объекты передаются по ссылке":
По сути, в методе вы никогда не сможете обновить значение строки, переданной в качестве аргумента:
String Object, содержит символы в массиве, объявленном как final, который нельзя изменить. Только адрес объекта может быть заменен другим, используя "новый". Использование "new" для обновления переменной не позволит получить доступ к объекту извне, поскольку переменная изначально была передана по значению и скопирована.
Насколько я знаю, 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, потому что в функции подкачки вы используете copys, которые не влияют на ссылки в основном. Но если ваши объекты не являются неизменяемыми, вы можете изменить это, например:
public static void appendWorld(StringBuffer s1) {
s1.append(" World");
}
public static void main(String[] args) {
StringBuffer s = new StringBuffer("Hello");
appendWorld(s);
System.out.println(s);
}
Это заполнит Hello World в командной строке. Если вы измените StringBuffer на String, он выдаст только Hello, потому что String неизменен. Например:
public static void appendWorld(String s){
s = s+" World";
}
public static void main(String[] args) {
String s = new String("Hello");
appendWorld(s);
System.out.println(s);
}
Однако вы можете создать оболочку для String, например, такую, которая позволит использовать ее со строками:
class StringWrapper {
public String value;
public StringWrapper(String value) {
this.value = value;
}
}
public static void appendWorld(StringWrapper s){
s.value = s.value +" World";
}
public static void main(String[] args) {
StringWrapper s = new StringWrapper("Hello");
appendWorld(s);
System.out.println(s.value);
}
редактировать: я считаю, что это также причина для использования StringBuffer, когда дело доходит до "добавления" двух строк, потому что вы можете изменить исходный объект, что вы не можете с неизменными объектами, такими как String.
Нет, это не передача по ссылке.
Java передается по значению в соответствии со спецификацией языка Java:
Когда метод или конструктор вызывается (§15.12), значения фактических выражений аргумента инициализируют вновь созданные переменные параметра, каждый из объявленного типа, перед выполнением тела метода или конструктора. Идентификатор, который появляется в DeclaratorId, может использоваться как простое имя в теле метода или конструктора для ссылки на формальный параметр.
Позвольте мне попытаться объяснить мое понимание с помощью четырех примеров. 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" - это "передача по значению", а объекты - "передача по ссылке".
Но из этого примера мы можем понять, что это только передача по значению, имея в виду, что здесь мы передаем ссылку как значение. т.е. ссылка передается по значению. Вот почему могут измениться, и все же это верно для локальной области видимости. Но мы не можем изменить фактическую ссылку за пределы исходной области. что это означает, продемонстрировано на следующем примере 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:
/**
В дополнение к тому, что было упомянуто в Example3 (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's 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 использует в своих вызовах методов. Спецификация языка 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
, Это именно то, что описывает определение передачи по значению. Таким образом, Java передается по значению.
Тот факт, что вы можете перейти по ссылке, чтобы вызвать метод или получить доступ к полю объекта, на который есть ссылка, совершенно не имеет отношения к беседе. Определение передачи по ссылке было
Как правило, это означает, что функция может модифицировать (т.е. назначать) переменную, используемую в качестве аргумента - то, что будет видно вызывающей стороне.
В Java изменение переменной означает ее переназначение. В Java, если вы переназначите переменную внутри метода, она останется незамеченной для вызывающей стороны. Модификация объекта, на который ссылается переменная, является совершенно другой концепцией.
Примитивные значения также определены в Спецификации виртуальной машины Java, здесь. Значением типа является соответствующее целочисленное значение или значение с плавающей запятой, закодированное соответствующим образом (8, 16, 32, 64 и т. Д. Биты).
Вы никогда не можете перейти по ссылке в Java, и один из очевидных способов - это когда вы хотите вернуть более одного значения из вызова метода. Рассмотрим следующий фрагмент кода на C++:
void getValues(int& arg1, int& arg2) {
arg1 = 1;
arg2 = 2;
}
void caller() {
int x;
int y;
getValues(x, y);
cout << "Result: " << x << " " << y << endl;
}
Иногда вы хотите использовать тот же шаблон в Java, но вы не можете; по крайней мере, не напрямую. Вместо этого вы можете сделать что-то вроде этого:
void getValues(int[] arg1, int[] arg2) {
arg1[0] = 1;
arg2[0] = 2;
}
void caller() {
int[] x = new int[1];
int[] y = new int[1];
getValues(x, y);
System.out.println("Result: " + x[0] + " " + y[0]);
}
Как было объяснено в предыдущих ответах, в Java вы передаете указатель на массив в качестве значения в getValues
, Этого достаточно, потому что метод затем модифицирует элемент массива, и по соглашению вы ожидаете, что элемент 0 будет содержать возвращаемое значение. Очевидно, что вы можете сделать это другими способами, такими как структурирование вашего кода, так что в этом нет необходимости, или создание класса, который может содержать возвращаемое значение или разрешить его установку. Но простой шаблон, доступный вам в C++ выше, не доступен в Java.
Различие, или, может быть, просто то, что я помню, как у меня было такое же впечатление, как у оригинального плаката, заключается в следующем: Java всегда передается по значению. Все объекты (в Java, все, кроме примитивов) в Java являются ссылками. Эти ссылки передаются по значению.
Как уже упоминали многие люди, Java всегда передается по значению
Вот еще один пример, который поможет вам понять разницу ( классический пример обмена):
public class Test {
public static void main(String[] args) {
Integer a = new Integer(2);
Integer b = new Integer(3);
System.out.println("Before: a = " + a + ", b = " + b);
swap(a,b);
System.out.println("After: a = " + a + ", b = " + b);
}
public static swap(Integer iA, Integer iB) {
Integer tmp = iA;
iA = iB;
iB = tmp;
}
}
Печать:
До: а = 2, б = 3
После: а = 2, б = 3
Это происходит потому, что iA и iB являются новыми локальными ссылочными переменными, которые имеют одинаковое значение переданных ссылок (они указывают на a и b соответственно). Таким образом, попытка изменить ссылки iA или iB изменится только в локальной области, а не вне этого метода.
Я всегда думаю об этом как о "проходе копией". Это копия значения, будь то примитив или ссылка. Если это примитив, то это копия битов, являющихся значением, а если это объект, то это копия ссылки.
public class PassByCopy{
public static void changeName(Dog d){
d.name = "Fido";
}
public static void main(String[] args){
Dog d = new Dog("Maxx");
System.out.println("name= "+ d.name);
changeName(d);
System.out.println("name= "+ d.name);
}
}
class Dog{
public String name;
public Dog(String s){
this.name = s;
}
}
вывод Java PassByCopy:
имя = макс
имя = Фидо
Примитивные классы-обертки и строки неизменны, поэтому любой пример, использующий эти типы, не будет работать так же, как другие типы / объекты.
В отличие от некоторых других языков, Java не позволяет вам выбирать передачу по значению или передачу по ссылке - все аргументы передаются по значению. Вызов метода может передавать в метод два типа значений - копии примитивных значений (например, значений типа 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 в main методе, array1 в main начнет указывать на массив c.
Вы также можете присвоить значение array2 в main для array1. массив1 начнет указывать на б.
У 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();
}
Короче говоря, у объектов Java есть некоторые очень специфические свойства.
Вообще, у Java есть примитивные типы (int
, bool
, char
, double
и т. д.), которые передаются непосредственно по значению. Тогда у Java есть объекты (все, что происходит от java.lang.Object
). Объекты на самом деле всегда обрабатываются с помощью ссылки (ссылка - это указатель, который вы не можете коснуться). Это означает, что, по сути, объекты передаются по ссылке, так как ссылки обычно не интересны. Это, однако, означает, что вы не можете изменить, на какой объект указывает указатель, поскольку сама ссылка передается по значению.
Это звучит странно и сбивает с толку? Давайте рассмотрим, как C реализует передачу по ссылке и передачу по значению. В С соглашением по умолчанию является передача по значению. void foo(int x)
передает int по значению. void foo(int *x)
это функция, которая не хочет int a
, но указатель на int: foo(&a)
, Можно использовать это с &
оператор для передачи переменного адреса.
Отнесите это на C++, и у нас есть ссылки. Ссылки в основном (в этом контексте) синтаксический сахар, который скрывает указатель части уравнения: void foo(int &x)
называется foo(a)
где сам компилятор знает, что это ссылка и адрес не-ссылки a
должен быть пройден. В Java все переменные, ссылающиеся на объекты, на самом деле относятся к ссылочному типу, фактически вызывая вызов по ссылке для большинства целей и задач без детального контроля (и сложности), предоставляемого, например, C++.
Несколько исправлений к некоторым постам.
C не поддерживает передачу по ссылке. ВСЕГДА передается по значению. C++ поддерживает передачу по ссылке, но не используется по умолчанию и довольно опасен.
Неважно, какое значение в Java: примитив или адрес (примерно) объекта, оно ВСЕГДА передается по значению.
Если Java-объект "ведет себя" так, как его передают по ссылке, это свойство изменчивости и не имеет абсолютно никакого отношения к механизмам передачи.
Я не уверен, почему это так сбивает с толку, возможно, потому, что так много "программистов" на Java не обучены формально и, следовательно, не понимают, что на самом деле происходит в памяти?