Полиморфизм в Java

Полиморфизм — возможность использовать одно и то же имя для разных реализаций методов или конструкторов.

В Java полиморфизм реализуется с помощью перегрузки (overloading) и переопределения (overriding).

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

Возникает закономерный вопрос: когда мы воспользуемся одним из таких методов с одинаковым именем, как компилятор определяет, какую реализацию выбрать? Ответ прост — он делает это на основании числа и типов переданных аргументов.

Рассмотрим пример перегрузки конструкторов:

class Animal { private String eats; private int noOfLegs; //Конструктор без параметров //(называется конструктором по умолчанию). public Animal(){} //Конструктор с одним параметром //задает значение только полю eats. public Animal(String food){ this.eats = food; } //Конструктор с несколькими параметрами //задает значения обоим полям. public Animal(String food, int legs){ this.eats = food; this.noOfLegs = legs; } //сеттеры и геттеры public String getEats() { return eats; } public void setEats(String eats) { this.eats = eats; } public int getNoOfLegs() { return noOfLegs; } public void setNoOfLegs(int noOfLegs) { this.noOfLegs = noOfLegs; } } class Polimorphism { public static void main(String[] args) { //Какой же из конструкторов //Animal, которые мы определили будет использован //при создании объекта животного? //Ведь у них же всех одинаковые имена. //На самом деле, можно просто выбрать //нужный конструктор передав в него //нужное количество параметров. //То есть, как видим, в строке кода ниже //вызывается конструктор с одним параметром. Animal animal = new Animal(“Grass”); //Значит будет использован конструктор //public Animal(String food), ведь в конструктор //передаётся всего один параметр. //В выводе увидим, что строка Grass успешно //записалась в поле объекта animal. System.out.println(animal.getEats()); } }

Вывод:

Даже несмотря на то, что у всех конструкторов одинаковое имя Animal, компилятор различает их по параметрам. Это и есть пример перегрузки конструктора, которая является одной из форм полиморфизма.

Однако помним, что полиморфизм может применяться не только к конструкторам, но и к методам.


Переопределение метода родителя в классе-наследнике

Под полиморфизмом также понимается переопределение метода родительского класса в классе-наследнике.

Например, ниже в классе Animal (Животное) определён метод move() (Движение), который просто сообщает, что животное может двигаться. Его реализация — вывод строки "The animal moves." — является универсальной и подходит для любого животного. Такой метод вполне может использоваться без изменений в классах-наследниках.

Однако в некоторых случаях желательно уточнить поведение метода. Например, если мы создадим класс Cat (Кот), который наследуется от Animal, то он унаследует и метод move(). Но в контексте кошки может быть полезно уточнить поведение этого метода, поскольку, разумеется, куда лучше, если move() будет описывать именно движения, характерные для кошки — например, "The cat gracefully walks and jumps." — а не просто общее "The animal moves."

В таких случаях метод move() можно переопределить в классе-наследнике, чтобы сделать поведение более конкретным и реалистичным.

Это и есть проявление полиморфизма: один и тот же метод, но разная реализация в разных классах.

Рассмотрим пример:

class Animal { private String eats; private int noOfLegs; public Animal(){} public Animal(String food){ this.eats = food; } public Animal(String food, int legs){ this.eats = food; this.noOfLegs = legs; } public String getEats() { return eats; } public void setEats(String eats) { this.eats = eats; } public int getNoOfLegs() { return noOfLegs; } public void setNoOfLegs(int noOfLegs) { this.noOfLegs = noOfLegs; } // Метод с базовой реализацией, который, // желательно, чтобы наследники переопределили. public void move() { System.out.println(“The animal moves.”); } } class Cat extends Animal{ private String name; private String color; Cat(){} Cat (String catName, String catColor) { name = catName; color = catColor; System.out.println(color); } public String getName(){ return name; } public void setName(String catName){ name = catName; } public String getColor(){ return color; } public void setColor(String catColor){ color = catColor; } public void move(){ //Переопределяем метод move. //Теперь движения кошки будут выводится как: //”The cat gracefully walks and jumps.”. System.out.println(“The cat gracefully walks and jumps.”); } } class Polimorphism2 { public static void main(String[] args) { Cat cat = new Cat(); // Воспользуемся методом move. cat.move(); // Если бы метод move не был переопределён, // на консоль вывелось бы “The animal moves.” // Но для кошки корректнее — “The cat gracefully // walks and jumps.” } }

Вывод:

Каждое животное, наследующее класс Animal, может по-своему переопределить метод move(). Так, у птицы это может быть “летает”, у собаки — “бежит”, и так далее.

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

Абстрактный класс

Разберитесь с абстрактными классами в Java: общие концепты, абстрактные методы, ключевое слово abstract. Практические примеры.

Time to read: 16

Интерфейсы в Java

Изучите интерфейсы в Java: как определять поведение классов, ключевые слова interface и implements, отличие от абстрактных классов. Примеры кода и сравнение.

Time to read: 13

Статические поля

Статические поля и методы в Java: как они работают, общие для всех объектов класса, вызов без создания экземпляра и практические примеры кода.

Time to read: 10