Давайте дружить в Телеграме: рассказываем про новые фичи, общаемся в комментах, прислушиваемся к вашим идеям Подписаться

Перегрузка и переопределение методов в Java: примеры

Команда Timeweb Cloud
Команда Timeweb Cloud
Наши инженеры, технические писатели, редакторы и маркетологи
09 сентября 2022 г.
5259
9 минут чтения
Средний рейтинг статьи: 2.7

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

Методы можно перегружать и переопределять. Как это делать и в чём разница между этими двумя механизмами — разберёмся в этой статье.

Перегрузка метода

Перегрузка методов в Java — это использование одного имени метода с разными параметрами. 

Чтобы разобраться с этим механизмом, начнём с простого — создадим помощника, который будет здороваться с пользователями.

public class Assistant {

    public void sayHello(String name) {
        System.out.println("Добрый день, " + name + "!");
    }

    public static void main(String[] args) {
        Assistant assistant = new Assistant();
        assistant.sayHello("Михаил");
    }
}

В консоли будет выведена фраза «Добрый день, Михаил!». 

Допустим, Михаил пришёл не один, а с другом Виталием. Сейчас метод реализован так, что помощник поприветствует только Михаила, а Виталия проигнорирует. Чтобы исправить это, реализуем в классе два метода. Имя у них будет одинаковое. Но параметры они принимают разные.

public class Assistant {

    public void sayHello(String firstGuest) {
        System.out.println("Добрый вечер, " + firstGuest + "!");
    }

    public void sayHello(String firstGuest, String secondGuest) {
        System.out.println("Добрый день, " + firstGuest + " и " + secondGuest + "!");
    }
    public static void main(String[] args) {
        Assistant assistant = new Assistant();
        assistant.sayHello("Михаил", "Виталий");
    }
}

Теперь в консоли отобразится фраза «Добрый день, Михаил и Виталий!». 

Мы уже перегрузили sayHello(). Теперь программа стала более гибкой — помощник может приветствовать сразу двух гостей. Но что произойдёт, если придут трое, четверо или пятеро? Проверим:

public class Assistant {

    public void sayHello(String firstGuest) {
        System.out.println("Добрый вечер, " + firstGuest + "!");
    }

    public void sayHello(String firstGuest, String secondGuest) {
        System.out.println("Добрый день, " + firstGuest + " и " + secondGuest + "!");
    }
    public static void main(String[] args) {
        Assistant assistant = new Assistant();
        assistant.sayHello("Михаил", "Виталий", "Марина");
    }
}

В ответ получим ошибку, потому что sayHello() готов принимать только два аргумента. Решение в лоб — перегружать его дальше. Сделать так, чтобы .sayHello() принимал троих, четверых, пятерых и больше гостей. Но это не похоже на гибкую работу программы. Придётся постоянно дописывать код.

Более гибкое решение — передать в качестве параметра аргумент переменной длины (String… names). Это позволит sayHello() принимать любое количество строк. А чтобы выводить в консоль приветствие каждого гостя, используем цикл.

public class Assistant {

    public void sayHello(String firstGuest) {
        System.out.println("Добрый вечер, " + firstGuest + "!");
    }

  public void sayHello(String… names) {

        for (String name: names) {
            System.out.println("Добрый вечер, " + name + "!");
        }
    }

    public static void main(String[] args) {
        Assistant assistant = new Assistant();
        assistant.sayHello("Михаил", "Виталий", "Марина", "Андрей", "Анна");
    }

В консоли отобразится приветствие каждого переданного гостя:

Добрый вечер, Михаил!
Добрый вечер, Виталий!
Добрый вечер, Марина!
Добрый вечер, Андрей!
Добрый вечер, Анна!

Порядок аргументов

В примере выше мы не думали о порядке аргументов, потому что все они были строками. Нет разницы, с кем здороваться сначала — с Михаилом или с Анной.

Но порядок аргументов имеет значение, если метод принимает, например, строку и число. Посмотрите:

public class User {

    public static void sayYourAge(String greeting, int age) {
        System.out.println(greeting + " " + age);
    }

    public static void main(String[] args) {
        sayYourAge(20, "Мой возраст - "); //ошибка!
    }
}

На этапе компиляции возникнет ошибка, потому что при определении sayYourAge() мы задали, что сначала должна быть строка, а затем — число, но аргументы передали в обратном порядке. 

Чтобы исправить ошибку, достаточно передать аргументы в правильном порядке:

public class User {

    public static void sayYourAge(String greeting, int age) {
        System.out.println(greeting + " " + age);
  }

    public static void main(String[] args) {
        sayYourAge("Мой возраст - ", 20);
    }
}

Чтобы избежать ошибок, можно сделать перегрузку порядком параметров. Например, вот так:

public class User {

    public static void sayYourAge(String greeting, int age) {
        System.out.println(greeting + " " + age);
    }
    public static void sayYourAge(int age, String greeting) {
        System.out.println(greeting + " " + age);
    }

    public static void main(String[] args) {
        sayYourAge("Мой возраст - ", 20);
        sayYourAge(20, "Мой возраст - ");
    }
}

Теперь не имеет значения, в каком порядке передавать аргументы — оба варианты будут понятны программе.

Варианты перегрузки

Из примеров выше можно выделить три варианта перегрузки.

  1. По количеству параметров.
public class Calculator {
  void calculate(int number1, int number2) { }
  void calculate(int number1, int number2, int number3) { }
}
  1. По типам параметров:
public class Calculator {
  void calculate(int number1, int number2) { }
  void calculate(double number1, double number2) { }
}
  1. По порядку параметров:
public class Calculator {
  void calculate(double number1, int number2) { }
  void calculate(int number1, double number2) { }
}

Напоследок повторим, что означает перегрузка метода в Java. Это механизм языка, который позволяет создавать несколько методов с одинаковым названием, но разными параметрами. Так можно делать не во всех языках. 

Перегрузка — это часть полиморфизма, одной из ключевых составляющих объектно-ориентированного программирования. Главный плюс перегрузки в Java — можно использовать схожие методы с одинаковыми именами.

Переопределение метода

Переопределение метода в Java позволяет взять метод родительского класса и создать специфическую реализацию в классе-наследнике. 

Проще понять на примере. Допустим, вы создаёте класс Animal с методом voice(). Он нужен для того, чтобы животное могло подать голос:

public class Animal {

   public void voice() {

       System.out.println("Говори!");
   }
}

И сразу возникает проблема — все животные издают разные звуки. Можно создать для каждого отдельный метод. Например, у кошки это будет voiceCat(), а у собаки — voiceDog(). Но представьте, сколько строк кода придётся написать, чтобы дать возможность всем животным подать голос?

Здесь на помощь и приходит механизм переопределения в Java. Он позволяет заменить реализацию в классе-наследнике. Посмотрим на примере кошки и собаки:

public class Cat extends Animal {

   @Override
   public void voice() {
       System.out.println("Мяу!");
   }
}

public class Dog extends Animal {

   @Override
   public void voice() {
       System.out.println("Гав!");
   }
}

В выводе отобразится сначала «Мяу», а затем — «Гав». Чтобы добиться такого результата, нужно:

  1. В классе-наследнике создать метод с таким же именем, как в родительском классе.
  2. Добавить перед ним аннотацию @Override (с английского переводится как «переопределён»). Эта аннотация сообщит компилятору, что это не ошибка, вы намеренно переопределяете метод. Отметим, что наличие аннотации необязательно. Если в дочернем классе создать метод с такой же сигнатурой, как у родительского, метод все равно переопределится. Но рекомендуется ставить аннотацию всегда, так как это улучшает «читабельность» кода, а также при этом компилятор проверит на этапе сборки, что такой метод действительно есть в родительском классе.

Собственная реализация пишется для каждого класса-наследника. Если этого не сделать, то будет использована реализация родительского класса. 

Даже после переопределения вы можете обратиться к методу родительского класса при условии, что он не определён модификатором private. Для этого используется ключевое слово super:

super.method();

Ограничения при переопределении

У переопределения методов класса в Java есть ряд ограничений.

  • Название метода должно быть таким же, как у метода родителя (то есть сигнатура метода должна быть одинаковой).
  • Аргументы должны оставаться такими же, как у метода родителя. 
  • Тип возвращаемого значения должен быть таким же, как у метода родителя.
  • Модификатор доступа должен быть таким же, как у метода родителя.
  • Окончательные методы (final) нельзя переопределять. Это один из способов запрета переопределения — объявить метод с помощью ключевого слова final.

Например:

class Parent {
    final void show() {}
}

class Child extends Parent {
    void show() {}
}

Такой код вернёт ошибку, потому что в родительском классе использовано ключевое слово final.

  • Статические методы (static) нельзя переопределять. Если вы определите в классе-наследнике такую же сигнатуру метода, как в родительском классе, то выполните сокрытие. Подробнее об этом вы можете прочитать в документации.
  • Приватные методы (private) нельзя переопределять, так как они связываются на этапе компиляции, а не выполнения.
  • Нельзя сужать модификатор доступа — например, с public до private. Расширение уровня доступа возможно.
  • Нельзя менять тип возвращаемого значения, однако можно сузить возвращаемое значение, если они совместимы.

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

Собственные правила переопределения есть у отдельных методов. Например, equals() и hashCode(). Самое важное условие — если вы переопределяете equals(), то должны переопределить и hashCode(). В противном случае классы и методы, которые пользуются контрактами стандартной реализации этих двух методов, могут работать с ошибками. Подробнее от этом мы рассказали в отдельной статье

Заключение

Переопределение и перегрузка методов в Java — важные части полиморфизма, однако это разные механизмы. При перегрузке вы создаёте внутри одного класса много методов с одинаковым названием, но разными параметрами. При переопределении вы берёте один и тот же метод и заставляете его делать разные вещи в зависимости от того, в каком классе его вызвали.

Но есть характеристики, в которых перегрузка и переопределение Java похожи. Оба механизма помогают сделать код чище и читабельнее, а также уменьшить количество ошибок при выполнении программ.

Зарегистрируйтесь и начните пользоваться
сервисами Timeweb Cloud прямо сейчас

15 лет опыта
Сосредоточьтесь на своей работе: об остальном позаботимся мы
165 000 клиентов
Нам доверяют частные лица и компании, от небольших фирм до корпораций
Поддержка 24/7
100+ специалистов поддержки, готовых помочь в чате, тикете и по телефону