2

Есть проблема: на днях учитель по компьютерам обмолвился о том, что можно использовать метод с меткой void для изменения переменных. Привёл рабочий пример:

import java.util.*;

class training
{
    static Scanner reader = new Scanner(System.in);
    public static void main (String[]args)
    {
        int [] a = new int [10];
        firstnum(a);
        System.out.println(Arrays.toString(a));
    }

    static void firstnum (int[] t)
    {
        for (int i =0; i < t.length; i++)
        t[i] = (int)(Math.random() * 101);
    }
}

Код рабочий. Выводит заполненный массив целых чисел. Обратите внимание: метод в котором заполняется массив имеет метку void, то есть не возвращает никакие значение но, почему то, заполняет массив.

Случай второй. Пытался сделать такое же с обычным целым числом. Код:

import java.util.*;

class training2
{
    static Scanner reader = new Scanner(System.in);
    public static void main (String[]args)
    {
        int a = 0;
        firstnum(a);
        System.out.println(a);
    }

    static void firstnum (int t)
    {
        t = 1;
    }
}

Выводит 0. Почему и как исправить? Заранее спасибо.

AseN
  • 13,629
  • массив заполняется по ссылке в первом случае, больше информации в книге Философия Java (Инициализация массивов) – boden Jan 09 '17 at 18:42
  • Очень печально, что ваш учитель-по-компьютерам лишь "обмолвился", ибо из-за вот таких вот обмолвок JMM часто стреляет в колено. – AseN Jan 09 '17 at 19:25
  • @0xFFh Что такое JMM? – Alex Shvachko Jan 09 '17 at 19:57
  • Вообще-то это не дубликат вопроса http://ru.stackoverflow.com/questions/267073/Передача-по-значению-в-java. Там вопрос не про передачу параметров, а про присваивание новых значений immutable-объектам, а заголовок там неправильный. – m. vokhm Jan 10 '17 at 07:58

3 Answers3

9

В Java все параметры передаются при вызове по значению (pass by value), однако передача примитивных и объектных типов несколько различается. Из-за этого различия, некоторые ошибочно говорят, что объекты в Java передаются при вызове по ссылке – и это принципиально неверно, так как при передаче объектов в Java передается не сама ссылка на объект, а копия этой ссылки.

При передаче примитивного типа происходит копирование значения в локальную переменную метода, соответственно, изменение этого значения в методе никак не повлияет на значение переменной вне метода.

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

Стоит обратить внимание на передачу неизменяемых (immutable) объектов (например объектов типа String и всех оболочек примитивов: Integer, Float и т.д.): при изменении таких объектов создается новый объект, поэтому эти изменения извне видны не будут.

Касательно Ваших примеров:

В первом случае передается объект int[] t, соответственно, изменение его состояния будет видно извне.

Во втором случае передается примитив int t, соответственно, изменение значения этой переменной в методе не видно извне.


Есть еще такой термин: вызов по соиспользованию (call by sharing) – по сути, объекты в Java передаются именно так, но в данном контексте вызов по соиспользованию можно назвать некоторой разновидностью вызова по значению, поэтому в общем можно сказать, что в Java все параметры передаются при вызове по значению.

post_zeew
  • 21,983
2

В этом нет никакой мистики, и тип метода - void или какой-то другой - к вопросу не имеет никакого отношения. Все дело в том, как в метод передаются параметры. Параметры примитивных типов (int, double и т. п.) передаются по значению - иными словами, при вызове метода создается неявная локальная (существующая только внутри этого метода) переменная, куда копируется значение, переданное в параметре. Недаром же это именно значение, например, это может быть значение выражения 5 + 10. Метод может делать со своим параметром что угодно - меняться будет только переменная, существующая внутри метода, никакие переменные в окружающем мире никак не изменятся. Вы же не ждете, что при вызове firstnum(5 + 10) пять превратиться в 10 или 10 в 20?

Другое дело - параметры объектных типов, к которым относятся и массивы. В этом случае в метод фактически передается ссылка на объект, и когда метод выполняет какие-то манипуляции с таким объектом, меняется сам исходный объект. В вашем первом примере параметр t фактически является ссылкой на массив a, описанный вне метода, и когда метод меняет значения элементов массива - это меняются элементы массива a. Это важно понимать и учитывать, иначе можно получить совсем не тот результат, который надо.

При это также надо помнить, что неизменяемые объекты (immutable), напр. String, а также объекты-оболочки (типа Integer, Double и т. п.) при передаче их в качестве параметров ведут себя в некотором смысле не как объекты, а как примитивные типы - хотя в метод передается ссылка на исходный объект, но при присваивании такому параметру нового значения внутри метода фактически создается новый объект с новым значением, и локальной переменной внутри метода присваивается ссылка на этот новый объект, а при завершении работы метода такой объект просто уничтожается. Исходный объект, ссылка на который был передан методу, при этом никак не меняется. Внешне это выглядит так, как при работе с примитивными типами - исходная переменная вне метода не меняется, что бы метод не делал со своими параметрами.

m. vokhm
  • 3,377
1

Видимо потому, что массив (хоть и тип int) делает автоупаковку (попробовать почитать тут) в Integer[], а Integer — это объект. Ну собсн объект передается по ссылке (кто-то скажет, что в Java все передается по значению и там статьи в инете всякие, но, не будем вдаваться в детали). И поэтому меняется.

А не массив, простой примитив не распаковывается. Поэтому ничего и не меняется.

  • А в какой момент, по Вашему мнению, происходит автоупаковка? И, самое главное, зачем? – post_zeew Jan 09 '17 at 18:49
  • Быть честным, ничего не понял из написанного тут. Но это не так важно. Мне просто надо знать с какими типами переменных будет работать. – Alex Shvachko Jan 09 '17 at 18:50
  • Массив(не переменная но надо было включить) - да, int - нет. Float, double, String, boolean? – Alex Shvachko Jan 09 '17 at 18:52
  • @АлексейШвачко, быть честным, ничего не понял из вашей реплики. Что вы хотели сказать? – m. vokhm Jan 09 '17 at 19:12
  • @m. vokhm Уже не важно. Я получил ответ. – Alex Shvachko Jan 09 '17 at 19:14
  • Тут дело не в автоупаковке. Массив сам по себе - объект, и фактически передается ссылка на него. Его элементы при передаче никак не меняются, int не упаковывается в Intger. Физически это та же область памяти остается. – m. vokhm Jan 09 '17 at 19:15