Полиморфизм на Яве
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 .