Рубрики
Без рубрики

Полиморфизм на Яве

Откройте для себя полиморфизм на Java.

Автор оригинала: baeldung.

Полиморфизм на Яве

1. Обзор

Все языки объектно-ориентированного программирования (OOP) должны проявлять четыре основные характеристики: абстракция, инкапсуляция, наследование и полиморфизм.

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

2. Статический полиморфизм

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

Например, наша ТекстФиле класс в приложении файлового менеджера может иметь три метода с одинаковой подписью читать () метод:

public class TextFile extends GenericFile {
    //...

    public String read() {
        return this.getContent()
          .toString();
    }

    public String read(int limit) {
        return this.getContent()
          .toString()
          .substring(0, limit);
    }

    public String read(int start, int stop) {
        return this.getContent()
          .toString()
          .substring(start, stop);
    }
}

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

3. Динамический полиморфизм

С динамическим полиморфизмом Java Virtual Machine (JVM) обрабатывает обнаружение соответствующего метода для выполнения, когда подкласс присваивается его родительской форме . Это необходимо, поскольку подкласс может переопределить некоторые или все методы, определенные в родительском классе.

В гипотетическом приложении файлового менеджера давайте определим родительский класс для всех файлов, называемых ОбщиеФиле :

public class GenericFile {
    private String name;

    //...

    public String getFileInfo() {
        return "Generic File Impl";
    }
}

Мы также можем реализовать ImageFile класс, который расширяет ОбщиеФиле но переопределяет getFileInfo() метод и приложения более подробную информацию:

public class ImageFile extends GenericFile {
    private int height;
    private int width;

    //... getters and setters
    
    public String getFileInfo() {
        return "Image File Impl";
    }
}

Когда мы создаем экземпляр ImageFile и присвоить его ОбщиеФиле класс, неявный литые делается. Тем не менее, JVM сохраняет ссылку на фактическую форму ImageFile .

Вышеупомяпная конструкция аналогична методу переопределения. Мы можем подтвердить это, ссылаясь на getFileInfo() метод по:

public static void main(String[] args) {
    GenericFile genericFile = new ImageFile("SampleImageFile", 200, 100, 
      new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB)
      .toString()
      .getBytes(), "v1.0.0");
    logger.info("File Info: \n" + genericFile.getFileInfo());
}

Как и ожидалось, genericFile.getFileInfo () запускает getFileInfo() метод ImageFile класс, как поменьше на выходе ниже:

File Info: 
Image File Impl

4. Другие полиморфные характеристики на Java

В дополнение к этим двум основным типам полиморфизма в Java, есть и другие характеристики в языке программирования Java, которые демонстрируют полиморфизм. Давайте обсудим некоторые из этих характеристик.

4.1. Принуждение

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

String str = "string" + 2;

4.2. Перегрузка оператора

Перегрузка оператора или метода относится к полиморфной характеристике одного и того же символа или оператора, имеющих различные значения (формы) в зависимости от контекста.

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

String str = "2" + 2;
int sum = 2 + 2;
System.out.printf(" str = %s\n sum = %d\n", str, sum);

выпуск:

str = 22
sum = 4

4.3. Полиморфные параметры

Параметрический полиморфизм позволяет ассоциировать название параметра или метода в классе с различными типами. У нас есть типичный пример ниже, где мы определяем содержание в качестве Струнные а затем в качестве Интегер :

public class TextFile extends GenericFile {
    private String content;
    
    public String setContentDelimiter() {
        int content = 100;
        this.content = this.content + content;
    }
}

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

Для решения этой проблемы часто рекомендуется использовать глобальные ссылки, такие как этот ключевое слово, чтобы указать на глобальные переменные в локальном контексте.

4.4. Полиморфные подтипы

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

Например, если у нас есть коллекция ОбщиеФиле s и мы ссылаемся на getInfo () метод на каждом из них, мы можем ожидать, что выход будет отличаться в зависимости от подтипа, из которого каждый элемент в коллекции был получен:

GenericFile [] files = {new ImageFile("SampleImageFile", 200, 100, 
  new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB).toString() 
  .getBytes(), "v1.0.0"), new TextFile("SampleTextFile", 
  "This is a sample text content", "v1.0.0")};
 
for (int i = 0; i < files.length; i++) {
    files[i].getInfo();
}

Полиморфизм подтипа становится возможным благодаря сочетанию upcasting и поздний обязательный . Upcasting включает в себя отливку иерархии наследования от супертипа к подтипу:

ImageFile imageFile = new ImageFile();
GenericFile file = imageFile;

В результате эффект выше, что ImageFile- конкретные методы не могут быть вызваны на новом ОбщиеФиле . Однако методы в подтипе переопределяют аналогичные методы, определенные в супертипе.

Чтобы решить проблему не в состоянии вызвать подтип-специфических методов при upcasting к супертипу, мы можем сделать вниз наследования от супертипа к подтипу. Это делается:

ImageFile imageFile = (ImageFile) file;

Поздний обязательный стратегия помогает компилятору решить, чей метод вызвать после . В случае, если я mageFile-getInfo vs file’getInfo в приведеном выше примере компилятор сохраняет ссылку на ImageFile ‘ы getInfo метод.

5. Проблемы с полиморфизмом

Давайте посмотрим на некоторые двусмысленности в полиморфизме, которые потенциально могут привести к ошибкам времени выполнения, если не должным образом проверены.

5.1. Идентификация типа во время downcasting

Напомним, что ранее мы потеряли доступ к некоторым подтип-специфическим методам после выполнения upcast. Хотя мы смогли решить это с downcast, это не гарантирует фактической проверки типа.

Например, если мы выполняем upcast и последующее вниз:

GenericFile file = new GenericFile();
ImageFile imageFile = (ImageFile) file;
System.out.println(imageFile.getHeight());

Мы замечаем, что компилятор допускает ОбщиеФиле в ImageFile , хотя класс на самом деле является ОбщиеФиле а не ImageFile .

Следовательно, если мы попытаемся вызвать getHeight() метод на imageFile класса, мы получаем ClassCastException как ОбщиеФиле не определяет getHeight() метод:

Exception in thread "main" java.lang.ClassCastException:
GenericFile cannot be cast to ImageFile

Чтобы решить эту проблему, JVM выполняет проверку типа Run-Time (RTTI). Мы также можем попытаться точно идентифицировать тип с помощью instanceof ключевое слово так же, как это:

ImageFile imageFile;
if (file instanceof ImageFile) {
    imageFile = file;
}

Вышесказанного помогает избежать ClassCastException исключение во время выполнения. Другой вариант, который может быть использован, это упаковка литых в попробуйте и поймать блокировать и ловить ClassCastException.

Следует отметить, что Проверка RTTI стоит дорого из-за времени и ресурсов, необходимых для эффективной проверки правильности типа. Кроме того, частое использование instanceof ключевое слово почти всегда подразумевает плохой дизайн.

5.2. Хрупкая проблема базового класса

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

Рассмотрим декларацию суперкласса под названием ОбщиеФиле и его подкласс ТекстФиле :

public class GenericFile {
    private String content;

    void writeContent(String content) {
        this.content = content;
    }
    void toString(String str) {
        str.toString();
    }
}
public class TextFile extends GenericFile {
    @Override
    void writeContent(String content) {
        toString(content);
    }
}

Когда мы изменяем ОбщиеФиле класс:

public class GenericFile {
    //...

    void toString(String str) {
        writeContent(str);
    }
}

Мы отмечаем, что вышеупомяпная модификация ТекстФиле в бесконечной рекурсии в writeContent () метод, который в конечном итоге приводит к переполнению стека.

Для решения хрупкой проблемы базового класса мы можем использовать окончательный ключевое слово, чтобы предотвратить подклассы от переопределения writeContent () метод. Правильная документация также может помочь. И последнее, но не менее то, что состав, как правило, следует предпочтительным, чем наследство.

6. Заключение

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

Как всегда, исходный код для этой статьи доступен более на GitHub .