Автоматический Даункинг путем определения типа

11

В 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 
Сэм Washburn
источник
Вы говорите, что, если компилятор может сделать вывод, что понижаемый объект всегда имеет правильный тип, вы должны быть в состоянии явно не выражать пониженное значение? Но затем, в зависимости от версии компилятора и от того, какой вывод типов он может сделать, некоторые программы могут быть недействительными ... ошибки в выводе типов могут помешать написанию допустимых программ и т. Д. ... не имеет смысла вводить некоторые специальные В случае, когда это зависит от множества факторов, пользователи не будут интуитивно понятны, если им придется без всякой причины добавлять или удалять приведения в каждом выпуске.
Бакуриу

Ответы:

24

Апкасты всегда успешны.

Понижение рейтинга может привести к ошибке времени выполнения, когда тип среды выполнения объекта не является подтипом типа, используемого в приведении.

Поскольку вторая операция является опасной, большинство типизированных языков программирования требуют, чтобы программист явно запрашивал ее. По сути, программист говорит компилятору: «Поверьте мне, я знаю лучше - это будет хорошо во время выполнения».

Когда речь идет о системах типов, апскейты возлагают бремя доказательства на компилятор (который должен проверять его статически), а апгрейды возлагают бремя доказательства на программиста (который должен тщательно обдумать это).

Можно утверждать, что правильно спроектированный язык программирования полностью запрещает даункиты или обеспечивает безопасные альтернативы приведения, например, возвращает необязательный тип Option<T>. Многие распространенные языки, тем не менее, выбрали более простой и более прагматичный подход - просто возвращать Tи выдавать ошибку в противном случае.

В вашем конкретном примере компилятор мог бы быть разработан для вывода, который parentна самом деле является Appleпростым статическим анализом, и разрешить неявное приведение. Однако, в общем, проблема неразрешима, поэтому мы не можем ожидать, что компилятор выполнит слишком много магии.

чи
источник
1
Просто для справки, примером языка, который преобразуется в необязательные, является Rust, но это потому, что он не имеет истинного наследования, только «любой тип» .
Кролтан
3

Обычно подавление - это то, что вы делаете, когда статически известные знания компилятора о типе чего-либо менее конкретны, чем то, что вы знаете (или, по крайней мере, надеетесь).

В ситуациях, подобных вашему примеру, объект был создан как объект, Appleа затем эти знания были отброшены путем сохранения ссылки в переменной типа Fruit. Затем вы хотите использовать ту же ссылку, что и Appleснова.

Поскольку информация выбрасывалась только «локально», конечно, компилятор мог поддерживать знания, которые parentдействительно есть Apple, даже если его объявленный тип есть Fruit.

Но обычно никто этого не делает. Если вы хотите создать Appleи использовать его как Apple, вы сохраняете его в Appleпеременной, а не в переменной Fruit.

Когда у вас есть Fruitи вы хотите использовать его как Appleобычно, это означает, что вы получили Fruitчерез некоторые средства, которые обычно могут вернуть любой тип Fruit, но в этом случае вы знаете, что это было Apple. Почти всегда вы не просто сконструировали его, а передали другим кодом.

Очевидный пример - если у меня есть parseFruitфункция, которая может превращать такие строки, как «яблоко», «апельсин», «лимон» и т. Д., В соответствующий подкласс; как правило, все, что мы (и компилятор) можем знать об этой функции, - это то, что она возвращает какую-то функцию Fruit, но если я вызываю ее, parseFruit("apple")то я знаю, что она будет вызывать, Appleи, возможно, захочет использовать Appleметоды, так что я мог бы унывать.

Опять же, достаточно умный компилятор может понять это, вставив исходный код для parseFruit, поскольку я вызываю его с помощью константы (если он не в другом модуле, а у нас есть отдельная компиляция, как в Java). Но вы легко сможете увидеть, как более сложные примеры, включающие динамическую информацию, могут стать более трудными (или даже невозможными!) Для проверки компилятором.

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

Бен
источник
3

Вопрос в том, где вы хотите провести черту. Вы можете разработать язык, который будет определять достоверность неявного понижения:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Теперь давайте извлечем метод:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

У нас все еще хорошо. Статический анализ намного сложнее, но все же возможен.

Но проблема всплывает, как только кто-то добавляет:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

Теперь, где вы хотите поднять ошибку? Это создает дилемму, можно сказать, что проблема в Apple child = parent;, но это может быть опровергнуто словами «Но это работало раньше». С другой стороны, добавление eat(trouble);вызвало проблему », но весь смысл полиморфизма в том, чтобы позволить именно это.

В этом случае вы можете выполнять некоторую работу программиста, но не можете делать это полностью. Чем дальше вы принимаете это, прежде чем сдаться, тем труднее будет объяснить, что пошло не так. Таким образом, лучше остановиться как можно скорее, в соответствии с принципом раннего сообщения об ошибках.

Кстати, в Java смиренное вы описали это не на самом деле удрученный. Это общий актерский состав, который также может разливать яблоки в апельсины. Итак, технически говоря, идея @ chi уже здесь, в Java нет даункастов, только «Everycasts». Имеет смысл разработать специализированный оператор downcast, который будет выдавать ошибку компиляции, когда его тип результата не может быть найден после его типа аргумента. Было бы неплохо сделать так, чтобы использовать «Everycast» было намного сложнее, чтобы программисты не использовали его без веской причины. C ++ s XXXXX_cast<type>(argument)синтаксис приходит на ум.

Agent_L
источник