И снова привет! Разберемся, что такое методы в Java, как они принимают параметры, что такое перегрузка методов и чем передача по значению отличается от передачи по ссылке.
В прошлой лекции мы познакомились с
классами и конструкторами и научились создавать собственные.
Сегодня подробно разберем такую неотъемлемую часть классов как методы.
Метод это совокупность команд, которая позволяет выполнить некоторую операцию в программе. Иными словами, метод это функция, то, что умеет делать твой класс.
В других языках программирования методы часто называют "функциями", но в Java прижилось слово "метод" :)
Кратко
- Метод это действие класса: он выполняет операции над данными объекта.
- Оператор return возвращает результат и сразу завершает работу метода.
- Параметры передаются в метод строго в объявленном порядке и типах.
- Перегрузка это несколько методов с одним именем, но разными параметрами.
- Varargs (
String...names) позволяет передать любое количество аргументов.
- Примитивы передаются по значению (копия), объекты по ссылке, но сама ссылка тоже копируется.
![Метод это действие класса: операции над данными объекта, параметры и результат]()
Что такое метод в Java
В прошлой лекции мы создавали простые методы для класса Cat, чтобы наши коты умели мяукать и прыгать:
public class Cat {
String name;
int age;
public void sayMeow() {
System.out.println("Мяу!");
}
public void jump() {
System.out.println("Прыг-скок!");
}
public static void main(String[] args) {
Cat barsik = new Cat();
barsik.age = 3;
barsik.name = "Барсик";
barsik.sayMeow();
barsik.jump();
}
}
sayMeow() и
jump() являются методами нашего класса.
Результат их работы, вывод в консоль:
Мяу!
Прыг-скок!
Наши методы довольно просты: они просто выводят текст в консоль.
Но в Java у методов есть главная задача:
они должны выполнять действия над данными объекта. Менять значение данных объекта, преобразовывать их, выводить в консоль или делать с ними что-то другое.
Наши текущие методы ничего не делают с данными объекта
Cat. Давай рассмотрим более наглядный пример:
public class Truck {
int length;
int width;
int height;
int weight;
public int getVolume() {
int volume = length * width * height;
return volume;
}
}
К примеру, у нас есть класс, обозначающий грузовик,
Truck.
У прицепа грузовика есть длина, ширина, высота и вес (он будет нужен позже). В методе
getVolume() мы совершаем вычисления: преобразуем данные нашего объекта к числу, которое обозначает объем (умножаем длину, ширину и высоту).
Именно это число будет являться результатом метода.
Обрати внимание: в описании метода написано
public int getVolume. Это значит, что результатом работы этого метода
обязательно должно быть число в виде
int.
Мы высчитали результат метода и теперь должны
вернуть его нашей программе, которая вызвала метод.
Чтобы вернуть результат метода, в Java используется ключевое слово
return.
return volume;
Официальное описание методов есть в
документации Oracle (Java Tutorials).
Параметры методов
Методы могут принимать на вход значения, которые называют "параметрами методов".
Наш текущий метод
getVolume() в классе
Truck никаких параметров не принимает, поэтому давай попробуем расширить пример с грузовиками.
Создадим новый класс
BridgeOfficer. Офицер полиции дежурит на мосту и проверяет все проезжающие грузовики на предмет того, не превышает ли их груз допустимую норму веса.
public class BridgeOfficer {
int maxWeight;
public BridgeOfficer(int normalWeight) {
this.maxWeight = normalWeight;
}
public boolean checkTruck(Truck truck) {
if (truck.weight > maxWeight) {
return false;
} else {
return true;
}
}
}
Метод
checkTruck принимает на вход один параметр, объект грузовика
Truck, и определяет, пропустит ли офицер грузовик на мост или нет. Внутри метода логика достаточно проста: если вес грузовика превышает максимально допустимый, метод возвращает
false. Придется искать другую дорогу :(
Если вес меньше либо равен максимальному, можно проезжать, и метод возвращает
true.
Если тебе еще не до конца понятны фразы "вернуть", "метод возвращает значение", давай отвлечемся от программирования и разберем это на простом примере из реальной жизни :)
Допустим, ты заболел и несколько дней не был на работе. Ты приходишь в бухгалтерию со своим больничным листом, который тебе должны оплатить.
Если провести аналогию с методами, то у бухгалтера есть метод paySickLeave() ("оплатить больничный"). В качестве параметра ты передаешь в этот метод больничный лист (без него метод не сработает и тебе ничего не заплатят!). Внутри метода с листом производятся необходимые вычисления (бухгалтер считает по нему, сколько компания должна тебе заплатить), и тебе возвращается результат работы, денежная сумма.
Так же работает и программа. Она вызывает метод, передает туда данные и в конце получает результат. Вот метод
main() для нашей программы
BridgeOfficer:
public static void main(String[] args) {
Truck first = new Truck();
first.weight = 10000;
Truck second = new Truck();
second.weight = 20000;
BridgeOfficer officer = new BridgeOfficer(15000);
System.out.println("Грузовик номер 1! Могу я проехать, офицер?");
boolean canFirstTruckGo = officer.checkTruck(first);
System.out.println(canFirstTruckGo);
System.out.println();
System.out.println("Грузовик номер 2! А мне можно?");
boolean canSecondTruckGo = officer.checkTruck(second);
System.out.println(canSecondTruckGo);
}
Мы создаем два грузовика с грузами 10000 и 20000. При этом максимальный вес для моста, где дежурит офицер, 15000.
Программа вызвала метод
officer.checkTruck(first), метод все посчитал и вернул программе результат
true, а программа сохранила его в переменной
boolean canFirstTruckGo. Теперь может делать с ним что хочет (как и ты с деньгами, которые получил от бухгалтера).
В конечном итоге код
boolean canFirstTruckGo = officer.checkTruck(first);
сводится к
boolean canFirstTruckGo = true;
Очень важный момент: оператор
return не только возвращает результат работы метода, но и завершает его работу! Весь код, который написан после return, не будет выполнен!
public boolean checkTruck(Truck truck) {
if (truck.weight > maxWeight) {
return false;
System.out.println("Разворачивайся, перевес!");
} else {
return true;
System.out.println("Порядок, проезжай!");
}
}
Фразы, которые говорит офицер, не будут выведены в консоль, потому что метод уже вернул результат и завершил работу! Программа вернулась в ту точку, где вызывался метод.
Тебе не придется следить за этим самому: компилятор Java достаточно умен и выдаст ошибку при попытке написать код после
return.
Перегрузка методов (Мстители: война параметров)
Бывают ситуации, когда в нашей программе нужно несколько вариантов работы метода.
Почему бы нам не создать свой собственный искусственный интеллект? У Amazon есть Alexa, у Яндекса Алиса, так чем мы хуже? :)
В фильме про Железного Человека Тони Старк создал собственный выдающийся искусственный интеллект, J.A.R.V.I.S.
Отдадим должное прекрасному персонажу и назовем наш ИИ в его честь :)
Первое, чему мы должны научить Джарвиса, здороваться с людьми, заходящими в комнату (будет странно, если столь великий интеллект окажется невежливым).
public class Jarvis {
public void sayHi(String name) {
System.out.println("Добрый вечер, " + name + ", как ваши дела?");
}
public static void main(String[] args) {
Jarvis jarvis = new Jarvis();
jarvis.sayHi("Тони Старк");
}
}
Вывод в консоль:
Добрый вечер, Тони Старк, как ваши дела?
Отлично! Джарвис умеет приветствовать вошедшего. Чаще всего, конечно, это будет его хозяин, Тони Старк.
Но ведь он может прийти не один! А наш метод
sayHi() принимает на вход только один аргумент. И, соответственно, сможет поприветствовать только одного из пришедших, а другого проигнорирует. Не очень-то вежливо, согласен? :/
В этом случае, чтобы решить проблему, мы можем просто написать в классе 2 метода с одинаковым названием, но с разными параметрами:
public class Jarvis {
public void sayHi(String firstGuest) {
System.out.println("Добрый вечер, " + firstGuest + ", как ваши дела?");
}
public void sayHi(String firstGuest, String secondGuest) {
System.out.println("Добрый вечер, " + firstGuest + ", " + secondGuest + ", как ваши дела?");
}
}
Это называется
перегрузкой методов. Перегрузка позволяет нашей программе быть более гибкой и учитывать различные варианты работы. Проверим как это работает:
public class Jarvis {
public void sayHi(String firstGuest) {
System.out.println("Добрый вечер, " + firstGuest + ", как ваши дела?");
}
public void sayHi(String firstGuest, String secondGuest) {
System.out.println("Добрый вечер, " + firstGuest + ", " + secondGuest + ", как ваши дела?");
}
public static void main(String[] args) {
Jarvis jarvis = new Jarvis();
jarvis.sayHi("Тони Старк");
jarvis.sayHi("Тони Старк", "Капитан Америка");
}
}
Вывод в консоль:
Добрый вечер, Тони Старк, как ваши дела?
Добрый вечер, Тони Старк, Капитан Америка, как ваши дела?
Отлично, оба варианта сработали:)
Тем не менее, проблему мы не решили! Что, если гостей будет трое? Конечно, мы можем еще раз перегрузить метод
sayHi(), чтобы он принимал имена трех гостей. Но их ведь может быть и 4, и 5. И так до бесконечности.
Нет ли другого способа научить Джарвиса работать с любым количеством имен, без миллиона перегрузок метода
sayHi()? :/
Конечно, есть! Иначе разве была бы Java одним из самых востребованных языков программирования? ;)
Метод с любым числом аргументов (varargs)
Для этого в Java есть специальная запись:
public void sayHi(String...names) {
for (String name: names) {
System.out.println("Добрый вечер, " + name + ", как ваши дела?");
}
}
Запись (
String...names), переданная в качестве параметра, позволяет указать, что в метод передается какое-то количество строк. Мы не оговариваем заранее, сколько их должно быть, поэтому работа метода становится намного более гибкой:
public class Jarvis {
public void sayHi(String...names) {
for (String name: names) {
System.out.println("Добрый вечер, " + name + ", как ваши дела?");
}
}
public static void main(String[] args) {
Jarvis jarvis = new Jarvis();
jarvis.sayHi("Тони Старк", "Капитан Америка", "Черная Вдова", "Халк");
}
}
Вывод в консоль:
Добрый вечер, Тони Старк, как ваши дела?
Добрый вечер, Капитан Америка, как ваши дела?
Добрый вечер, Черная Вдова, как ваши дела?
Добрый вечер, Халк, как ваши дела?
Некоторый код здесь тебе незнаком, но не обращай на это внимания.
Его суть проста: метод перебирает все имена по очереди и приветствует каждого гостя! При этом он сработает при любом количестве переданных строк. Две, десять, хоть тысяча: метод будет стабильно работать с любым количеством гостей. Намного удобнее, чем делать перегрузки для всех возможных вариантов, согласен?:)
Порядок аргументов имеет значение
Допустим, наш метод принимает на вход строку и число:
public class Man {
public static void sayYourAge(String greeting, int age) {
System.out.println(greeting + " " + age);
}
public static void main(String[] args) {
sayYourAge("Мой возраст - ", 33);
sayYourAge(33, "Мой возраст - "); //ошибка!
}
}
Если метод
sayYourAge класса
Man принимает на вход строку и число, значит именно в таком порядке их нужно передавать в программе! Если мы передадим их в другом порядке, компилятор выдаст ошибку и человек не сможет назвать свой возраст.
Кстати, конструкторы, которые мы проходили в прошлой лекции, тоже являются методами! Их тоже можно перегружать (создавать несколько конструкторов с разным набором аргументов) и для них тоже принципиально важен порядок передачи аргументов. Настоящие методы! :)
Передача параметров: по значению и по ссылке (снова про параметры)
Да-да, мы с ними еще не закончили :)
Тема, которую мы рассмотрим сейчас, является очень важной.
С вероятностью 90% об этом будут спрашивать на всех твоих будущих собеседованиях!
Мы поговорим о передаче параметров в методы.
Примитивы передаются по значению
Рассмотрим простой пример:
public class TimeMachine {
public void goToFuture(int currentYear) {
currentYear = currentYear+10;
}
public void goToPast(int currentYear) {
currentYear = currentYear-10;
}
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
int currentYear = 2020;
System.out.println("Какой сейчас год?");
System.out.println(currentYear);
timeMachine.goToPast(currentYear);
System.out.println("А сейчас?");
System.out.println(currentYear);
}
}
Машина времени имеет два метода. Оба принимают на вход число, обозначающее текущий год, и либо увеличивают, либо уменьшают значение (в зависимости от того, хотим мы отправиться в прошлое или в будущее).
Но, как видно из вывода в консоль, метод не сработал!
Вывод в консоль:
Какой сейчас год?
2020
А сейчас?
2020
Мы передали переменную
currentYear в метод
goToPast(), но ее значение не поменялось.
Как было 2020, так и осталось. Но почему? :/
Потому что примитивы в Java передаются в методы по значению.
Что это значит?
Когда мы вызываем метод
goToPast() и передаем туда нашу переменную
int currentYear = 2020, в метод попадает не сама переменная
currentYear, а ее
копия.
Значение этой копии, конечно, тоже равно 2020, но все
изменения, которые происходят с копией, никак не влияют на нашу изначальную переменную currentYear!
Сделаем наш код более подробным и посмотрим, что происходит с
currentYear:
public class TimeMachine {
public void goToFuture(int currentYear) {
currentYear = currentYear+10;
}
public void goToPast(int currentYear) {
System.out.println("Метод goToPast начал работу!");
System.out.println("Значение currentYear внутри метода goToPast (в начале) = " + currentYear);
currentYear = currentYear-10;
System.out.println("Значение currentYear внутри метода goToPast (в конце) = " + currentYear);
}
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
int currentYear = 2020;
System.out.println("Какой год в самом начале работы программы?");
System.out.println(currentYear);
timeMachine.goToPast(currentYear);
System.out.println("А сейчас какой год?");
System.out.println(currentYear);
}
}
Вывод в консоль:
Какой год в самом начале работы программы?
2020
Метод goToPast начал работу!
Значение currentYear внутри метода goToPast (в начале) = 2020
Значение currentYear внутри метода goToPast (в конце) = 2010
А сейчас какой год?
2020
Это наглядно показывает, что та переменная, которая была передана в метод
goToPast(), является лишь копией
currentYear. И изменение копии никак не повлияло на значение "оригинала".
"
Передача по ссылке" носит прямо противоположный смысл.
Объекты передаются по ссылке
Потренируемся на кошках!
В смысле, посмотрим как выглядит передача по ссылке на примере кошек :)
public class Cat {
int age;
public Cat(int age) {
this.age = age;
}
}
Теперь при помощи нашей машины времени мы будем запускать в прошлое и будущее Барсика, первого в мире кота-путешественника во времени!
Изменим класс
TimeMachine, чтобы машина умела работать с объектами
Cat;
public class TimeMachine {
public void goToFuture(Cat cat) {
cat.age += 10;
}
public void goToPast(Cat cat) {
cat.age -= 10;
}
}
Методы теперь меняют не просто переданное число, а поле
age конкретного объекта
Cat.
В случае с примитивами, как ты помнишь, у нас это не получилось: изначальное число не изменилось.
Посмотрим, что будет тут!
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
Cat barsik = new Cat(5);
System.out.println("Сколько лет Барсику в самом начале работы программы?");
System.out.println(barsik.age);
timeMachine.goToFuture(barsik);
System.out.println("А теперь?");
System.out.println(barsik.age);
System.out.println("Елки-палки! Барсик постарел на 10 лет! Живо гони назад!");
timeMachine.goToPast(barsik);
System.out.println("Получилось? Мы вернули коту его изначальный возраст?");
System.out.println(barsik.age);
}
Вывод в консоль:
Сколько лет Барсику в самом начале работы программы?
5
А теперь?
15
Елки-палки! Барсик постарел на 10 лет! Живо гони назад!
Получилось? Мы вернули коту его изначальный возраст?
5
Ого! Теперь метод сработал по-другому: наш кот резко постарел, а потом снова помолодел! :)
Попробуем разобраться почему.
В отличие от примера с примитивами, в случае с объектами в метод передается ссылка на объект. В методы
goToFuture(barsik) и
goToPast(barsik) была передана ссылка на наш исходный объект
barsik.
Поэтому когда внутри методов мы меняем
barsik.age, мы обращаемся к той самой области памяти, где хранится наш объект.
Это ссылка на того самого Барсика, которого мы создали в самом начале.
Это и называется "передачей по ссылке"!
Однако с этими ссылками все не так просто :)
Важный нюанс: копируется сама ссылка
Попробуем изменить наш пример:
public class TimeMachine {
public void goToFuture(Cat cat) {
cat = new Cat(cat.age);
cat.age += 10;
}
public void goToPast(Cat cat) {
cat = new Cat(cat.age);
cat.age -= 10;
}
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
Cat barsik = new Cat(5);
System.out.println("Сколько лет Барсику в самом начале работы программы?");
System.out.println(barsik.age);
timeMachine.goToFuture(barsik);
System.out.println("Барсик отправился в будущее! Его возраст изменился?");
System.out.println(barsik.age);
System.out.println("А если попробовать в прошлое?");
timeMachine.goToPast(barsik);
System.out.println(barsik.age);
}
}
Вывод в консоль:
Сколько лет Барсику в самом начале работы программы?
5
Барсик отправился в будущее! Его возраст изменился?
5
А если попробовать в прошлое?
5
Опять не работает! О_О
Давай разбираться, что произошло :)
Все дело в методах
goToPast/
goToFuture и в механике работы ссылок.
Сейчас внимание! Этот момент является
самым важным в понимании работы ссылок и методов.
На самом деле,
когда мы вызываем метод goToFuture(Cat cat) в него передается не сама ссылка на объект cat, а копия этой ссылки.
То есть в случае,
когда мы передаем объект в метод, ссылок на этот объект становится две.
Это очень важно для понимания происходящего.
Ведь именно поэтому наш последний пример не изменил возраст кота.
В предыдущем примере с изменением возраста мы просто брали внутри метода
goToFuture() переданную ссылку, находили по ней объект в памяти и меняли его возраст (
cat.age += 10).
Теперь же внутри метода
goToFuture() мы создаем новый объект
(cat = new Cat(cat.age)),
и той самой ссылке-копии, которая была передана в метод, присваивается этот объект.
В результате:
- Первая ссылка (
Cat barsik = new Cat(5)) указывает на изначального кота (с возрастом 5)
- После того, как мы передали переменную
cat в метод goToPast(Cat cat) и присвоили ей новый объект, ссылка была скопирована.
После этого у нас и образовалась итоговая ситуация: две ссылки указывают на два разных объекта.
Но возраст мы поменяли только одному из них, тому, что создали внутри метода.
cat.age += 10;
И естественно, выводя в методе
main() на консоль
barsik.age, мы видим, что его возраст не поменялся. Ведь
barsik это переменная-ссылка, которая по-прежнему указывает на старый, изначальный объект с возрастом 5, с которым ничего не произошло. Все наши манипуляции с возрастом производились уже над новым объектом.
Таким образом, получается, что
объекты передаются в методы по ссылке. Копии объектов никогда не создаются автоматически. Если ты передал объект кота в метод и поменял ему возраст, он успешно поменяется.
Но
значения переменных-ссылок копируются при присваивании и/или вызове методов!
Давай повторим здесь абзац про передачу примитивов:
"Когда мы вызываем метод
changeInt() и передаем туда нашу переменную
int x = 15, в метод попадает не сама переменная
x, а ее
копия.
Ведь
все изменения, которые происходят с копией, никак не влияют на нашу изначальную переменную x."
С копированием ссылок все работает точь-в-точь так же!
Ты передаешь объект кота в метод. Если ты будешь что-то делать с самим котом (то есть с объектом в памяти), все изменения успешно пройдут: объект-то у нас как был один, так и остался. Но если внутри метода ты создашь новый объект и сохранишь его в переменную-ссылку, которая является параметром метода, с этого момента у нас два объекта и две переменные-ссылки.
Вот и все! Это было не так просто, возможно тебе пришлось даже прочитать лекцию несколько раз. Но главное, что ты усвоил эту суперважную тему. Тебе еще не раз предстоит столкнуться со спорами (даже среди опытных разработчиков) о том, как же передаются аргументы в Java. Теперь ты точно знаешь, как это работает. Так держать! :)
Коротко разницу между передачей по значению и по ссылке можно свести в таблицу:
| Что передается | Как | Изменения внутри метода |
Примитив (int, double и т.д.) | по значению (копия значения) | не видны снаружи |
| Объект | по ссылке, но копируется сама ссылка на тот же объект | изменение полей объекта видно снаружи; переприсвоение ссылки внутри метода снаружи не видно |
Вопросы и ответы
Что такое метод в Java?
Метод это набор команд, который выполняет операцию, чаще всего над данными объекта: меняет их, преобразует или выводит. Метод можно вызвать по имени и получить результат его работы.
Что делает оператор return?
return возвращает результат работы метода в то место, где метод вызвали, и одновременно завершает метод. Код, написанный после return, не выполнится, компилятор выдаст ошибку.
Что такое перегрузка методов?
Это несколько методов с одинаковым именем, но разным набором параметров. Java выбирает нужный по количеству и типам аргументов. Например,
sayHi(String) и
sayHi(String, String) это два разных метода.
Что такое varargs (String...names)?
Это способ принять любое количество аргументов одного типа. Внутри метода они доступны как массив, который можно перебрать циклом. Удобно, когда число аргументов заранее неизвестно.
Влияет ли порядок аргументов метода?
Да. Если метод объявлен как
sayYourAge(String, int), аргументы нужно передавать именно в таком порядке и тех типах, иначе компилятор выдаст ошибку.
Чем передача по значению отличается от передачи по ссылке в Java?
Примитивы передаются по значению: в метод попадает копия значения, и изменения внутри метода не влияют на оригинал. Объекты передаются по ссылке, поэтому изменение полей объекта внутри метода видно снаружи. Но сама переменная-ссылка тоже копируется: если внутри метода присвоить ей новый объект, оригинальная ссылка не изменится. Это один из самых частых вопросов на собеседованиях.
Читайте также
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ