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

Полиморфизм (греч. «много форм») — третий принцип ООП. В Java он проявляется в том, что ссылка одного типа (обычно — общего родительского) может указывать на объекты разных подклассов, и при вызове метода через эту ссылку выполнится та реализация, которая определена в фактическом классе объекта. Это называется динамической диспетчеризацией.

Полиморфизм позволяет писать обобщённый код: метод принимает параметр типа Device, а работает с любым его наследником — Led, Motor, Sensor — не зная о них заранее. Чтобы добавить новый вид устройства, не нужно менять старый код — достаточно создать новый класс-наследник и переопределить нужные методы.

Различают два вида полиморфизма:

  • Статический (compile-time) — перегрузка методов.

  • Динамический (runtime) — переопределение методов в подклассах, о котором эта статья.

Пример 1. Метод вызывается по фактическому типу

class Device {
    void run() {
        System.out.println("Device.run");
    }
}

class Led extends Device {
    @Override
    void run() {
        System.out.println("Led.run — blink");
    }
}

public class Motor extends Device {
    @Override
    void run() {
        System.out.println("Motor.run — rotate");
    }

    public static void main(String[] args) {
        Device a = new Device();
        Device b = new Led();
        Device c = new Motor();
        a.run();
        b.run();
        c.run();
    }
}

Output:

Device.run
Led.run — blink
Motor.run — rotate

Пример 2. Массив разных устройств

class Device {
    void info() {
        System.out.println("[Device]");
    }
}

class Sensor extends Device {
    @Override
    void info() { System.out.println("[Sensor]"); }
}

class Servo extends Device {
    @Override
    void info() { System.out.println("[Servo]"); }
}

public class Hub {
    public static void main(String[] args) {
        Device[] all = { new Device(), new Sensor(), new Servo() };
        for (Device d : all) {
            d.info();
        }
    }
}

Output:

[Device]
[Sensor]
[Servo]

Пример 3. Метод принимает базовый тип

class Device {
    void start() {
        System.out.println("device starts");
    }
}

class Led extends Device {
    @Override
    void start() { System.out.println("led on"); }
}

class Motor extends Device {
    @Override
    void start() { System.out.println("motor spins"); }
}

public class Bus {
    static void launch(Device d) {
        d.start();
    }

    public static void main(String[] args) {
        launch(new Led());
        launch(new Motor());
        launch(new Device());
    }
}

Output:

led on
motor spins
device starts

Пример 4. instanceof и приведение типа

class Device {
    String name = "?";
}

class Sensor extends Device {
    int value = 42;
}

public class Motor extends Device {
    int speed = 100;

    public static void main(String[] args) {
        Device[] all = { new Sensor(), new Motor() };
        for (Device d : all) {
            if (d instanceof Sensor) {
                Sensor s = (Sensor) d;
                System.out.println("Sensor val=" + s.value);
            } else if (d instanceof Motor) {
                Motor m = (Motor) d;
                System.out.println("Motor speed=" + m.speed);
            }
        }
    }
}

Output:

Sensor val=42
Motor speed=100

Пример 5. toString — переопределение метода Object

public class Led {
    int pin;
    boolean on;

    public Led(int pin, boolean on) {
        this.pin = pin;
        this.on = on;
    }

    @Override
    public String toString() {
        return "Led{pin=" + pin + ", on=" + on + "}";
    }

    public static void main(String[] args) {
        Led a = new Led(13, true);
        Led b = new Led(9, false);
        System.out.println(a);
        System.out.println("b=" + b);
    }
}

Output:

Led{pin=13, on=true}
b=Led{pin=9, on=false}

Пример 6. Поля не полиморфны

class Device {
    String name = "Device";
}

public class Sensor extends Device {
    String name = "Sensor";

    public static void main(String[] args) {
        Sensor s = new Sensor();
        Device d = s;
        System.out.println("s.name=" + s.name);
        System.out.println("d.name=" + d.name); // обращение к полю — по типу ссылки
    }
}

Output:

s.name=Sensor
d.name=Device

Подводные камни

  • Поля решаются статически по типу ссылки, а методы — динамически по фактическому классу объекта. Не полагайтесь на «скрытые» поля.

  • static-методы тоже не полиморфны: они привязаны к классу, а не к объекту.

  • private и final методы переопределить нельзя — они также вызываются «по типу ссылки».

  • Безопасное приведение типа делайте после instanceof, иначе словите ClassCastException.

  • Чрезмерные instanceof + cast — признак того, что вы не используете полиморфизм по назначению. Часто лучше добавить нужный метод в базовый класс или интерфейс.

См. также

Примечание

Лицензия и источники

Концепция описана на основе официального Java Tutorial от Oracle (https://docs.oracle.com/javase/tutorial/), Oracle Free Documentation License. Перевод, примеры и пояснения — © AlashEd Wiki.