Логика выполнения вызванного метода при передаче параметра

Рейтинг: 1Ответов: 3Опубликовано: 10.03.2023

Никак не могу понять логику выполнения метода:

До метода main() мы создаём метод test1(), который увеличивает int a на единицу.

После метода main() мы прописываем значение для int a, и обращаемся к ранее созданному методу test1().

Из-за того, что Java передаёт параметры по значению, результат в методе до main() будет равен 11, а после - 10. Но разве метод, вызываемый после main() не должен также увеличивать на единицу, переданное ему значение?

Почему он просто оставляет значение 10? Ведь это один и тот же метод...

static void test1(int a) {
    a++;
    System.out.printf("test 1 a = %d", a);
    }

public static void main(String[] args) {
    {
        int a=10;
        test1(a);
        System.out.printf("a=%d", a);
    }
}

Ответы

▲ 1Принят

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

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

В вашем случае есть метод test1:

static void test1(int a) {
    a++;
    System.out.printf("test 1 a = %d", a);
}

Он имеет параметр int a. Это его собственная переменная, он ничего не знает о каких-либо других переменных с именем a в других методах. Что на место параметра a передастся из вызывающего метода - то этот метод скопирует и с тем будет работать, нарастив на 1 и выведя на экран.

Вызывающим же методом является метод main:

public static void main(String[] args) {
    int a=10;
    test1(a);
    System.out.printf("a=%d", a);
}

У него есть своя переменная int a = 10. Он передаёт её в метод test1. Там она копируется и наращивается на 1: в результате получаем переменную int a = 10 в методе main и переменную int a = 11 в методе test1. Затем методом test1 его переменная a выводится на экран, он заканчивает свою работу, а метод main - продолжает, выводя на экран свою переменную a, равную 10-ти.

▲ 1

Потому, что параметры методов передаются не по ссылке, а по значению. Для того чтобы получить новое значение из метода нужно его вернуть оператором return. По этому поводу можно посмотреть Зачем возвращать что-либо из методов?.

Вот что по этому поводу написано в Java Tutorial:

Возврат значения из метода: Метод возвращается к коду, который его вызвал, когда он

  • завершает все операторы в методе,
  • достигает оператора возврата, или
  • выдает исключение (рассмотрено позже), в зависимости от того, что произойдет первым.

Вы объявляете возвращаемый тип метода в его объявлении метода. В теле метода вы используете оператор return для возврата значения.

Любой метод, объявленный как void, не возвращает значение. Он не обязательно должен содержать оператор возврата, но он может его содержать. В таком случае можно использовать оператор return для выхода из блока потока управления и выхода из метода, и он просто используется следующим образом:

  • возвращаться; Если вы попытаетесь вернуть значение из метода, который объявлен как void, вы получите ошибку компилятора.

Любой метод, который не объявлен как void, должен содержать оператор return с соответствующим возвращаемым значением, например:

  • вернуть возвращаемое значение; Тип данных возвращаемого значения должен соответствовать объявленному типу возвращаемого значения метода; вы не можете вернуть целочисленное значение из метода, объявленного для возврата логического значения.

Чтобы метод возвращал значение код можно переписать так:

static int test1(int a) {
    a++;
    System.out.printf("test 1 a = %d", a);
    return a;
    }

public static void main(String[] args) {
    {
        int a=10;
        a = test1(a);
        System.out.printf("a=%d", a);
    }
}
▲ 1

В данном случае в качестве аргумента передаётся примитивное целочисленное значение переменной a (локальной для метода main), то есть при вызове метода test1 в стек записывается копия целого числа (аргумента метода), внутри метода эта копия считывается из стека и все модификации этой переменной внутри метода остаются незамеченными снаружи.

Если бы передавалась переменная-объект типа Integer, результат был бы такой же, так как класс Integer является неизменяемым (immutable), то есть его внутреннее состояние нельзя изменить и разным числам 1, 2, .. соответствуют разные экземпляры.

static void test2(Integer a) {
    a++;
    System.out.println("test2: a=" + a);
}

Integer a = 10;
test2(a);
System.out.println("main: a=" + a);
test2: a=11
main: a=10

То есть здесь в метод через стек передаётся копия ссылки на Integer(1), вследствие инкремента локальной переменной a внутри метода присваивается другая ссылка на Integer(2), и это изменение никак не затрагивает "внешнюю" локальную переменную a.

Чтобы добиться изменения внутри некоего экземпляра класса после вызова метода, необходимо этот класс реализовать соответствующим образом, то есть реализовать класс с полем для хранения состояния и методами для изменения этого состояния:

static class Foo {
    private int x;
    Foo(int x) {this.x = x;}
    public void inc() {this.x++;}
    public void dec() {this.x--;}
    public void setX(int x) { this.x = x;}
    public void add(int y) {this.x += y;}
// и т.д.
    public int getX() {return this.x;}
    @Override public String toString() {
        return "Foo: x=" + x;
    }
}

Тогда при передаче ссылки на экземпляр такого класса и вызове методов-мутаторов состояния внутри некоего метода, эти изменения состояния будут видимы снаружи.

static void test3(Foo f) {
    f.inc();
    System.out.println("test3: f=" + f);
}

Foo f = new Foo(10);
test3(f);
System.out.println("main: f=" + f);

Результат:

test3: f=Foo: x=11
main: f=Foo: x=11