Будет ли эта функциональность реализована в более поздней версии Java?
Может ли кто-нибудь объяснить, почему я не могу этого сделать, например, технический способ работы оператора Java switch?
Sun была честна в своей оценке: "Don't hold your breath." lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
@raffian Я думаю, это потому, что она дважды вздохнула. Они тоже немного опоздали с ответом, спустя почти 10 лет. Тогда она могла бы упаковывать ланч-боксы своим внукам.




Операторы переключения с случаями String были реализованы в Java SE 7, по крайней мере, 16 лет после того, как они были впервые запрошены. Явной причины задержки не было указано, но, вероятно, это было связано с производительностью.
Эта функция теперь реализована в javac. с процессом "обесахаривания";: чистый высокоуровневый синтаксис с использованием констант String в объявлениях case расширяется во время компиляции в более сложный код, следующий шаблону. Результирующий код использует инструкции JVM, которые существовали всегда.
switch с корпусами String транслируется в два переключателя во время компиляции. Первый отображает каждую строку в уникальное целое число - ее положение в исходном переключателе. Это делается путем первого включения хэш-кода метки. Соответствующий случай - это оператор if, который проверяет равенство строк; если есть коллизии в хэше, тестом является каскадный if-else-if. Второй переключатель отражает это в исходном исходном коде, но заменяет метки case их соответствующими позициями. Этот двухэтапный процесс позволяет легко сохранить управление потоком исходного коммутатора.
Для получения дополнительной технической информации о switch вы можете обратиться к Спецификации JVM, где описывается компиляция операторов переключения. Вкратце, есть две разные инструкции JVM, которые можно использовать для переключения, в зависимости от разреженности констант, используемых в случаях. Оба зависят от использования целочисленных констант для каждого случая для эффективного выполнения.
Если константы плотные, они используются в качестве индекса (после вычитания наименьшего значения) в таблице указателей инструкций - инструкции tableswitch.
Если константы редкие, выполняется двоичный поиск правильного регистра - инструкция lookupswitch.
При снятии сахара с switch на объектах String, вероятно, будут использоваться обе инструкции. lookupswitch подходит для первого включения хэш-кодов, чтобы найти исходное положение корпуса. Полученный порядковый номер естественным образом подходит для tableswitch.
Обе инструкции требуют, чтобы целочисленные константы, присвоенные каждому случаю, были отсортированы во время компиляции. Во время выполнения, хотя производительность O(1) у tableswitch обычно лучше, чем у O(log(n)) у lookupswitch, требуется некоторый анализ, чтобы определить, достаточно ли плотна таблица, чтобы оправдать компромисс между пространством и временем. Билл Веннерс написал отличная статья, который описывает это более подробно, наряду с внутренним взглядом на другие инструкции управления потоком Java.
До JDK 7 enum мог приближаться к коммутатору на основе String. При этом используется метод статический valueOf, сгенерированный компилятором для каждого типа enum. Например:
Pill p = Pill.valueOf(str);
switch(p) {
case RED: pop(); break;
case BLUE: push(); break;
}
Возможно, будет быстрее просто использовать If-Else-If вместо хеша для переключателя на основе строки. Я обнаружил, что словари довольно дороги, если хранить только несколько элементов.
If-elseif-elseif-elseif-else может быть быстрее, но я бы взял более чистый код 99 раз из 100. Строки, будучи неизменными, кэшируют свой хэш-код, поэтому "вычисление" хеша происходит быстро. Чтобы определить, в чем заключается выгода, нужно профилировать код.
Причина, по которой не следует добавлять switch (String), заключается в том, что это не соответствует гарантиям производительности, ожидаемым от операторов switch (). Они не хотели «вводить в заблуждение» разработчиков. Честно говоря, я не думаю, что они должны изначально гарантировать производительность switch ().
Если вы просто используете Pill для выполнения некоторых действий на основе str, я бы поспорил, что предпочтительнее if-else, поскольку он позволяет обрабатывать значения str за пределами диапазона КРАСНЫЙ, СИНИЙ без необходимости перехватывать исключение из valueOf или вручную проверять совпадение против имени каждого типа перечисления, что просто добавляет ненужные накладные расходы. По моему опыту, использование valueOf для преобразования в перечисление имело смысл только в том случае, если позже потребовалось безопасное представление значения String.
Интересно, прилагают ли компиляторы какие-либо усилия, чтобы проверить, существует ли какая-либо пара чисел (x, y), для которой набор значений (hash >> x) & ((1<<y)-1) будет давать различные значения для каждой строки, чей hashCode отличается, а (1<<y) меньше, чем в два раза, количество строк (или, по крайней мере, не намного больше).
Быстрее ли if-else-if для строк?
@ fernal73 Это зависит от того, сколько if вы каскадировали, и был ли уже вычислен хэш-код строки переключения. Для двоих или троих это могло бы быть быстрее. Однако в какой-то момент оператор switch, вероятно, будет работать лучше. Что еще более важно, во многих случаях оператор switch, вероятно, более читабелен.
Думаю, разница незначительная.
Переключатели, основанные на целых числах, можно оптимизировать для получения очень эффективного кода. Переключатели, основанные на другом типе данных, могут быть скомпилированы только в серию операторов if ().
По этой причине C и C++ допускают переключение только на целочисленные типы, поскольку с другими типами это было бессмысленно.
Разработчики C# решили, что стиль важен, даже если в нем нет преимущества.
Создатели Java, по-видимому, думали, как дизайнеры C.
Переключения, основанные на любом хешируемом объекте, могут быть очень эффективно реализованы с использованием хэш-таблицы - см. .NET. Итак, ваша причина не совсем верна.
Да, и вот этого я не понимаю. Боятся ли они, что хэширующие объекты в конечном итоге станут слишком дорогими?
@Nalandial: на самом деле, с небольшими усилиями со стороны компилятора, это совсем не дорого, потому что, когда известен набор строк, довольно легко сгенерировать идеальный хеш (хотя .NET не делает этого; наверное, тоже не стоит усилий).
@Nalandial и @Konrad Rudolph - при хешировании строки (из-за ее неизменной природы) кажется решением этой проблемы, вы должны помнить, что все неокончательные объекты могут иметь переопределенные функции хеширования. Это затрудняет во время компиляции обеспечение согласованности в переключателе.
Вы также можете создать DFA для соответствия строке (как это делают механизмы регулярных выражений). Возможно, даже более эффективно, чем хеширование.
@Konrad & Nate: В то время как хеш-таблица или конечный автомат будут работать в коммутаторе, потребуется чертовски много элементов case для преодоления внутренних накладных расходов любого из них. Мое «безумное предположение» состоит в том, что 80% всех переключателей имеют менее 10 случаев, а 99,9% имеют менее 20. При таких размерах было бы очень сложно победить цепочку if () для скорости.
Еще одна вещь, о которой следует упомянуть, это то, что байт-код Java имеет прямую поддержку переключения значений int; так что есть «родная» реализация для эффективного переключения на константы типа int (включая Enums, где можно использовать ordinal ()).
Каким образом внутренние накладные расходы конечного автомата (который сам может быть реализован в виде серии каскадных переключателей с каждым последующим символом в строке в качестве включаемого значения) будут больше, чем у связанных if () с точки зрения скорости? В худшем случае мне кажется, что они будут примерно равны, поскольку связанные if будут выполнять сравнение строк каждый раз, если не будет выполнена какая-то оптимизация, и в лучшем случае FSM-as-cascaded-Switches фактически быть более эффективным с точки зрения количества выполняемых операций.
Конечно, это не принимает во внимание накладные расходы памяти и то, как на скорость будут влиять проблемы с кешированием и различными другими аспектами конвейерной обработки процессора и т. д., А также тот факт, что худший и лучший сценарии для двух алгоритмы могут отличаться, а могут и не отличаться в зависимости от того, какие строки проверяются и как они упорядочены ... (я думаю, лучше всего будет амортизироваться в лучшем / худшем случае?)
То, что вы думаете, что в этом нет никакого преимущества, на самом деле не означает, что это бесполезно. Смею вас спросить любого разработчика Python о многострочных комментариях, они все, вероятно, скажут вам, что это бесполезно. Они скажут вам, что если вы действительно хотите, чтобы они использовали строки документации, даже если строки документов загружаются в память (по умолчанию).
Загрузка строк документации в память способствует самоанализу. Вы не только знаете имена и подписи функций, но и имеете их документацию. Отлично подходит для интерактивного или для создания IDE.
Если у вас есть место в вашем коде, где вы можете включить String, то может быть лучше реорганизовать String, чтобы он был перечислением возможных значений, которые вы можете включить. Конечно, вы ограничиваете потенциальные значения String, которые вы можете иметь, значениями в перечислении, что может быть, а может и не быть желательным.
Конечно, ваше перечисление может иметь запись для 'other' и метод fromString (String), тогда у вас может быть
ValueEnum enumval = ValueEnum.fromString(myString);
switch (enumval) {
case MILK: lap(); break;
case WATER: sip(); break;
case BEER: quaff(); break;
case OTHER:
default: dance(); break;
}
Этот метод также позволяет вам решать такие проблемы, как нечувствительность к регистру, псевдонимы и т. д. Вместо того, чтобы полагаться на разработчика языка, чтобы придумать универсальное решение.
Согласитесь с JeeBee, если вы включаете строки, вероятно, потребуется перечисление. Строка обычно представляет собой что-то, поступающее в интерфейс (пользовательский или иной), что может измениться или не измениться в будущем, поэтому лучше замените его перечислениями.
См. Красивое описание этого метода в xefer.com/2006/12/switchonstring.
@DavidSchmitt В описании есть один серьезный недостаток. Он перехватывает исключения все вместо тех, которые фактически выбрасываются методом.
Джеймс Карран лаконично говорит: «Переключатели, основанные на целых числах, могут быть оптимизированы для получения очень эффективного кода. Переключатели, основанные на другом типе данных, могут быть скомпилированы только в серию операторов if (). По этой причине C и C++ допускают переключение только на целочисленные типы, поскольку с другими типами это было бессмысленно ".
Мое мнение, и только оно, заключается в том, что как только вы начинаете включать непримитивы, вам нужно начать думать о «равных» и «==». Во-первых, сравнение двух строк может быть довольно длительной процедурой, что усугубляет проблемы с производительностью, упомянутые выше. Во-вторых, если есть переключение строк, будет требоваться включение строк без учета регистра, включение строк с учетом / игнорирование языкового стандарта, переключение строк на основе регулярного выражения .... Я бы одобрил решение, которое сэкономило много времени для языковых разработчиков за счет небольшого количества времени для программистов.
Технически регулярные выражения уже «переключаются», поскольку они в основном являются конечными автоматами; у них всего два «футляра», matched и not matched. (Однако не принимая во внимание такие вещи, как [named] groups / и т. д.).
Помимо приведенных выше хороших аргументов, я добавлю, что многие люди сегодня видят switch как устаревший остаток процедурного прошлого Java (назад во времена C).
Я не полностью разделяю это мнение, я думаю, что switch может быть полезен в некоторых случаях, по крайней мере, из-за своей скорости, и в любом случае он лучше, чем некоторые серии каскадных числовых else if, которые я видел в каком-то коде ...
Но действительно, стоит посмотреть на случай, когда вам нужен переключатель, и посмотреть, нельзя ли его заменить чем-то более OO. Например, перечисления в Java 1.5+, возможно, HashTable или какая-то другая коллекция (иногда я сожалею, что у нас нет (анонимных) функций в качестве первоклассного гражданина, как в Lua - у которого нет переключателя - или JavaScript) или даже полиморфизма.
"иногда я сожалею, что у нас нет (анонимных) функций как первоклассный гражданин" Это уже не так.
@dorukayhan Да, конечно. Но не хотите ли вы добавить комментарий ко всем ответам за последние десять лет, чтобы сообщить миру, что они могут быть получены, если мы обновимся до более новых версий Java? :-D
Ниже приведен полный пример, основанный на сообщении JeeBee, с использованием перечисления java вместо использования настраиваемого метода.
Обратите внимание, что в Java SE 7 и более поздних версиях вы можете вместо этого использовать объект String в выражении оператора switch.
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
String current = args[0];
Days currentDay = Days.valueOf(current.toUpperCase());
switch (currentDay) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
System.out.println("boring");
break;
case THURSDAY:
System.out.println("getting better");
case FRIDAY:
case SATURDAY:
case SUNDAY:
System.out.println("much better");
break;
}
}
public enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
}
В Groovy это совсем несложно; Я встраиваю groovy jar и создаю служебный класс groovy, чтобы делать все эти и многие другие вещи, которые мне неприятно делать в Java (поскольку я застрял на Java 6 на предприятии).
it.'p'.each{
switch ([email protected]()){
case "choclate":
myholder.myval=(it.text());
break;
}}...
@SSpoke Потому что это вопрос Java, а ответ Groovy не по теме и бесполезный плагин.
Даже в консервативных крупных компаниях программного обеспечения Groovy используется вместе с Java. JVM дает языковой независимой среде больше, чем язык, чтобы смешивать и использовать наиболее подходящую парадигму программирования для решения. Так что, возможно, теперь мне стоит добавить фрагмент в Clojure, чтобы собрать больше голосов против :) ...
Кроме того, как работает синтаксис? Я предполагаю, что Groovy - это другой язык программирования ...? Извиняюсь. Я ничего не знаю о Groovy.
В течение многих лет мы использовали для этого препроцессор с открытым исходным кодом.
//#switch(target)
case "foo": code;
//#end
Предварительно обработанные файлы называются Foo.jpp и обрабатываются в Foo.java с помощью ant-скрипта.
Преимущество в том, что он переработан в Java, работающую на 1.0 (хотя обычно мы поддерживали только обратно до 1.4). К тому же это было намного проще (много переключателей строк) по сравнению с подделкой перечислениями или другими обходными путями - код было намного легче читать, поддерживать и понимать. IIRC (на данный момент не может предоставить статистику или технические рассуждения), он также был быстрее, чем естественные эквиваленты Java.
Недостатки в том, что вы не редактируете Java, поэтому это немного больше рабочего процесса (редактирование, обработка, компиляция / тестирование), плюс IDE будет связываться с Java, которая немного запутана (переключатель становится серией логических шагов if / else) и порядок корпуса переключателя не сохраняется.
Я бы не рекомендовал его для версии 1.7+, но он полезен, если вы хотите запрограммировать Java, ориентированную на более ранние JVM (поскольку Joe public редко устанавливает последнюю версию).
Вы можете получить его из SVN или просмотреть код онлайн. Вам понадобится EBuild, чтобы собрать его как есть.
Для запуска кода с переключателем String вам не нужна JVM 1.7. Компилятор 1.7 превращает переключатель String во что-то, что использует ранее существовавший байтовый код.
Также может быть показан пример прямого использования String начиная с версии 1.7:
public static void main(String[] args) {
switch (args[0]) {
case "Monday":
case "Tuesday":
case "Wednesday":
System.out.println("boring");
break;
case "Thursday":
System.out.println("getting better");
case "Friday":
case "Saturday":
case "Sunday":
System.out.println("much better");
break;
}
}
Если вы не используете JDK7 или выше, вы можете использовать hashCode() для его моделирования. Поскольку String.hashCode() обычно возвращает разные значения для разных строк и всегда возвращает равные значения для одинаковых строк, он довольно надежен (разные строки может создают тот же хэш-код, что и @Lii, упомянутый в комментарии, например "FB" и "Ea") См. документация.
Итак, код будет выглядеть так:
String s = "<Your String>";
switch(s.hashCode()) {
case "Hello".hashCode(): break;
case "Goodbye".hashCode(): break;
}
Таким образом, вы технически включаете int.
В качестве альтернативы вы можете использовать следующий код:
public final class Switch<T> {
private final HashMap<T, Runnable> cases = new HashMap<T, Runnable>(0);
public void addCase(T object, Runnable action) {
this.cases.put(object, action);
}
public void SWITCH(T object) {
for (T t : this.cases.keySet()) {
if (object.equals(t)) { // This means that the class works with any object!
this.cases.get(t).run();
break;
}
}
}
}
Две разные строки могут иметь один и тот же хэш-код, поэтому, если вы включите хэш-коды, может быть выбрана неправильная ветвь case.
@Lii Спасибо, что указали на это! Хотя это маловероятно, но я бы не поверил, что это работает. «FB» и «Ea» имеют одинаковый хэш-код, поэтому найти коллизию не исключено. Второй код, наверное, более надежен.
Я удивлен, что эта компиляция компилируется, поскольку операторы case, как я думал, всегда должны быть постоянными значениями, а String.hashCode() не является таковым (даже если на практике расчет между JVM никогда не менялся).
@StaxMan Хм, интересно, я никогда не переставал это наблюдать. Но да, значения оператора case необязательно определять во время компиляции, поэтому он работает нормально.
public class StringSwitchCase {
public static void main(String args[]) {
visitIsland("Santorini");
visitIsland("Crete");
visitIsland("Paros");
}
public static void visitIsland(String island) {
switch(island) {
case "Corfu":
System.out.println("User wants to visit Corfu");
break;
case "Crete":
System.out.println("User wants to visit Crete");
break;
case "Santorini":
System.out.println("User wants to visit Santorini");
break;
case "Mykonos":
System.out.println("User wants to visit Mykonos");
break;
default:
System.out.println("Unknown Island");
break;
}
}
}
OP не спрашивает, как включить строку. Он / она спрашивает, почему он / она не может это сделать из-за ограничений синтаксиса до JDK7.
Когда вы используете intellij, также посмотрите:
Файл -> Структура проекта -> Проект
Файл -> Структура проекта -> Модули
Если у вас несколько модулей, убедитесь, что вы установили правильный уровень языка на вкладке модулей.
Не уверен, насколько ваш ответ имеет отношение к вопросу. Он спросил, почему недоступны следующие операторы переключения строк: String mystring = "something"; switch (mystring) {case "что-то" sysout ("попал сюда"); . . }
В других ответах говорится, что это было добавлено в Java 7 и даны обходные пути для более ранних версий. Этот ответ пытается ответить на вопрос «почему».
Java была реакцией на чрезмерную сложность C++. Он был разработан как простой чистый язык.
Строка получила небольшую обработку особых случаев в языке, но мне кажется очевидным, что дизайнеры пытались свести к минимуму количество специального корпуса и синтаксического сахара.
включение строк довольно сложно, поскольку строки не являются простыми примитивными типами. В то время, когда разрабатывалась Java, это не было распространенной особенностью, и она не очень хорошо вписывалась в минималистичный дизайн. Тем более, что они решили не использовать специальный случай == для строк, было бы (и есть) немного странно, если бы случай работал там, где == не работает.
Между 1.0 и 1.4 сам язык практически не изменился. Большинство усовершенствований Java было сделано на стороне библиотеки.
Все изменилось с выходом Java 5, язык был существенно расширен. Дальнейшие расширения последовали в версиях 7 и 8. Я ожидаю, что это изменение отношения было вызвано появлением C#.
Рассказ о переключателе (String) соответствует истории, временной шкале, контексту cpp / cs.
Не реализовывать эту функцию было большой ошибкой, все остальное - дешевое оправдание. За эти годы Java потеряла многих пользователей из-за отсутствия прогресса и упрямства дизайнеров, не желающих развивать язык. К счастью, они полностью изменили направление и отношение после JDK7.
Не очень красиво, но вот другой способ для Java 6 и ниже:
String runFct =
queryType.equals("eq") ? "method1":
queryType.equals("L_L")? "method2":
queryType.equals("L_R")? "method3":
queryType.equals("L_LR")? "method4":
"method5";
Method m = this.getClass().getMethod(runFct);
m.invoke(this);
JEP 354: Switch Expressions (предварительная версия) в JDK-13 и JEP 361: Switch Expressions (Стандарт) в JDK-14 расширяют оператор переключения, поэтому его можно использовать как выражение.
Теперь вы можете:
case L ->):
The code to the right of a "case L ->" switch label is restricted to be an expression, a block, or (for convenience) a throw statement.
To yield a value from a switch expression, the
breakwith value statement is dropped in favor of ayieldstatement.
Таким образом, демонстрация из ответов (1, 2) может выглядеть так:
public static void main(String[] args) {
switch (args[0]) {
case "Monday", "Tuesday", "Wednesday" -> System.out.println("boring");
case "Thursday" -> System.out.println("getting better");
case "Friday", "Saturday", "Sunday" -> System.out.println("much better");
}
В Java 11+ это возможно и с переменными. Единственное условие - он должен быть постоянным.
Например:
final String LEFT = "left";
final String RIGHT = "right";
final String UP = "up";
final String DOWN = "down";
String var = ...;
switch (var) {
case LEFT:
case RIGHT:
case DOWN:
default:
return 0;
}
PS. Я не пробовал этого с более ранними jdks. Пожалуйста, обновите ответ, если он там тоже поддерживается.
информация: метки должны быть "постоянными выражениями", начиная с версии 7: JLS 14.11
Технические детали были хорошо объяснены в этом отвечать. Я просто хотел добавить, что с Java 12 переключать выражения вы можете сделать это со следующим синтаксисом:
String translation(String cat_language) {
return switch (cat_language) {
case "miau miau" -> "I am to run";
case "miauuuh" -> "I am to sleep";
case "mi...au?" -> "leave me alone";
default -> "eat";
};
}
Это в SE 7. 16 лет после запроса. download.oracle.com/javase/tutorial/java/nutsandbolts/…