Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Типы полиморфизма в 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"
}
}
Сравнение типов полиморфизма
| Тип | Время разрешения | Гибкость | Производительность |
|---|---|---|---|
| Overloading | Compile-time | Низкая | Отличная |
| Overriding | Runtime | Высокая | Хорошая |
| Generics | Compile-time | Средняя | Отличная (erasure) |
| Интерфейсы | Runtime | Очень высокая | Хорошая |
Best Practices
- Предпочитай интерфейсы абстрактным классам - более гибко
- Используй generics для type-safety - ошибки на compile time
- Не переусложняй перегрузку - может запутать
- Используй @Override аннотацию - помогает избежать ошибок
- Следуй принципу Liskov Substitution - подклассы должны быть заменяемы
Полиморфизм - это ключ к гибкому и масштабируемому коду. Правильное использование каждого типа полиморфизма делает архитектуру более гибкой и поддерживаемой!