← Назад к вопросам

Какие знаешь типы полиморфизма?

1.6 Junior🔥 161 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Типы полиморфизма в Java

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

1. Полиморфизм подтипов (Subtype Polymorphism)

Основной вид полиморфизма - использование объекта подкласса как объекта родительского класса.

// Родительский класс
public abstract class Animal {
  public abstract void makeSound();
  
  public void sleep() {
    System.out.println("Zzz...");
  }
}

// Подклассы
public class Dog extends Animal {
  @Override
  public void makeSound() {
    System.out.println("Woof!");
  }
}

public class Cat extends Animal {
  @Override
  public void makeSound() {
    System.out.println("Meow!");
  }
}

// Использование полиморфизма
public class AnimalHandler {
  public void handleAnimal(Animal animal) {
    // Одна переменная может содержать Dog или Cat
    animal.makeSound();  // Вызывается нужный метод
    animal.sleep();      // Общий метод всех животных
  }
  
  public static void main(String[] args) {
    List<Animal> animals = new ArrayList<>();
    animals.add(new Dog());
    animals.add(new Cat());
    animals.add(new Dog());
    
    AnimalHandler handler = new AnimalHandler();
    for (Animal animal : animals) {
      handler.handleAnimal(animal);  // Полиморфное поведение
    }
  }
}

Вывод:

Woof!
Zzz...
Meow!
Zzz...
Woof!
Zzz...

2. Перегрузка методов (Method Overloading)

Несколько методов с одинаковым именем, но разными параметрами. Это compile-time полиморфизм (static dispatch).

public class Calculator {
  
  // Перегрузка 1: два int
  public int add(int a, int b) {
    return a + b;
  }
  
  // Перегрузка 2: два double
  public double add(double a, double b) {
    return a + b;
  }
  
  // Перегрузка 3: три int
  public int add(int a, int b, int c) {
    return a + b + c;
  }
  
  // Перегрузка 4: массив int
  public int add(int... numbers) {
    int sum = 0;
    for (int num : numbers) {
      sum += num;
    }
    return sum;
  }
  
  // Перегрузка 5: List
  public int add(List<Integer> numbers) {
    return numbers.stream().mapToInt(Integer::intValue).sum();
  }
}

// Использование
Calculator calc = new Calculator();
System.out.println(calc.add(5, 3));           // int
System.out.println(calc.add(5.5, 3.2));       // double
System.out.println(calc.add(1, 2, 3));        // три int
System.out.println(calc.add(1, 2, 3, 4, 5)); // varargs
System.out.println(calc.add(List.of(1,2,3))); // List

Правила перегрузки:

  • Разное количество параметров
  • Разные типы параметров
  • Разный порядок типов параметров
  • НЕ просто разный return type

3. Переопределение методов (Method Overriding)

Подкласс предоставляет реализацию для метода из родительского класса. Это runtime полиморфизм (dynamic dispatch).

public class Shape {
  public void draw() {
    System.out.println("Drawing shape");
  }
  
  public double calculateArea() {
    return 0;
  }
}

public class Circle extends Shape {
  private double radius;
  
  public Circle(double radius) {
    this.radius = radius;
  }
  
  @Override  // Аннотация для проверки переопределения
  public void draw() {
    System.out.println("Drawing circle");
  }
  
  @Override
  public double calculateArea() {
    return Math.PI * radius * radius;
  }
}

public class Rectangle extends Shape {
  private double width, height;
  
  public Rectangle(double width, double height) {
    this.width = width;
    this.height = height;
  }
  
  @Override
  public void draw() {
    System.out.println("Drawing rectangle");
  }
  
  @Override
  public double calculateArea() {
    return width * height;
  }
}

// Runtime полиморфизм - решение принимается в runtime
public class GeometryApp {
  public static void drawAndMeasure(Shape shape) {
    shape.draw();  // Вызывается реальный класс
    System.out.println("Area: " + shape.calculateArea());
  }
  
  public static void main(String[] args) {
    List<Shape> shapes = new ArrayList<>();
    shapes.add(new Circle(5));
    shapes.add(new Rectangle(3, 4));
    shapes.add(new Circle(2));
    
    for (Shape shape : shapes) {
      drawAndMeasure(shape);  // Полиморфное поведение в runtime
    }
  }
}

Вывод:

Drawing circle
Area: 78.5
Drawing rectangle
Area: 12.0
Drawing circle
Area: 12.56

4. Интерфейсы и полиморфизм

Полиморфизм через интерфейсы - более гибкий чем через наследование.

// Интерфейс
public interface Drawable {
  void draw();
}

public interface Measurable {
  double getArea();
}

// Реализация
public class Circle implements Drawable, Measurable {
  private double radius;
  
  public Circle(double radius) {
    this.radius = radius;
  }
  
  @Override
  public void draw() {
    System.out.println("Drawing circle");
  }
  
  @Override
  public double getArea() {
    return Math.PI * radius * radius;
  }
}

public class Text implements Drawable {
  private String content;
  
  public Text(String content) {
    this.content = content;
  }
  
  @Override
  public void draw() {
    System.out.println("Drawing text: " + content);
  }
}

// Полиморфизм через интерфейсы
public class DrawingApp {
  public void renderAll(List<Drawable> items) {
    for (Drawable item : items) {
      item.draw();  // Работает с любым Drawable
    }
  }
  
  public double calculateTotalArea(List<Measurable> items) {
    return items.stream()
      .mapToDouble(Measurable::getArea)
      .sum();
  }
}

5. Параметрический полиморфизм (Generics)

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

// Generic класс
public class Box<T> {
  private T content;
  
  public void put(T item) {
    this.content = item;
  }
  
  public T get() {
    return content;
  }
  
  public Class<?> getType() {
    return content.getClass();
  }
}

// Generic метод
public class GenericUtils {
  public static <T> T getFirst(List<T> list) {
    return list.isEmpty() ? null : list.get(0);
  }
  
  public static <T extends Comparable<T>> T getMax(List<T> list) {
    if (list.isEmpty()) return null;
    T max = list.get(0);
    for (T item : list) {
      if (item.compareTo(max) > 0) {
        max = item;
      }
    }
    return max;
  }
  
  // Wildcard - неизвестный тип
  public static void printList(List<?> list) {
    for (Object item : list) {
      System.out.println(item);
    }
  }
  
  // Bounded wildcard
  public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number num : numbers) {
      sum += num.doubleValue();
    }
    return sum;
  }
}

// Использование
public static void main(String[] args) {
  Box<String> stringBox = new Box<>();
  stringBox.put("Hello");
  System.out.println(stringBox.get());  // Hello
  
  Box<Integer> intBox = new Box<>();
  intBox.put(42);
  System.out.println(intBox.get());     // 42
  
  List<String> strings = Arrays.asList("apple", "banana", "cherry");
  System.out.println(GenericUtils.getFirst(strings));  // apple
  System.out.println(GenericUtils.getMax(strings));    // cherry
  
  List<Integer> integers = Arrays.asList(1, 5, 3, 9, 2);
  System.out.println(GenericUtils.getMax(integers));   // 9
  System.out.println(GenericUtils.sumNumbers(integers)); // 20.0
}

6. Ad-hoc полиморфизм (Operator overloading через методы)

В Java нельзя перегружать операторы напрямую, но можно через методы.

public class Vector {
  private double x, y;
  
  public Vector(double x, double y) {
    this.x = x;
    this.y = y;
  }
  
  // Эмулируем сложение
  public Vector add(Vector other) {
    return new Vector(this.x + other.x, this.y + other.y);
  }
  
  // Эмулируем умножение на скаляр
  public Vector multiply(double scalar) {
    return new Vector(this.x * scalar, this.y * scalar);
  }
  
  // toString для удобства
  @Override
  public String toString() {
    return String.format("(%.2f, %.2f)", x, y);
  }
}

// Использование
public static void main(String[] args) {
  Vector v1 = new Vector(1, 2);
  Vector v2 = new Vector(3, 4);
  
  Vector sum = v1.add(v2);        // (4.00, 6.00)
  Vector scaled = v1.multiply(2); // (2.00, 4.00)
  
  System.out.println(sum);
  System.out.println(scaled);
}

7. Включение типов (Type Inclusion - мощный вид полиморфизма)

Объект может принадлежать нескольким типам одновременно.

public interface Swimmer {
  void swim();
}

public interface Flyer {
  void fly();
}

public class Duck implements Swimmer, Flyer {
  @Override
  public void swim() {
    System.out.println("Duck swimming");
  }
  
  @Override
  public void fly() {
    System.out.println("Duck flying");
  }
}

// Полиморфизм - один объект, разные интерфейсы
public static void main(String[] args) {
  Duck duck = new Duck();
  
  // Одна переменная
  Swimmer swimmer = duck;  // Duck как Swimmer
  Flyer flyer = duck;      // Duck как Flyer
  
  swimmer.swim();  // Duck swimming
  flyer.fly();     // Duck flying
}

8. Dynamic Dispatch vs Static Dispatch

public class DispatchExample {
  
  static class Parent {
    public void method() {
      System.out.println("Parent");
    }
    
    public static void staticMethod() {
      System.out.println("Parent static");
    }
  }
  
  static class Child extends Parent {
    @Override
    public void method() {  // Dynamic dispatch
      System.out.println("Child");
    }
    
    public static void staticMethod() {  // Static dispatch (скрывает, не переопределяет)
      System.out.println("Child static");
    }
  }
  
  public static void main(String[] args) {
    Parent obj = new Child();
    
    obj.method();           // Dynamic dispatch -> "Child" (runtime решение)
    obj.staticMethod();     // Static dispatch -> "Parent static" (compile time решение)
    
    Child obj2 = new Child();
    obj2.method();          // -> "Child"
    obj2.staticMethod();    // -> "Child static"
  }
}

Сравнение типов полиморфизма

ТипВремя разрешенияГибкостьПроизводительность
OverloadingCompile-timeНизкаяОтличная
OverridingRuntimeВысокаяХорошая
GenericsCompile-timeСредняяОтличная (erasure)
ИнтерфейсыRuntimeОчень высокаяХорошая

Best Practices

  1. Предпочитай интерфейсы абстрактным классам - более гибко
  2. Используй generics для type-safety - ошибки на compile time
  3. Не переусложняй перегрузку - может запутать
  4. Используй @Override аннотацию - помогает избежать ошибок
  5. Следуй принципу Liskov Substitution - подклассы должны быть заменяемы

Полиморфизм - это ключ к гибкому и масштабируемому коду. Правильное использование каждого типа полиморфизма делает архитектуру более гибкой и поддерживаемой!