- Передача по значению как механизм передачи параметров в Java
- 1. введение
- 2. Pass-by-Value vs Pass-by-Reference
- 2.1. Передача по значению
- 2.2. Пропуск по ссылке
- 3. Передача параметров в Java
- 3.1. Передача Примитивных Типов
- 3.2. Передача ссылок на объекты
- 4. Заключение
- Читайте ещё по теме:
- Java: передача параметров по значению или по ссылке
Передача по значению как механизм передачи параметров в Java
Узнайте, как передача параметров обрабатывается в Java для случаев примитивных и объектных типов.
1. введение
Двумя наиболее распространенными способами передачи аргументов методам являются “передача по значению” и “передача по ссылке”. Различные языки программирования используют эти понятия по-разному. Что касается Java, все строго Pass-by-Value .
В этом уроке мы проиллюстрируем, как Java передает аргументы для различных типов.
2. Pass-by-Value vs Pass-by-Reference
Давайте начнем с некоторых различных механизмов передачи параметров функциям:
Двумя наиболее распространенными механизмами в современных языках программирования являются “Передача по значению” и “Передача по ссылке”. Прежде чем мы продолжим, давайте сначала обсудим это:
2.1. Передача по значению
Когда параметр передается по значению, вызывающий и вызываемый метод работают с двумя различными переменными, которые являются копиями друг друга. Любые изменения одной переменной не изменяют другую.
Это означает, что при вызове метода параметры , передаваемые вызываемому методу, будут клонами исходных параметров. Любая модификация, выполненная в методе вызываемого абонента, не повлияет на исходные параметры в методе вызывающего абонента.
2.2. Пропуск по ссылке
Когда параметр передается по ссылке, вызывающий и вызываемый объект работают с одним и тем же объектом.
Это означает, что при передаче переменной по ссылке методу отправляется уникальный идентификатор объекта. Любые изменения членов экземпляра параметров приведут к изменению исходного значения.
3. Передача параметров в Java
Фундаментальными понятиями в любом языке программирования являются “значения” и “ссылки”. В Java примитивные переменные хранят фактические значения, в то время как непримитивы хранят ссылочные переменные, которые указывают на адреса объектов, на которые они ссылаются. Как значения, так и ссылки хранятся в памяти стека.
Аргументы в Java всегда передаются по значению. Во время вызова метода копия каждого аргумента, будь то значение или ссылка, создается в памяти стека, которая затем передается методу.
В случае примитивов значение просто копируется в память стека, которая затем передается вызываемому методу; в случае непримитивов ссылка в памяти стека указывает на фактические данные, которые находятся в куче. Когда мы передаем объект, ссылка в памяти стека копируется, и новая ссылка передается методу.
Давайте теперь посмотрим на это в действии с помощью некоторых примеров кода.
3.1. Передача Примитивных Типов
Язык программирования Java имеет восемь примитивных типов данных . Примитивные переменные непосредственно хранятся в памяти стека. Всякий раз, когда какая-либо переменная примитивного типа данных передается в качестве аргумента, фактические параметры копируются в формальные аргументы, и эти формальные аргументы накапливают свое собственное пространство в памяти стека.
Срок службы этих формальных параметров длится только до тех пор, пока этот метод работает, и после возврата эти формальные аргументы удаляются из стека и отбрасываются.
Давайте попробуем разобраться в этом с помощью примера кода:
public class PrimitivesUnitTest < @Test public void whenModifyingPrimitives_thenOriginalValuesNotModified() < int x = 1; int y = 2; // Before Modification assertEquals(x, 1); assertEquals(y, 2); modify(x, y); // After Modification assertEquals(x, 1); assertEquals(y, 2); >public static void modify(int x1, int y1) < x1 = 5; y1 = 10; >>
Давайте попробуем понять утверждения в приведенной выше программе, проанализировав, как эти значения хранятся в памяти:
- Переменные ” x” и ” y” в основном методе являются примитивными типами, и их значения непосредственно хранятся в памяти стека
- Когда мы вызываем метод modify() , точная копия для каждой из этих переменных создается и хранится в другом месте в памяти стека
- Любое изменение этих копий влияет только на них и оставляет исходные переменные неизменными
3.2. Передача ссылок на объекты
В Java все объекты динамически хранятся в пространстве кучи под капотом. Эти объекты ссылаются на ссылки, называемые ссылочными переменными.
Объект Java, в отличие от примитивов, хранится в два этапа. Ссылочные переменные хранятся в памяти стека, а объект, на который они ссылаются, хранится в памяти кучи.
Всякий раз, когда объект передается в качестве аргумента, создается точная копия ссылочной переменной, которая указывает на то же расположение объекта в памяти кучи, что и исходная ссылочная переменная.
В результате этого всякий раз, когда мы вносим какие-либо изменения в один и тот же объект в методе, это изменение отражается в исходном объекте. Однако, если мы выделим новый объект для переданной ссылочной переменной, то он не будет отражен в исходном объекте.
Давайте попробуем понять это с помощью примера кода:
public class NonPrimitivesUnitTest < @Test public void whenModifyingObjects_thenOriginalObjectChanged() < Foo a = new Foo(1); Foo b = new Foo(1); // Before Modification assertEquals(a.num, 1); assertEquals(b.num, 1); modify(a, b); // After Modification assertEquals(a.num, 2); assertEquals(b.num, 1); >public static void modify(Foo a1, Foo b1) < a1.num++; b1 = new Foo(1); b1.num++; >> class Foo < public int num; public Foo(int num) < this.num = num; >>
Давайте проанализируем утверждения в приведенной выше программе. Мы передали объекты a и b в modify() метод, который имеет то же значение 1 . Первоначально эти ссылки на объекты указывают на два различных местоположения объектов в пространстве кучи:
Когда эти ссылки a и b передаются в методе modify () , он создает зеркальные копии этих ссылок a1 и b1 , которые указывают на одни и те же старые объекты:
В методе modify () , когда мы изменяем ссылку a1 , он изменяет исходный объект. Однако для ссылки b1, мы назначили новый объект. Таким образом, теперь он указывает на новый объект в памяти кучи.
Любое изменение, внесенное в b1 , ничего не отразит в исходном объекте:
4. Заключение
В этой статье мы рассмотрели, как обрабатывается передача параметров в случае примитивов и непримитивов.
Мы узнали, что передача параметров в Java всегда происходит по значению. Однако контекст меняется в зависимости от того, имеем ли мы дело с примитивами или объектами:
- Для примитивных типов параметры передаются по значению
- Для типов объектов ссылка на объект является передаваемым значением
Фрагменты кода, используемые в этой статье, можно найти на GitHub .
Читайте ещё по теме:
Java: передача параметров по значению или по ссылке
Простое объяснение принципов передачи параметров в Java.
Многие программисты часто путают, какие параметры в Java передаются по значению, а какие по ссылке. Давайте визуализируем этот процесс, и тогда вы увидите насколько все просто.
Данные передаются между методами через параметры. Есть два способа передачи параметров:
- Передача по значению (by value). Значения фактических параметров копируются. Вызываемый метод создает свою копию значений аргументов и затем ее использует. Поскольку работа ведется с копией, на исходный параметр это никак не влияет.
- Передача по ссылке (by reference). Параметры передаются как ссылка (адрес) на исходную переменную. Вызываемый метод не создает свою копию, а ссылается на исходное значение. Следовательно, изменения, сделанные в вызываемом методе, также будут отражены в исходном значении.
В Java переменные хранятся следующим образом:
- Локальные переменные, такие как примитивы и ссылки на объекты, создаются в стеке.
- Объекты — в куче (heap).
Теперь вернемся к основному вопросу: переменные передаются по значению или по ссылке?
Java всегда передает параметры по значению
Чтобы разобраться с этим, давайте посмотрим на пример.
Поскольку Java передает параметры по значению, метод processData работает с копией данных. Следовательно, в исходных данных (в методе main ) не произошло никаких изменений.
Теперь рассмотрим другой пример:
Что здесь происходит? Если Java передает параметры по значению, то почему был изменен исходный список? Похоже, что Java все-таки передает параметры не по значению? Нет, неправильно. Повторяйте за мной: «Java всегда передает параметры по значению».
Чтобы с этим разобраться, давайте посмотрим на следующую диаграмму.
В программе, приведенной выше, список fruits передается методу processData . Переменная fruitRef — это копия параметра fruit . И fruits и fruitsRef размещаются в стеке. Это две разные ссылки. Но самое интересное заключается в том, что они указывают на один и тот же объект в куче. То есть, любое изменение, которое вы вносите с помощью любой из этих ссылок, влияет на объект.
Давайте посмотрим на еще один пример:
В этом случае для изменения ссылки fruitRef мы использовали оператор new . Теперь fruitRef указывает на новый объект, и, следовательно, любые изменения, которые вы вносите в него, не повлияют на исходный объект списка фруктов.
Итак, Java всегда передает параметры по значению. Однако мы должны быть осторожны при передаче ссылок на объекты.
Вышеприведенная концепция очень важна для правильного решения ваших задач.
Например, рассмотрим удаление узла в односвязном списке.
class Node < int data; Node next; Node(int d)< data = d; next = null; >> class LinkedList < public static Node push(Node head, int data) < Node newNode = new Node(data); newNode.next = head; head = newNode; return head; >public static void deleteNode(Node head, int position) < // List is empty if (head == null)< return; >// If position is 1st, removing head node if (position == 1) < head = head.next; return; >Node prevNode = head; int i = 2; while (prevNode != null && i != position) < prevNode = prevNode.next; i++; >// When position is more than number of node if (prevNode == null || prevNode.next == null) < return; >prevNode.next = prevNode.next.next; > public static void printList(Node head) < Node currNode = head; while (currNode != null) < System.out.print(currNode.data + " "); currNode = currNode.next; >> public static void main(String[] args) < Node head = null; head = push(head, 5); head = push(head, 4); head = push(head, 3); head = push(head, 2); head = push(head, 1); System.out.println("Created Linked list is: "); printList(head); // Delete node at position 2 deleteNode(head, 2); System.out.println("\nLinked List after Deletion at position 2: "); printList(head); >>
Это решение работает во всех случаях, кроме одного — когда вы удаляете первый узел ( Position = 1 ). Основываясь на ранее описанной концепции, видите ли вы в чем здесь проблема? Возможно, поможет следующая диаграмма.
Для исправления алгоритма необходимо сделать следующее:
public static Node deleteNode(Node head, int position) < // List is empty if (head == null)< return head; >// If position is 1st, removing head node if (position == 1) < head = head.next; return head; >Node prevNode = head; int i = 2; while (prevNode != null && i != position) < prevNode = prevNode.next; i++; >// When position is more than number of node if (prevNode == null || prevNode.next == null) < return head; >prevNode.next = prevNode.next.next; return head; > public static void main(String[] args) < Node head = null; head = push(head, 5); head = push(head, 4); head = push(head, 3); head = push(head, 2); head = push(head, 1); System.out.println("Created Linked list is: "); printList(head); // Delete node at position 2 head = deleteNode(head, 2); System.out.println("\nLinked List after Deletion at position 2: "); printList(head); >//Rest of the code remains same
В этой статье мы обсудили одну небольшую, но важную концепцию Java: передачу параметров.
Перевод статьи подготовлен в преддверии старта курса «Подготовка к сертификации Oracle Java Programmer (OCAJP)».
Подробнее о курсе и программе обучения можно узнать на бесплатном вебинаре, который пройдет 15 апреля.
ЗАПИСАТЬСЯ НА ВЕБИНАР