В Java, вы должны явно привести, чтобы уменьшить значение переменной
public class Fruit{} // parent class
public class Apple extends Fruit{} // child class
public static void main(String args[]) {
// An implicit upcast
Fruit parent = new Apple();
// An explicit downcast to Apple
Apple child = (Apple)parent;
}
Есть ли какая-либо причина для этого требования, кроме того факта, что Java не делает никакого вывода типа?
Есть ли какие-то «подводные камни» с автоматической реализацией понижающего приведения в новом языке?
Например:
Apple child = parent; // no cast required
object-oriented
type-inference
language-design
Сэм Washburn
источник
источник
Ответы:
Апкасты всегда успешны.
Понижение рейтинга может привести к ошибке времени выполнения, когда тип среды выполнения объекта не является подтипом типа, используемого в приведении.
Поскольку вторая операция является опасной, большинство типизированных языков программирования требуют, чтобы программист явно запрашивал ее. По сути, программист говорит компилятору: «Поверьте мне, я знаю лучше - это будет хорошо во время выполнения».
Когда речь идет о системах типов, апскейты возлагают бремя доказательства на компилятор (который должен проверять его статически), а апгрейды возлагают бремя доказательства на программиста (который должен тщательно обдумать это).
Можно утверждать, что правильно спроектированный язык программирования полностью запрещает даункиты или обеспечивает безопасные альтернативы приведения, например, возвращает необязательный тип
Option<T>
. Многие распространенные языки, тем не менее, выбрали более простой и более прагматичный подход - просто возвращатьT
и выдавать ошибку в противном случае.В вашем конкретном примере компилятор мог бы быть разработан для вывода, который
parent
на самом деле являетсяApple
простым статическим анализом, и разрешить неявное приведение. Однако, в общем, проблема неразрешима, поэтому мы не можем ожидать, что компилятор выполнит слишком много магии.источник
Обычно подавление - это то, что вы делаете, когда статически известные знания компилятора о типе чего-либо менее конкретны, чем то, что вы знаете (или, по крайней мере, надеетесь).
В ситуациях, подобных вашему примеру, объект был создан как объект,
Apple
а затем эти знания были отброшены путем сохранения ссылки в переменной типаFruit
. Затем вы хотите использовать ту же ссылку, что иApple
снова.Поскольку информация выбрасывалась только «локально», конечно, компилятор мог поддерживать знания, которые
parent
действительно естьApple
, даже если его объявленный тип естьFruit
.Но обычно никто этого не делает. Если вы хотите создать
Apple
и использовать его какApple
, вы сохраняете его вApple
переменной, а не в переменнойFruit
.Когда у вас есть
Fruit
и вы хотите использовать его какApple
обычно, это означает, что вы получилиFruit
через некоторые средства, которые обычно могут вернуть любой типFruit
, но в этом случае вы знаете, что это былоApple
. Почти всегда вы не просто сконструировали его, а передали другим кодом.Очевидный пример - если у меня есть
parseFruit
функция, которая может превращать такие строки, как «яблоко», «апельсин», «лимон» и т. Д., В соответствующий подкласс; как правило, все, что мы (и компилятор) можем знать об этой функции, - это то, что она возвращает какую-то функциюFruit
, но если я вызываю ее,parseFruit("apple")
то я знаю, что она будет вызывать,Apple
и, возможно, захочет использоватьApple
методы, так что я мог бы унывать.Опять же, достаточно умный компилятор может понять это, вставив исходный код для
parseFruit
, поскольку я вызываю его с помощью константы (если он не в другом модуле, а у нас есть отдельная компиляция, как в Java). Но вы легко сможете увидеть, как более сложные примеры, включающие динамическую информацию, могут стать более трудными (или даже невозможными!) Для проверки компилятором.В реалистичном коде понижения обычно происходят, когда компилятор не может проверить, насколько он безопасен, используя универсальные методы, и не в таких простых случаях, как сразу после апскейтинга, отбрасывающего ту же информацию о типе, которую мы пытаемся получить обратно при даункастинге.
источник
Вопрос в том, где вы хотите провести черту. Вы можете разработать язык, который будет определять достоверность неявного понижения:
Теперь давайте извлечем метод:
У нас все еще хорошо. Статический анализ намного сложнее, но все же возможен.
Но проблема всплывает, как только кто-то добавляет:
Теперь, где вы хотите поднять ошибку? Это создает дилемму, можно сказать, что проблема в
Apple child = parent;
, но это может быть опровергнуто словами «Но это работало раньше». С другой стороны, добавлениеeat(trouble);
вызвало проблему », но весь смысл полиморфизма в том, чтобы позволить именно это.В этом случае вы можете выполнять некоторую работу программиста, но не можете делать это полностью. Чем дальше вы принимаете это, прежде чем сдаться, тем труднее будет объяснить, что пошло не так. Таким образом, лучше остановиться как можно скорее, в соответствии с принципом раннего сообщения об ошибках.
Кстати, в Java смиренное вы описали это не на самом деле удрученный. Это общий актерский состав, который также может разливать яблоки в апельсины. Итак, технически говоря, идея @ chi уже здесь, в Java нет даункастов, только «Everycasts». Имеет смысл разработать специализированный оператор downcast, который будет выдавать ошибку компиляции, когда его тип результата не может быть найден после его типа аргумента. Было бы неплохо сделать так, чтобы использовать «Everycast» было намного сложнее, чтобы программисты не использовали его без веской причины. C ++ s
XXXXX_cast<type>(argument)
синтаксис приходит на ум.источник