История идет некоторым путем.
Интерфейсы - это своего рода классы.
Это не сразу очевидно, но другое подходящее название для "интерфейса" Java будет "абстрактный класс" . То, что интерфейсы на самом деле являются подмножеством классов, конечно, сомнительно, поэтому мы должны ставить его отдельно. Если мы узнаем, что это неверно, мы должны также вернуться к этому ответу. Но сейчас давайте предположим, что это правда.
Мы также должны предположить, что Java "классы" являются разновидностями типов . Опять же, это может быть подвергнуто сомнению само по себе, в другом месте. Если мы предположим это, мы можем извлечь некоторую информацию из теории типов. Что нам конкретно нужно, так это понятие подтип . Короче говоря, то, что C
является подтипом I
, означает, что когда в вашем коде есть переменная типа I
, вы можете заменить ее на переменную типа C
, и некоторая часть вашего кода все еще будет работать.
Примечания:
- Конечно, вы никогда не сможете создать значение типа
I
, поскольку I
является абстрактным классом - у него нет определения;но вы можете рассмотреть значение, о котором известно только то, что он относится к некоторому классу, который реализует I
. - Какая часть вашего кода будет работать впоследствии, зависит от точного определения подтипа, из которого естьнекоторые - читайте дальше, чтобы узнать, какое определение использует Java.
Типы аргумента и возврата принципиально различны в отношении подтипов.
Здесь ковариация и контравариантность введите. Эти термины пришли в информатику из физика и теория категорий . Это кажется неправдоподобным, но посмотрите, как подтип играет с функциями , и вы обязательно увидите, как все это выравнивается.
Вот пример. Это немного сложнее, поэтому возьмите ручку, немного бумаги и сами проверите.
- Возьмите функцию
f: Integer -> Boolean
. Он принимает число и возвращает значение истинности. Например, пусть он скажет нам, является ли число четным. - Мы можем подтипировать эту функцию следующими двумя способами:
- Конечно, если она работает с любым числом, она будет работать с простыми числами. , поэтому мы можем использовать такую же функцию, как эта:
f -- specialize argument --> f1: Prime -> Boolean
. (3 все еще дает False, 2 дает True. Все хорошо.) Там, где работает последний, первый тоже будет работать. Итак, f1
является подклассом f
. - Также верно, что мы можем использовать такую же функцию, как эта:
f -- generalize result --> f2 = Integer -> Byte
. (Мы просто преобразовали бы True в 0x01
и False в 0x00
.) Там, где требуется f2
, подойдет f
. Итак, f2
также является подклассом f
!
- Обратите внимание, что
Prime
является подклассом Integer
: вы все равно можете добавлять, умножать и инвертировать их, новы также можете использовать их для криптографии . - Обратите внимание, что
Bool
является подклассом Byte
: вы все равно можете сохранить его в памяти, но вы также можете сохранить массив Bool
упаковано, в 8 раз меньше памяти, чем массив Byte
. - Теперь обратите внимание: когда мы перемещаем аргумент
f
в направлении подтипа (специализируем его) функция также перемещается в этом направлении, в то время как тип результата должен быть перемещен в противоположном направлении (обобщено) для функции, которая должна быть подтипирована. Читатель может на самом деле нарисовать это и увидеть, как она согласуется с определением векторной дисперсии в физике.
Это, конечно, не реальное доказательство, а лишь махание рукой. У меня нет реальных доказательств. Задайте вопрос!
Подклассы в Java выровнены с определенным «подтипом» частичного порядка типов.
Знаменитое требование подтипа Барбары Лисков является односторонним для определения отношения подтипа. Это конкретное определение называется «поведенческий» подтип, и оно открывает глубокую кроличью нору с неразрешимостью , которая может вызывать или не беспокоить нас. (Итак, снова у нас есть вопрос.) Именно здесь мы должны верить, что Java верна в своем поведенческом подтипировании. (Привычный вопрос. Один из возможных способов вбить клин - внимательно посмотреть на взаимосвязь между подтипом и наследованием .)
Итак, давайте предположим, что наследование совместимос подтипом в Java. Что это нам дает?
Функция с подтипированным аргументом не является подтипом оригинала.
То есть метод подтипированного элемента не является подтипом оригинала.
Итак, предположим, у вас есть эта функция, которая выполняет шифрование RSA, и ей просто нужно одно простое число, f: Prime -> X
. Если вы поместите эту функцию в ситуацию, когда ей может быть дан непростой аргумент, то она, вероятно, создаст сообщение, которое зашифровано очень слабо. Вы ввели ошибку. Таким образом, хотя вы всегда можете поместить простое число в место, где ожидается число, вы не можете поместить вызов метода, который запрашивает простое число, в место, где ему в качестве аргумента будет дано простое число.
Но объект , из которого вызывается метод, сам по себе является неявным аргументом , можете спросить вы, поэтому подклассы должны быть вообще запрещены ! Действительно, предположим, что я определяю метод Integer
, который вычисляет возможно слабый ключ RSA и подкласс Prime
из этого Integer
. Теперь тот же метод, с теми же очевидными типами, вычисляет сильный ключ RSA, потому что его неявный аргумент был подтипирован - и, если я хочу сильные ключи, я не могу слепо заменить Integer
на Prime
.
Это первый большой вопрос нашего фэнтезийного приключения.
(на данный момент я очень нечеткий.)
Java пытается убедиться в реализации методавсегда является подтипом своего определения.
Один из способов обеспечения этого - разрешить только подтипирование результата. (Это единственный способ? Правильный путь? Действительно ли это безопасно?)
По какой-то причине Java не пытается выдавать ошибки типа при использовании сайта, а просто запрещает определение.
В чем причина нашего большого вопроса номер два. Возможно, потому что он хочет выровнять подтипы и наследование.