Ответ 1
Лучшее объяснение по теме, которое я когда-либо читал, - это статья Luca Cardelli, известного теоретика типа. Статья называется О понятиях понимания, абстракции данных и полиморфизмах.
Типы полиморфизма
Карделли определяет несколько типов полиморфизма в этой статье:
- Универсальные
- параметрическое
- Включение
- Ad-Hoc
- перегрузка
- принуждение
Тип полиморфизма, связанного с наследованием, классифицируется как полиморфизм включения или полиморфизм подтипа.
Wikipedia обеспечивает хорошее определение:
В объектно-ориентированном программировании подтип полиморфизма или включения полиморфизм - это понятие в теории типов, в котором имя может обозначать экземпляры многих разных классов, если они связаны какой-то общий супер класс. Полиморфизм включения обычно поддерживаемые посредством подтипирования, то есть объекты разных типов полностью подставляемый для объектов другого типа (их база тип (ы)) и, таким образом, может обрабатываться через общий интерфейс. Альтернативно, полиморфизм включения может быть достигнут посредством типа принуждение, также известное как литье типа.
Еще одна статья в Википедии под названием Полиморфизм в объектно-ориентированном программировании также отвечает на ваши вопросы.
В Java
Эта функция подтипирования в Java достигается, среди прочего, путем наследования классов и интерфейсов. Хотя функции подтипирования Java могут быть не очевидны с точки зрения наследования все время. Возьмем, к примеру, случаи ковариации и контравариантности с дженериками. Кроме того, массивы являются Serializable и Cloneable, хотя это не видно нигде в иерархии типов. Можно также сказать, что с помощью примитивного расширения преобразования числовые операторы в Java являются полиморфными, в некоторых случаях даже принимают абсолютно не связанные операнды (т.е. Объединение строк и чисел или строки плюс некоторый другой объект). Рассмотрим также случаи бокса и распаковки примитивов. Эти последние случаи полиморфизма (принуждения и перегрузки) отнюдь не связаны с наследованием.
Примеры
Включение
List<Integer> myInts = new ArrayList<Integer>();
Это случай, к которому, как представляется, относится ваш вопрос, например, когда есть отношения наследования или реализации между типами, так как в этом случае, когда ArrayList реализует List.
Как я уже упоминал, когда вы вводите Java-генераторы, некоторое время правила подтипирования становятся нечеткими:
List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();
И в других случаях отношения даже не очевидны в API
Cloneable clone = new int[10];
Serializable obj = new Object[10]
Тем не менее, все это, по словам Карделли, является формами универсального полиморфизма.
Parametric
public <T> List<T> filter(Predicate<T> predicate, List<T> source) {
List<T> result = new ArrayList<>();
for(T item : source) {
if(predicate.evaluate(item)){
result.add(item);
}
return result;
}
}
Тот же алгоритм может использоваться для фильтрации всех видов списков со всеми предикатами видов, не повторяя одну строку кода для каждого типа списка. Тип фактического списка и тип предиката являются параметрическими. См. Этот пример с лямбда-выражениями, доступными в JDK 8 Preview (для краткости реализации предикатов).
filter(x -> x % 2 == 0, asList(1,2,3,4,5,6)); //filters even integers
filter(x -> x % 2 != 0, asList(1L,2L,3L,4L,5L,6L)); //filters odd longs
filter(x -> x >= 0.0, asList(-1.0, 1.0)); //filters positive doubles
Согласно Карделли, это форма универсального полиморфизма.
Принуждение
double sum = 1 + 2.0;
Целочисленная и арифметическая с плавающей запятой совершенно разные. Применение оператора плюс к двум операндам разных типов здесь невозможно без какой-либо формы принуждения.
В этом примере типы integer и double, автоматически принудительно (преобразуются), чтобы ввести double без явного приведения. Все выражение удваивается. Это происходит потому, что в Java мы имеем примитивные расширяющиеся преобразования.
Согласно Карделли, эта форма автоматического принуждения представляет собой форму ad-hoc-полиморфизма, предоставляемую для оператора плюс.
Существуют языки, в которых вы не могли даже суммировать целое число и число с плавающей запятой без явного приведения (т.е. AFAIK, SML, в котором, кстати, параметрический полиморфизм является ключом к решению таких проблем).
Перегрузки
double sum = 2.0 + 3.0;
String text = "The sum is" + sum;
Оператор плюс здесь означает две разные вещи в зависимости от используемых аргументов. Очевидно, оператор был перегружен. Это означает, что он имеет разные реализации в зависимости от типов операндов. По словам Карделли, это форма ad-hoc-полиморфизма, предоставляемая для оператора plus.
Это, конечно, также относится к формам перегрузки методов в классах (например, методы java.lang.Math min и max перегружены для поддержки разных примитивных типов).
На других языках
Даже если наследование играет важную роль в реализации некоторых из этих форм полиморфизма, конечно, это не единственный способ. Другие языки, которые не являются объектно-ориентированными, предоставляют другие формы полиморфизма. Возьмем, к примеру, случаи duck typing в динамических языках, таких как Python или даже в статически типизированных языках, таких как Go или алгебраических типов данных в таких языках, как классы классов SML, Ocaml и Scala или в таких языках, как Haskell, несколько методов в Clojure, прототипное наследование в JavaScript и т.д.