Возможно ли полиморфизм без наследования?

В интервью мне спросили, можно ли добиться полиморфизма без наследования. Возможно ли это?

Ответ 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 и т.д.

Ответ 2

Ad-hoc полиморфизм > перегрузка операторa > без наследования

Ad-hoc Polymorphism > Перегрузка методa > Без наследования

Ad-hoc Полиморфизм > Переопределение методa > С наследованием

Параметрический полиморфизм > Общие > Без наследования

Полиморфизм подтипа или полиморфизм включения > Полиморфное назначение > С наследованием

Полиморфизм подтипа или полиморфизм включения > Полиморфный тип возвратa > С наследованием

Полиморфизм подтипа или полиморфизм включения > Полиморфный тип аргументa > с наследованием

Полиморфизм принуждения > Расширение > С или без наследования

Полиморфизм принуждения > Автоматический бокс и распаковкa > Без наследования

Полиморфизм принуждения > Var args > Без наследования

Полиморфизм принуждения > Литье по типам > Без наследования

Ответ 3

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

public interface Foo {
  public int a();
}

public class A implements Foo {
  public int a() {
    return 5;
  }
}


public class B implements Foo {
  public int a() {
    return 6;
  }
}

Тогда в другом месте:

Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())

Оба x и y являются Foo s, но при вызове a() они имеют разные результаты.

Ответ 4

Да, я думаю, они, вероятно, хотели услышать о полиморфизме по интерфейсам. Итак, если есть 2 класса, которые реализуются из одного и того же интерфейса, то мы можем использовать их во всех местах, где мы исследуем объект с таким интервалом. Смотрите код из Википедии:

// from file Animal.java

public interface Animal {
        public String talk();
}

// from file Cat.java

public class Cat implements Animal {
        @Override
        public String talk() {
                return "Cat says Meow!";
        }
}

// from file Dog.java

public class Dog implements Animal {
        @Override
        public String talk() {
                return "Dog says Woof! Woof!";
        }
}

// from file PolymorphismExample.java

public class PolymorphismExample {

        public static void main(String[] args) {
                Collection<Animal> animals = new ArrayList<Animal>();
                animals.add(new Cat());
                animals.add(new Dog());

                for (Animal a : animals) {
                        System.out.println(a.talk());
                }
        }

}

Ответ 5

Статический тип

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

class StaticPolyExample
{
void print(int s)
{
    //print s
}

void print(String s)
{
    //print s
}

}

Динамический тип

overriding - что означает, что метод в суперклассе будет переопределен в подклассе, который нуждается в наследовании

class Printer
{
void print(String s)
{
  // prints String
}

}

class diffPrinter extends Printer
{
void print(String s)
{
  // prints String differently
}

}

Ответ 6

Перегрузка функций - это один из полиморфизмов (хотя это не тот реальный полиморфизм), который может быть достигнут без наследования.

например.

class Foo { 
public void Arrest( Animal A){
    /*code...*/
 }  
public void Arrest( Terrorist T ) {
    /*code...*/
 }  

}


from main :

Foo f= new Foo();
f.Arrest( new Lion() );
f.Arrest(new Terrorist());

Метод арест называется 2 раза, но путь выполнения кода отличается.

* Опять же, это не истинная форма полиморфизма. Реальный полиморфизм вообще не может быть достигнут без наследования.