Почему ваш тип данных оператора переключения не может быть длинным, Java?

80

Вот отрывок из руководств Sun по Java :

Коммутатор работает с byte, short, charи intпримитивными типами данных. Она также работает с перечисленными типами (обсуждавшихся в классах и наследовании) и несколько специальных классов, «завернуть» некоторые примитивные типы: Character, Byte, Short, и Integer(обсуждаемый в объекты Simple Data).

Должна быть веская причина, по которой longпримитивный тип данных не разрешен. Кто-нибудь знает, что это?

Fostah
источник

Ответы:

52

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

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

Любой из этих методов в принципе может использовать длинное значение, а не целое число. Но я думаю, что это было, вероятно, просто практическое решение сбалансировать сложность набора инструкций и компилятора с реальной потребностью: случаи, когда вам действительно нужно переключаться на долгое время, достаточно редки, и их можно переписать как последовательность операторов IF или обойтись каким-либо другим способом (если рассматриваемые длинные значения близки друг к другу, вы можете в своем коде Java переключить результат int вычитания наименьшего значения).

Нил Коффи
источник
Я должен согласиться с аргументом «редкости», поскольку я какое-то время занимался разработкой на Java и до сих пор не сталкивался с ситуацией, когда мне нужно было / пытаться включиться.
Fostah
3
Димитрис, мои рассуждения не основывались на промежуточном понимании безопасности потоков, просто я не думаю, что выдвинутый вами аргумент о безопасности потоков верен.
Нил Коффи,
13
Самое глупое дизайнерское решение. У меня есть длинный, который представляет собой связку флагов, и я должен носить с собой if ... else if ... else ... Совершенно нелепо.
m0skit0
2
Я согласен с w / @ m0skit0. В моем случае кто-то другой написал API, который использует длинные в качестве констант, потому что это то, что они хранят в БД. Теперь я не могу использовать выключатель / корпус из-за чьего-то плохого решения.
CodeChimp
1
Я не говорю, что это «конец света». И да, есть способы обойти это, но все они кажутся хакерскими. Я считаю, что, как разработчики, мы всегда должны стараться не думать о том, как, по нашему мнению, люди будут использовать то, что мы создаем. В этом случае кто-то принял решение не разрешать переключение / регистрацию на длительное время, исходя из первоначальной мысли, что «у кого честно будет 2 ^ 64 случая». Может быть, это чрезмерное упрощение. Может быть, есть какая-то другая причина, по которой нельзя переключить лонги, потому что мы просто не были посвящены. Мне кажется странным не поддерживать это.
CodeChimp
21

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

[РЕДАКТИРОВАТЬ: извлечено из комментариев к этому ответу, с некоторыми дополнениями в фоновом режиме]

Если быть точным, 2³² - это очень много случаев, и любая программа с методом, достаточно длинным, чтобы вместить больше, будет совершенно ужасной! На любом языке. (Самая длинная функция, о которой я знаю в любом коде на любом языке, составляет чуть более 6 тыс. SLOC - да, это большой switch- и действительно неуправляемый.) Если вы действительно застряли в том, чтобы иметь место, longгде у вас должно быть только intили меньше, тогда у вас есть две реальные альтернативы.

  1. Используйте какой-нибудь вариант на тему хэш-функций, чтобы сжать файл longв формате int. Самый простой способ, который можно использовать только тогда, когда у вас неправильный тип, - это просто приведение! Полезнее было бы сделать так:

    (int) ((x&0xFFFFFFFF) ^ ((x >>> 32) & 0xFFFFFFFF))
    

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

  2. Гораздо лучшим решением, если вы работаете с очень большим количеством кейсов, является изменение вашего дизайна на использование Map<Long,Runnable>или чего-то подобного, чтобы вы искали, как отправить конкретное значение. Это позволяет вам разделять дела на несколько файлов, что намного проще контролировать, когда количество дел становится большим, хотя становится сложнее организовать регистрацию хоста задействованных классов реализации (аннотации могут помочь, позволяя вам создать регистрационный код автоматически).

    FWIW, я сделал это много лет назад (мы перешли на недавно выпущенный J2SE 1.2 в ходе реализации проекта) при создании собственного механизма байт-кода для моделирования массивно-параллельного оборудования (нет, повторное использование JVM было бы неприемлемым из-за радикального различные модели значений и исполнения), и это значительно упростило код по сравнению с тем, switchчто использовала версия кода C.

Повторим Забирать домой сообщение, желая switchна longэто признак того, что либо у вас есть типы неправильно в вашей программе или что вы создаете систему с этим много вариаций вовлеченного , что вы должны использовать классы. В любом случае время для переосмысления.

Donal Fellows
источник
1
Но действительно ли переключаемый диапазон превышает 32 бита? Я никогда не слышал о коде, который требовал такого большого количества переключений. Без этого вы можете сжать диапазон (например, используя некоторые вариации темы хэш-функций), чтобы сделать что-то, что будет работать. Или используйте, Map<Long,Runnable>чтобы решить проблему совершенно другим способом. :-)
Donal Fellows
3
@ Лорд Торгамус: Предположительно, потому что это глупо. Задумайтесь на мгновение: зачем кому- то вообще иметь код с более чем 2 32 руками в a switch? Желание сделать выбор в пользу ограниченного набора из такого количества элементов просто указывает на ошибку в фундаментальном дизайне программы. То, что люди просят об этом, просто означает, что ** они ошиблись в дизайне .
Donal Fellows
1
Кстати, если кто-то захочет спорить со мной дальше по этому поводу, пожалуйста, начните с примера использования для включения long. Иначе мы застрянем в споре с гипотезами навсегда ...
Донал Феллоуз,
2
@DonalFellows, Другой случай в Android с ListView's. В то время как listViews может иметь longколичество строк, мне, в частности, нужно только 4. Это случай, когда мне longпередают руку, которая указывает, какая строка обрабатывается, и мне нужно делать разные вещи в зависимости от того, какая это строка. Думаю, я мог бы привести к int, но это облегчило бы мою жизнь, если бы я мог просто switchиспользовать переменную. Как бы то ни было, я просто использую строку из ifи else if.
Кристиан Смит
7
«Повторяю полезное сообщение: желание включить долгое время означает, что либо у вас неправильные типы в вашей программе» - Нет, это не так! Наличие longне означает, что вы собираетесь проверить все возможности, точно так же, как наличие intили Stringне означает этого. Это означает, что значения, которые у вас есть, которых может быть несколько, имеют большой диапазон . Вы можете проверить несколько простых случаев и попробовать defaultостальные. Необходимость выполнять сдвиги и преобразования означает, что вы рискуете потерять данные. В итоге, это плохое дизайнерское решение Java, а не проблема пользователя.
jbx
6

Потому что индекс таблицы поиска должен быть 32 бита.

JRL
источник
3
Но опять же, switchнеобязательно реализовывать справочную таблицу.
Joachim Sauer
10
Если бы это было так, они бы никогда не смогли реализовать переключатель для строк (как они в настоящее время планируют).
Димитрис Андреу
1
@DimitrisAndreou, да, теперь мы можем переключаться на Strings: D (уже много лет: P)
J. Doe
-11

В 32-битной архитектуре long обозначается двумя словами . А теперь представьте, что могло бы случиться, если бы из-за недостаточной синхронизации выполнение оператора switch наблюдало бы long с его старшими 32 битами от одной записи, а 32 младшими - с другой! Он мог бы попытаться пойти… кто знает куда! В основном где-то наугад. Даже если обе записи представляют допустимые случаи для оператора switch, их забавная комбинация, вероятно, не приведет ни к первому, ни ко второму - или, что еще хуже, это может привести к другому допустимому, но не связанному с ним случаю!

По крайней мере, с int (или меньшими типами), независимо от того, насколько сильно вы напортачили, оператор switch по крайней мере прочитает значение, которое кто-то на самом деле написал , а не значение «из воздуха».

Конечно, я не знаю истинной причины (прошло уже более 15 лет, я так давно не обращал внимания!), Но если вы понимаете, насколько небезопасной и непредсказуемой может быть такая конструкция, вы согласитесь, что это определенно очень хорошая причина, чтобы никогда не включать longs (и, как и предполагалось long -pun, будут 32-битные машины, эта причина останется в силе).

Димитрис Андреу
источник
1
Я не думаю, что это следует. Включаемое значение необходимо вычислить и сохранить в регистре или в стеке. Если это значение вычисляется на основе данных, к которым обращаются несколько потоков, это вычисление необходимо сделать поточно-ориентированным, независимо от ширины результата. Но затем, когда этот результат находится в регистре или в стеке, к нему обращается только поток переключения и он безопасен.
Нил Коффи
Нил, ваш аргумент довольно запутан: «Но тогда, когда этот результат находится в регистре или в стеке, он доступен только для потока переключения и безопасен». Конечно, использование этого значения безопасно для потоков! Но я хочу сказать, что это значение может _ уже_ быть неправильным из-за ошибок синхронизации в пользовательском коде . Использование поточно-безопасного неправильного значения менее чем полезно :) Эту проблему никогда нельзя устранить: ошибочный параллельный код мог уже произвести "из воздуха" / неправильное длинное значение, которое впоследствии может быть использовано в переключателе, в результате чего перейти на адрес случая, который никто никогда не указывал .
Димитрис Андреу
1
Димитрис, может быть, я чего-то не понимаю в ваших аргументах. Включенное значение действительно могло быть неправильным из-за ошибок синхронизации в пользовательском коде. Но я не верю, что в операторе switch есть что-то такое, что делает это более вероятным, чем в других случаях. И обдумывая это как можно лучше, я не верю, что неатомарность высоких / низких слов длинных операций чтения / записи в память на самом деле является проблемой. (Думая о вещах по-другому: вы можете решить, что сравнение if для длинного числа не разрешено на основании того же аргумента.)
Нил Коффи
Хотя потенциальные проблемы, связанные с представлением long как два слова без гарантированной атомарной записи, являются общей проблемой, мы согласились, в случае переключения это будет еще более явной опасностью. Это похоже на отправку конверта с сообщением, где половина адреса принадлежит одному человеку, половина - другому - окончательный адрес может быть действительным и соответствовать совершенно случайному человеку, который затем получит конверт и будет действовать соответствующим образом. Это одна вещь , которую читает мусор и производить мусор (например , неправильное булево), но читать мусор и делать случайные скачки делает звук чуть более опасен для меня.
Димитрис Андреу
7
Я знаю, что это устарело, и комментировать его - спорный вопрос, но я хочу подчеркнуть, что ваш аргумент также применим ifи результат будет таким же плохим: неправильный результат ~> выбрана неправильная ветка. Создание длинного if- else-if цепь вместо того , чтобы switchфактически приведет к точно такому же результату.
Николай Парлог