Логика выполнения вызванного метода при передаче параметра
Никак не могу понять логику выполнения метода: до функции 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);
}
}
Ответы (3 шт):
В 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-ти.
Потому, что параметры методов передаются не по ссылке, а по значению. Для того чтобы получить новое значение из метода нужно его вернуть оператором 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);
}
}
В данном случае в качестве аргумента передаётся примитивное целочисленное значение переменной 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