Dlaczego typ danych instrukcji przełącznika nie może być długi, Java?

80

Oto fragment samouczków Java firmy Sun :

Przełącznik działa z byte, short, chari intprymitywnych typów danych. Działa również z wymienionych typów (omówionych klas i dziedziczenie) i kilka specjalnych klas, które „wrap” pewne prymitywne typy: Character, Byte, Short, i Integer(omówione w prosty Data Objects).

Musi istnieć dobry powód, dla którego longpierwotny typ danych jest niedozwolony. Ktokolwiek wie co to jest?

Fostah
źródło

Odpowiedzi:

52

Myślę, że w pewnym stopniu była to prawdopodobnie arbitralna decyzja oparta na typowym zastosowaniu przełącznika.

Przełącznik można zasadniczo zaimplementować na dwa sposoby (lub w zasadzie kombinację): dla niewielkiej liczby przypadków lub tych, których wartości są szeroko rozproszone, przełącznik zasadniczo staje się odpowiednikiem serii if na zmiennej tymczasowej ( włączaną wartość można ocenić tylko raz). W przypadku umiarkowanej liczby przypadków, które mają mniej więcej następującą po sobie wartość, używana jest tablica przełączników (instrukcja TABLESWITCH w języku Java), dzięki czemu lokalizacja, do której należy przeskoczyć, jest efektywnie wyszukiwana w tabeli.

Każda z tych metod mogłaby zasadniczo używać wartości długiej zamiast liczby całkowitej. Ale myślę, że prawdopodobnie była to tylko praktyczna decyzja, aby zrównoważyć złożoność zestawu instrukcji i kompilatora z rzeczywistymi potrzebami: przypadki, w których naprawdę trzeba przełączyć się na długi czas, są na tyle rzadkie, że dopuszczalne jest ponowne napisanie jako serię instrukcji IF lub obejść w inny sposób (jeśli długie wartości, o których mowa, są blisko siebie, możesz w kodzie Java przełączyć się na wynik int, odejmując najniższą wartość).

Neil Coffey
źródło
Muszę się zgodzić z argumentem „rzadkość”, ponieważ od jakiegoś czasu rozwijam się w Javie i nigdy nie natknąłem się na sytuację, w której potrzebowałem / próbowałem włączyć się przez długi czas.
Fostah
3
Dimitrisie, moje rozumowanie nie opierało się na średnim zrozumieniu bezpieczeństwa nici, tylko to, że nie sądzę, aby argument dotyczący bezpieczeństwa nici, który wysunąłeś, był słuszny.
Neil Coffey
13
Najgłupsza decyzja projektowa w historii. Mam długą, która jest wiązką flag, i muszę nosić ze sobą, jeśli ... inaczej, jeśli ... jeszcze ... Całkowicie śmieszne.
m0skit0
2
Zgadzam się w / @ m0skit0. W moim przypadku ktoś inny napisał API, które używa długich znaków jako stałych, ponieważ to właśnie przechowuje w bazie danych. Teraz nie mogę użyć przełącznika / obudowy z powodu złej decyzji innej osoby.
CodeChimp
1
Nie mówię, że to „koniec świata”. I tak, są sposoby na obejście tego, ale wszystkie wyglądają jak hacki. Chodzi mi o to, że jako programiści powinniśmy zawsze starać się pomyśleć o tym, jak myślimy, że ludzie będą używać stworzonych przez nas rzeczy. W tym przypadku ktoś podjął decyzję, aby nie zezwolić na zmianę / przypadek na długi czas, na podstawie początkowej myśli, że „Kto uczciwie miałby 2 ^ 64 przypadki”. Może to nadmierne uproszczenia. Może jest jakiś inny powód, dla którego nie można zamienić tęsknoty, że po prostu nie byliśmy wtajemniczeni. Dla mnie po prostu wydaje mi się dziwne, że go nie popieram.
CodeChimp
21

Ponieważ nie zaimplementowali niezbędnych instrukcji w kodzie bajtowym i naprawdę nie chcesz pisać tak wielu przypadków, bez względu na to, jak "gotowy do produkcji" jest twój kod ...

[EDYTUJ: Wyciąg z komentarzy do tej odpowiedzi, z kilkoma dodatkami w tle]

Mówiąc dokładniej, 2³² to wiele przypadków, a każdy program z metodą wystarczająco długą, by pomieścić więcej, będzie całkowicie przerażający! W dowolnym języku. (Najdłuższa funkcja, jaką znam w każdym kodzie w jakimkolwiek języku, to nieco ponad 6k SLOC - tak, jest duża switch- i naprawdę nie da się nią zarządzać.) Jeśli naprawdę utkniesz w miejscu, w longktórym powinieneś mieć tylko intlub mniej wtedy masz dwie prawdziwe alternatywy.

  1. Użyj longinnego wariantu tematu funkcji skrótu, aby skompresować plik do pliku int. Najprostszym, do użycia tylko wtedy, gdy masz zły typ, jest po prostu rzucanie! Bardziej przydatne byłoby zrobienie tego:

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

    przed włączeniem wyniku. Będziesz musiał dowiedzieć się, jak przekształcić przypadki, przeciwko którym testujesz. Ale tak naprawdę to nadal jest okropne, ponieważ nie rozwiązuje prawdziwego problemu wielu przypadków.

  2. Znacznie lepszym rozwiązaniem, jeśli pracujesz z bardzo dużą liczbą przypadków, jest zmiana projektu na użycie Map<Long,Runnable>czegoś podobnego, aby sprawdzić, jak wysłać określoną wartość. Pozwala to na podzielenie spraw na wiele plików, co jest znacznie łatwiejsze do zarządzania, gdy liczba przypadków rośnie, chociaż organizowanie rejestracji wielu zaangażowanych klas implementacyjnych staje się bardziej skomplikowane (adnotacje mogą pomóc, umożliwiając utworzyć kod rejestracyjny automatycznie).

    FWIW, zrobiłem to wiele lat temu (przestawiliśmy się na nowo wydaną J2SE 1.2 częściowo przez projekt) podczas budowania niestandardowego silnika kodu bajtowego do symulacji sprzętu na masową skalę (nie, ponowne użycie JVM nie byłoby odpowiednie ze względu na radykalnie różne modele wartości i wykonania) i ogromnie uprościło kod w stosunku do dużego switch, którego używała wersja C kodu.

Aby powtórzyć wiadomość, którą można zabrać do domu, chęć switchwłączenia a longjest wskazówką, że albo masz nieprawidłowe typy w swoim programie, albo budujesz system z tak dużą różnorodnością, że powinieneś używać klas. W obu przypadkach czas na przemyślenie.

Donal Fellows
źródło
1
Ale czy przełączany zakres jest rzeczywiście większy niż 32 bity? Nigdy nie słyszałem o kodzie, który wymagałby tylu ramion przełączników. Bez tego możesz skompaktować zakres (np. Używając pewnej wariacji na temat funkcji skrótu), aby stworzyć coś, co będzie działać. Lub użyj, Map<Long,Runnable>aby rozwiązać problem w zupełnie inny sposób. :-)
Donal Fellows
3
@Lord Torgamus: Prawdopodobnie dlatego, że jest głupi. Pomyśl o tym przez chwilę: dlaczego ktokolwiek, ktokolwiek miałby mieć kod z więcej niż 2 32 ramionami w switch? Chęć wybrania skończonego zestawu tak wielu elementów po prostu wskazuje na błąd w podstawowym projekcie programu. To, że ludzie o to proszą, oznacza jedynie, że ** źle zaprojektowali swój projekt .
Donal Fellows
1
Swoją drogą, jeśli ktoś chce się ze mną dalej spierać, proszę zacznij od podania przypadku użycia do włączenia długiego. W przeciwnym razie utkniemy na zawsze w kłótni z hipotezami ...
Donal Fellows
2
@DonalFellows, inny przypadek jest w systemie Android z ListView. Podczas gdy listViews może mieć longliczbę wierszy, potrzebuję tylko 4. Jest to przypadek, w którym longotrzymałem informację, która wskazuje, który wiersz jest wykonywany, i muszę zrobić różne rzeczy w zależności od tego, który to wiersz. Myślę, że mógłbym rzutować na int, ale ułatwiłoby mi to życie, gdybym mógł po prostu switchwłączyć tę zmienną. Obecnie używam tylko ciągu znaków ifi else if.
Christian Smith
7
„Aby powtórzyć wiadomość o zabraniu do domu, chęć włączenia długiego włączenia oznacza, że ​​albo masz błędne typy w swoim programie” - Nie, nie jest! Posiadanie longnie oznacza, że ​​zamierzasz sprawdzić wszystkie możliwości, tak jak posiadanie intlub Stringnie oznacza, że ​​też. Oznacza to, że wartości, które masz, które mogą być garstką, mają duży zakres . Możesz sprawdzić kilka prostych przypadków i zająć się defaultresztą. Konieczność wykonywania zmian i rzutów oznacza, że ​​ryzykujesz utratę danych. Podsumowując, jest to zła decyzja dotycząca projektu Java, a nie problem użytkownika.
jbx
6

Ponieważ indeks tabeli przeglądowej musi mieć 32 bity.

JRL
źródło
3
Ale z drugiej strony switchnie trzeba koniecznie implementować tabeli przeglądowej.
Joachim Sauer
10
Gdyby tak było, nigdy nie mogliby zaimplementować przełącznika dla Strings (tak jak obecnie planują).
Dimitris Andreou
1
@DimitrisAndreou tak, możemy teraz przełączyć się w Strings: D (od lat: P)
J. Doe
-11

Długi, w architekturze 32-bitowej, jest reprezentowany przez dwa słowa . Teraz wyobraź sobie, co mogłoby się stać, gdyby z powodu niewystarczającej synchronizacji wykonanie instrukcji switch obserwowało długi z jego wysokimi 32 bitami z jednego zapisu i 32 niskimi z innego! Mógłby spróbować ... kto wie gdzie! Zasadniczo gdzieś przypadkowo. Nawet gdyby oba zapisy reprezentowały ważne przypadki dla instrukcji przełącznika, ich zabawna kombinacja prawdopodobnie nie prowadziłaby ani do pierwszego, ani do drugiego - lub, co gorsza, mogłaby prowadzić do innego ważnego, ale niezwiązanego z nim przypadku!

Przynajmniej w przypadku typu int (lub mniejszych typów), bez względu na to, jak bardzo zepsujesz, instrukcja switch przynajmniej odczyta wartość, którą ktoś faktycznie napisał , zamiast wartości „z powietrza”.

Oczywiście nie znam faktycznego powodu (minęło ponad 15 lat, nie zwracałem na to uwagi tak długo!), Ale jeśli zdasz sobie sprawę, jak niebezpieczny i nieprzewidywalny może być taki konstrukt, zgodzisz się, że jest to zdecydowanie bardzo dobry powód, aby nigdy nie włączać długich pozycji (i tak długo, jak zamierzano - będą maszyny 32-bitowe, ten powód pozostanie ważny).

Dimitris Andreou
źródło
1
Nie sądzę, żeby to następowało. Włączana wartość musi zostać obliczona i zapisana w rejestrze lub na stosie. Jeśli ta wartość jest obliczana na podstawie danych, do których uzyskuje dostęp wiele wątków, to obliczenie musi być bezpieczne dla wątków, niezależnie od szerokości wyniku. Ale potem, gdy ten wynik znajdzie się w rejestrze lub na stosie, jest dostępny tylko przez wątek przełączający i jest bezpieczny.
Neil Coffey
Neil, twój argument jest dość zagmatwany: „Ale kiedy wynik ten znajdzie się w rejestrze lub na stosie, jest dostępny tylko przez wątek przełączający i jest bezpieczny”. Jasne, używanie tej wartości jest bezpieczne dla wątków! Chodzi mi jednak o to, że ta wartość może _ już_ być błędna z powodu błędów synchronizacji w kodzie użytkownika . Używanie bezpiecznego wątku z niewłaściwą wartością jest mniej niż przydatne :) Tego problemu nigdy nie można wyeliminować: błędny kod współbieżny mógł już wygenerować „z powietrza” / niewłaściwą długą wartość, którą można następnie wykorzystać w przełączniku switch przejdź do adresu sprawy, którego nikt nigdy nie podał .
Dimitris Andreou
1
Dimitris, może w twojej argumentacji jest coś, czego nie rozumiem. Włączona wartość może rzeczywiście być błędna z powodu błędów synchronizacji w kodzie użytkownika. Ale nie sądzę, aby było coś nieodłącznego w oświadczeniu o przełączniku, co sprawia, że ​​jest to bardziej prawdopodobne niż w innych przypadkach. A myśląc o tym najlepiej, jak potrafię, nie wierzę, że brak atomowości wysokich / niskich słów długich odczytów / zapisów do pamięci jest w rzeczywistości problemem. (Myśląc o rzeczach w inny sposób: możesz zdecydować, że jeśli porównanie na pozycji długiej jest niedozwolone na podstawie tego samego argumentu.)
Neil Coffey
O ile potencjalne problemy związane z długim przedstawieniem dwóch słów bez gwarantowanych zapisów atomowych są, zgodnie z porozumieniem, kwestią ogólną, o tyle w przypadku przełącznika byłoby to jeszcze bardziej wyraźne zagrożenie. To tak, jakby wysłać kopertę z wiadomością, w której połowa adresu pochodzi od jednej osoby, a połowa od drugiej - ostateczny adres może być ważny i odpowiadać całkowicie przypadkowemu facetowi, który otrzyma kopertę i postąpi zgodnie z nią. Czytanie śmieci i wytwarzanie śmieci to jedno, ale czytanie śmieci i wykonywanie przypadkowych skoków wydaje mi się odrobinę bardziej niebezpieczne.
Dimitris Andreou
7
Wiem, że to jest stare i komentowanie tego jest trochę dyskusyjne, ale chcę podkreślić, że twój argument dotyczy ifrównież i wynik byłby równie zły: zły wynik ~> niewłaściwa gałąź. Tworzenie długi if- else- ifłańcuch zamiast switchfaktycznie doprowadzić do dokładnie tego samego rezultatu.
Nicolai Parlog