Является ли Java «передачей по ссылке» или «передачей по значению»?

Введение Этот вопрос часто возникает как в Интернете, так и когда кто-то хочет проверить ваши знания о том, как Java обрабатывает переменные:> Является ли Java «передачей по ссылке» или «передачей по значению» при передаче аргументов методам? Это кажется простым вопросом (это так), но многие люди ошибаются, говоря:> Объекты передаются по ссылке, а примитивные типы передаются по значению. Правильным утверждением будет:> Ссылки на объекты передаются по значению, как и примитивные типы. Таким образом, Java p

Вступление

Этот вопрос часто возникает как в Интернете, так и когда кто-то хочет проверить ваши знания о том, как Java обрабатывает переменные:

Является ли Java «передачей по ссылке» или «передачей по значению» при передаче аргументов методам?

Это кажется простым вопросом (это так), но многие люди ошибаются, говоря:

Объекты передаются по ссылке, а примитивные типы передаются по значению.

Правильным заявлением будет:

Ссылки на объекты передаются по значению, как и примитивные типы . Таким образом, Java во всех случаях передает по значению, а не по ссылке.

Некоторым это может показаться неинтуитивным, поскольку на лекциях часто демонстрируют разницу между такими примерами:

 public static void main(String[] args) { 
 int x = 0; 
 incrementNumber(x); 
 System.out.println(x); 
 } 
 
 public static void incrementNumber(int x) { 
 x += 1; 
 } 

и пример вроде этого:

 public static void main(String[] args) { 
 Number x = new Number(0); 
 incrementNumber(x); 
 System.out.println(x); 
 } 
 
 public static void incrementNumber(Number x) { 
 x.value += 1; 
 } 
 
 public class Number { 
 int value; 
 // Constructor, getters and setters 
 } 

Первый пример напечатает:

 0 

В то время как второй пример напечатает:

 1 

Причина этой разницы часто понимается как "передача по значению" (первый пример, скопированное значение x передается, и любая операция с копией не отражается на исходном значении) и "передача по значению" -reference " (второй пример, передается ссылка, и при изменении она отражает исходный объект).

В следующих разделах мы объясним, почему это неверно .

Как Java обрабатывает переменные

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

Примитивные типы

Java - это язык со статической типизацией . Он требует, чтобы мы сначала объявили переменную, затем инициализировали ее, и только после этого мы можем ее использовать:

 // Declaring a variable and initializing it with the value 5 
 int i = 5; 
 
 // Declaring a variable and initializing it with a value of false 
 boolean isAbsent = false; 

Вы можете разделить процесс объявления и инициализации:

 // Declaration 
 int i; 
 boolean isAbsent; 
 
 // Initialization 
 i = 5; 
 isAbsent = false; 

Но если вы попытаетесь использовать неинициализированную переменную:

 public static void printNumber() { 
 int i; 
 System.out.println(i); 
 i = 5; 
 System.out.println(i); 
 } 

Вас встречает ошибка:

 Main.java:10: error: variable i might not have been initialized 
 System.out.println(i); 

Для локальных примитивных типов, таких как i нет значений по умолчанию. Хотя, если вы определяете глобальные переменные, такие как i в этом примере:

 static int i; 
 
 public static void printNumber() { 
 System.out.println(i); 
 i = 5; 
 System.out.println(i); 
 } 

Запустив это, вы увидите следующий результат:

 0 
 5 

Переменная i была выведена как 0 , хотя она еще не была назначена.

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

В Java есть 8 примитивных типов:

  • byte : Диапазон от -128 до 127 включительно, 8-битное целое число со знаком
  • short : диапазон от -32,768 до 32,767 включительно, 16-битное целое число со знаком
  • int : варьируется от -2,147,483,648 147 483 648 до 2,147,483,647 включительно, 32-разрядное целое число со знаком.
  • long : варьируется от -2 ^31^ до 2 ^31^ -1 включительно, 64-битное целое число со знаком
  • float : 32-битное целое число IEEE 754 с плавающей запятой одинарной точности с 6-7 значащими цифрами.
  • double : 64-битное целое число с плавающей запятой IEEE 754 двойной точности с 15 значащими цифрами.
  • boolean : двоичные значения, true или false
  • char : диапазон от 0 до 65,536 включительно, 16-разрядное целое число без знака, представляющее символ Юникода.

Передача примитивных типов

Когда мы передаем примитивные типы в качестве аргументов метода, они передаются по значению. Вернее, их значение копируется, а затем передается в метод.

Вернемся к первому примеру и разберем его:

 public static void main(String[] args) { 
 int x = 0; 
 incrementNumber(x); 
 System.out.println(x); 
 } 
 
 public static void incrementNumber(int x) { 
 x += 1; 
 } 

Когда мы объявляем и инициализируем int x = 0; , Мы говорили Java , чтобы сохранить 4-байтовое пространство в стеке для int , чтобы быть сохранен в. int не должен заполнить все 4 байта ( Integer.MAX_VALUE ), но все 4 байта будет доступен.

На это место в памяти затем обращается компилятор, когда вы хотите использовать целое число x . Имя x - это то, что мы используем для доступа к ячейке памяти в стеке. У компилятора есть собственные внутренние ссылки на эти места.

После того, как мы передали x incrementNumber() и компилятор достигает сигнатуры метода с параметром int x он создает новую ячейку / пространство памяти в стеке.

Используемое нами имя переменной x имеет большого значения для компилятора. Мы можем даже пойти дальше и сказать, что int x мы объявили в методе main() x_1 а int x мы объявили в сигнатуре метода, равен x_2 .

Затем мы увеличили значение целого числа x_2 в методе и затем распечатали x_1 . Естественно, печатается значение, хранящееся в ячейке памяти для x_1 и мы видим следующее:

 0 

Вот визуализация кода:

стек Java и примитивныепеременные{.ezlazyload}

В заключение компилятор делает ссылку на место в памяти примитивных переменных.

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

Вероятно, это то, что вы уже знали, и то, что знают все, кто ответил с первоначальным неверным утверждением. Самое большое заблуждение заключается в следующем типе данных.

Типы ссылок

Тип, используемый для передачи данных, - это ссылочный тип .

Когда мы объявляем и создаем экземпляры / инициализируем объекты (аналогично примитивным типам), на них создается ссылка - опять же, очень похоже на примитивные типы:

 // Declaration and Instantiation/initialization 
 Object obj = new Object(); 

Опять же, мы также можем разделить этот процесс:

 // Declaration 
 Object obj; 
 
 // Instantiation/initialization 
 obj = new Object(); 

Примечание: существует разница между созданием экземпляра и инициализацией . Создание экземпляра относится к созданию объекта и назначению ему места в памяти. Инициализация относится к заполнению полей этого объекта через конструктор после его создания.

Когда мы закончим с объявлением, переменная obj станет ссылкой на new объект в памяти. Этот объект хранится в куче - в отличие от примитивных типов, которые хранятся в стеке .

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

Значение по умолчанию для объектов после объявления - null . Не существует типа, который является instanceof null и не принадлежит ни к какому типу или набору. Если ссылке не присвоено никакого значения, например obj , ссылка будет указывать на null .

Допустим, у нас есть такой класс, как Employee :

 public class Employee { 
 String name; 
 String surname; 
 } 

И создайте экземпляр класса как:

 Employee emp = new Employee(); 
 emp.name = new String("David"); 
 emp.surname = new String("Landup"); 

Вот что происходит в фоновом режиме:

Создание объекта памяти кучиJava{.ezlazyload}

emp указывает на объект в пространстве кучи. Этот объект содержит ссылки на два String которые содержат значения David и Landup .

Каждый раз, когда используется new , создается новый объект.

Передача ссылок на объекты

Посмотрим, что происходит, когда мы передаем объект в качестве аргумента метода:

 public static void main(String[] args) { 
 Employee emp = new Employee(); 
 emp.salary = 1000; 
 incrementSalary(emp); 
 System.out.println(emp.salary); 
 } 
 
 public static void incrementSalary(Employee emp) { 
 emp.salary += 100; 
 } 

Мы передали нашу emp на метод incrementSalary() . Метод обращается к int salary объекта и увеличивает его на 100 . В итоге нас встречают:

 1100 

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

Неправильно . Так же , как с примитивными типами, мы можем идти вперед и сказать , что есть два emp переменных после того , как метод был назван - emp_1 и emp_2 , в глаза компилятора.

Разница между примитивом x мы использовали раньше, и emp которую мы используем сейчас, заключается в том, что и emp_1 и emp_2 указывают на один и тот же объект в памяти .

Используя любую из этих двух ссылок, осуществляется доступ к одному и тому же объекту и изменяется одна и та же информация.

java heap memory несколькоссылок{.ezlazyload}

При этом это подводит нас к первоначальному вопросу.

Является ли Java «передачей по ссылке» или «передачей по значению»?

Java проходит по значению. Примитивные типы передаются по значению, ссылки на объекты передаются по значению.

Java не передает объекты. Он передает ссылки на объекты - поэтому, если кто-нибудь спросит, как Java передает объекты, ответ будет: «Нет». ^1^

В случае примитивных типов после передачи им выделяется новое пространство в стеке, и, таким образом, все дальнейшие операции с этой ссылкой связываются с новой ячейкой памяти.

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

[1. По словам Брайана Гетца, архитектора языка Java, работающего над проектами Valhalla и Amber. Вы можете прочитать об этом здесь .]{.small}

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus