Jaka jest korzyść z włączenia ciągów w Javie 7?


19

Kiedy zaczynałem programować w Javie, frustrowało mnie to, że instrukcje switch nie miały ciągów. Następnie, korzystając z Enums, zdałem sobie sprawę z korzyści, jakie z nich czerpiesz, zamiast przekazywać surowe wartości - bezpieczeństwo typu (co ułatwia refaktoryzację) oraz przejrzystość dla innych programistów.

Próbuję wymyślić sytuację, w której w SE7 zdecyduję się teraz użyć przełącznika z ciągami jako wejściami, a nie z Enums. Jeśli są one implementowane poprzez przełączanie całych łańcuchów (np. Zamiast dopasowań częściowych lub wyrażeń regularnych), wydaje się, że nie oferuje mniejszego powodu do zmiany kodu.

A dzięki narzędziom IDE i stosunkowi kodowania do odczytu i zapisu znacznie chętniej wygenerowałbym dodatkowe Enum niż przekazywanie wartości ciągów.

Jakie korzyści przynoszą nam jako programistom? Mniej płyty kotła?

Nie wydaje się, żeby język wołał o tę funkcję. Chociaż może istnieje przypadek użycia, który przeoczam.


Ciągi są już często używane w instrukcjach przełączników, ale ponieważ ludzie nie mogli ich używać bezpośrednio, uciekali się do obejść takich jak olbrzym, jeśli inaczej drzewa lub konwersje do wyliczeń. Oba obejścia powodują, że kod jest mniej czytelny. Oba sposoby są obejściami i dlaczego warto skorzystać z obejścia, jeśli natywnie zaimplementowane rozwiązanie lepiej pokazuje intencje programistów.
Pieter B,

@PieterB Prawdopodobnie Enum dodaje wartość semantyczną, a nie jest obejściem, co zapewnia bezpieczeństwo typu (np. Literówka zostanie złapana w czasie kompilacji, a nie doprowadzi do błędu). Pozwala nam również refaktoryzować wszystkie wystąpienia ciągu, tak jak jest to używane w tym kontekście , bez wpływu na inne zastosowania tego ciągu (co może się zdarzać dość często w przypadku obiektów domeny).
anotherdave

Odpowiedzi:


12

O ile mogę stwierdzić, pytanie brzmi: dlaczego zawijanie stałych String do enum nie zostało uznane za wystarczające do zaspokojenia potrzeb użytkowników języka. Zostało to rozwiązane w oficjalnej propozycji funkcji ogłoszonej na liście mailingowej JDK 7 (Project Coin).

Według mojej lektury propozycji odrzucono alternatywę używania wyliczeń, ponieważ wprowadza ona typy wzdęć. Dla Twojej wygody odpowiednia część wniosku jest cytowana poniżej, a oświadczenie odnoszące się do wyliczeń pogrubione :

GŁÓWNA ZALETA: Co sprawia, że ​​wniosek jest korzystną zmianą?

Bardziej regularne wzorce kodowania można zastosować do operacji wybranych na podstawie zestawu stałych wartości ciągu; znaczenie nowej konstrukcji powinno być oczywiste dla programistów Java.

NAJWAŻNIEJSZE KORZYŚCI: Dlaczego platforma jest lepsza, jeśli wniosek zostanie przyjęty?

Potencjalnie lepsza wydajność dla kodu wysyłki opartego na łańcuchach.

GŁÓWNA WADA: Zawsze jest jakiś koszt.

Zwiększona złożoność implementacji i testowania kompilatora.

ALTERNATYWY: Czy można uzyskać korzyści i zalety bez zmiany języka?

Nie; połączone łańcuchy testy równości łańcuchów są potencjalnie drogie, a wprowadzenie wyliczenia stałych przełączalnych, po jednej na interesującą wartość ciągu, dodałoby inny typ programu bez dobrego powodu ...


odważna część jest dość zaskakująca
Simon Bergot

1
@ Simon dobrze dla mnie osobiście brzmi to jako pośredni traktat: „przegramy konkurencję do C #, jeśli będziemy nadal trzymać się sposobów wcześniejszych niż Java5”. :) Przykład „sposobów wcześniejszych niż Java5” można znaleźć w JDK-1223179: włączanie ciągów - przesłane w 1995 r. I szybko zamknięte jako Won't Fix : „Nie wstrzymuj oddechu. Nic nie przypomina tego w naszych planach . ”
komara

Dzięki, bardzo interesujące, zobaczyć powody przedstawione we wniosku. Nadal źle się mówi, że wprowadzamy typ programu „bez ważnej przyczyny” - jesteśmy programistami OO! :)
anotherdave

5
@anotherdave „obiektowo” uzasadnia wprowadzanie typów tylko wtedy, gdy służą one znaczącemu celowi. Najwyraźniej w JCP panuje opinia, że ​​użycie wyliczeń jako bezmyślnych opakowań dla stałych String w przełączniku nie kwalifikuje się jako znaczące
gnat

1
Wow, jestem zaskoczony (w dobrym sensie), że oficjalna propozycja aktywnie przeciwdziała wzdęciom typu. To odświeżające. Typ bloat to ogromny problem w Javie.
Ben Lee,

7

Oprócz zwiększenia czytelności kodu, istnieje potencjalny wzrost wydajności w if/else ifporównaniu z porównaniami. To, czy zmiana jest opłacalna, zależy od liczby porównań. Przełącznik łańcuchowy będzie emitowany jako dwie oddzielne instrukcje przełączania. Pierwszy działa na kody hash, więc wydaje się być lookupswitch, ostatecznie uzyskując O(log n)złożoności. Drugi jest zawsze idealny O(1) tableswitch, więc połączona złożoność jest nadal O(log n). Pojedynczy, liniowy łańcuch if/else ifinstrukcji dałby nieco gorszą O(n)złożoność.

Jeśli porównujesz więcej niż, powiedzmy, trzy ciągi, switchto prawdopodobnie jest bardziej czytelny i zwarty. Prawdopodobnie będzie działał lepiej, chociaż nie zauważysz różnicy, chyba że wykonasz dużą liczbę porównań na ścieżce kodu.


1
Bardziej pytałem jednak, dlaczego miałbym używać przełącznika na sznurku zamiast przełącznika na wyliczeniu. Rozważałbym dużą if/elseblokową złą praktykę, ale w tej chwili nie miałbym zbyt wiele powodów, by z niej korzystać.
anotherdave

Ale mapa w połączeniu z wzorcem poleceń dałaby nawet większą czytelność przy tej samej złożoności, ponieważ masz dodatkowy typ polecenia
SpaceTrucker,

3

Przełącznik ciągów znaków może być używany, gdy twoje enumwartości pochodzą z zewnątrz, tj. Są przechowywane w bazie danych.

Kolejną godną uwagi rzeczą w JDK 7 switchjest to, że jest znacznie bardziej wydajny niż if-elsekonstrukcje.

Dobrym przykładem użycia dla szybkich ciągów znaków switchmoże być parsowanie strumienia JSON / XML, gdy trzeba dokonać wielu przełączeń na typach węzłów i atrybutów. Nie mogę wymyślić lepszej opcji.


1
„Przełączanie na ciągi znaków jest niezastąpione, gdy wartości wyliczeniowe pochodzą z zewnątrz, tj. Są przechowywane w bazie danych.”: Czy możesz to rozwinąć? Ciągi w instrukcji switch są zakodowane na stałe: gdy tylko ciągi w bazie danych ulegną zmianie, kod jest nieaktualny.
Giorgio,

Cóż, masz rację - enumw tym przypadku można użyć z powodu statycznej natury Javy. Kiedy to pisałem, myślałem o Rolebibliotece bezpieczeństwa, która zwykle jest udostępniana jako klasa i nie można jej umieścić enum. Innym przypadkiem jest dynamicznie kompilowany kod Java / Groovy - w tej rzadkiej sytuacji lepszym rozwiązaniem mogą być przełączniki łańcuchowe.
Andrey Chaschev,

2

Prostota

Obsługa ciągu w przełączniku jest przydatna do przetwarzania danych bez konwersji na wyliczenie lub if-elselogikę. Czasami łatwiej jest włączyć String.

Od propozycji funkcji na liście mailingowej JDK 7 (Project Coin) ( odpowiedź @gnat )

wprowadzenie wyliczenia dla jego przełączalnych stałych, po jednej na interesującą wartość ciągu, dodałoby inny typ programu bez dobrego powodu ...

Wersja If-Else

To jest krótkie, ale wiele z nich if'sjest trudnych do odczytania. I to jest powolne.

if (color.equals("red")) {
    System.out.println("Color is Red");
} else if (color.equals("green")) {
    System.out.println("Color is Green");
} else {
    System.out.println("Color not found");
}

Wersja enum

Należy zdefiniować wyliczenia, to dobrze, ale czasami nie jest potrzebne.

enum Color {RED, GREEN}

Przetwarzanie jak zwykle

try {
    switch (Color.valueOf(color)) {
        case RED:
            System.out.println("Color is Red");
            break;
        case GREEN:
            System.out.println("Color is Green");
            break;
    }
} catch (IllegalArgumentException e) {
    System.out.println("Color not found");
}

JDK 7 - Ciągi w wersji instrukcji switch

Możemy przetwarzać bez konwersji i definiowania dodatkowych typów.

switch (color) {
    case "red":
        System.out.println("Color is Red");
        break;
    case "green":
        System.out.println("Color is Green");
        break;
    default:
        System.out.println("Color not found");
}

7
Nie dotyczy to jednak pytania: dlaczego miałbyś tutaj używać ciągów zamiast wyliczeń?
Dave Newton,

0

Większość ulepszeń w dowolnym języku ma na celu ułatwienie odczytu kodu. Wolę czytelny kod od wyszukanego każdego dnia.

Możesz w to nie wierzyć, ale spróbuj:

public enum JettStaff {
  ADRIAN("Adrian German") {
    public String toString() {
      return name + " (dgerman@indiana.edu)";
    }
  },
  ARJIT("Arjit Sengupta") {
    public String toString() {
      return name + " (asengupt@indiana.edu)";
    }
  },

  // and on for the rest...

  private String name;

  public JettStaff(String n) { this.name = n; }
}

JettStaff x = JettStaff.SUZANNE;
System.out.println(x);

Nie jestem pewien w twoim przykładzie kodu w porównaniu do twoich komentarzy powyżej. Czy masz na myśli Enum jako przykład czegoś trudnego do odczytania?
anotherdave


2
To jest bardzo wymyślone; udajesz, że wyliczenie nie miałoby pola e-mail lub że nie byłaby to klasa.
Dave Newton

4
@ user2860598 Jeśli próbujesz wskazać punkt, skorzystaj z odpowiedniego przykładu. Nie rozwiązuje również problemu ciągnięcia palcem tłuszczu.
Dave Newton

1
@ user2860598 Odpowiedź, którą podłączyłeś, mówi, że zostały wprowadzone i że wcześniej można je „zbliżyć” za pomocą Enum. Chodzi mi o to, że użycie Enum wydaje się być nadal preferowane, nawet po ich wprowadzeniu.
anotherdave

0

Wyliczenia są świetne i powinieneś ich używać zamiast Strun, jeśli masz taką możliwość. Ale są chwile, kiedy po prostu nie możesz, na przykład, gdy musisz pracować z jakimś zewnętrznym obiektem pochodzącym spoza Javy. Wyobraź sobie, że musisz coś przeanalizować. Na przykład odpowiedź serwera, konfiguracja, plik dziennika lub coś podobnego: masz wiele opcji, których szukasz, i nie ma możliwości, aby były to wyliczenia. Więc w Javie <7 utknąłbyś z wieloma

  if (something.equals("foo")) {
    // foo processing
  } else 
  if (something.equals("bar")) {
   // bar processing
  }

W tym przypadku możesz nadal używać wyliczeń, po prostu próbując uzyskać je według nazw i / lub zapewniając niestandardową procedurę String-> Enum, ale czasami jest to niemożliwe lub po prostu niepraktyczne (tj. Gdy będziesz musiał utworzyć zbyt wiele wyliczeń)

TLDR : absolutnie powinieneś używać Enums, gdy pracujesz wyłącznie z kodem Java, ale nie zawsze jest to możliwe w przypadku obiektów zewnętrznych. Mam nadzieję, że moje wyjaśnienie ma sens.


Jeśli masz do czynienia z danymi zewnętrznymi, a ty już wiesz, wystarczy wiedzieć, co trzeba w swoim ifsprawozdaniu, to należy owijając go w każdym razie , co oznacza, że można nadal używać teksty stałe lub wzorzec komendy, itp
Dave Newton

@DaveNewton tak, nadal możesz używać wyliczeń, ale jak powiedziałem w mojej odpowiedzi, czasami nie jest to praktyczne, jak w przypadku, gdy skończyłoby się tworzeniem wielu wyliczeń, które można użyć tylko raz w kodzie podczas parsowania.

@dimoniy Ale gdybyś musiał stworzyć setki wartości Enum, czy nie oznaczałoby to, że przy opcji zamiany musiałbyś mieć setki instrukcji przypadków? Jeśli jest tak wiele zmiennych, zdecydowanie wolałbym bezpieczeństwo typu wyliczeń.
anotherdave

0

Nie zgadzam się z opinią, że jest to czystszy i czytelniejszy kod, gdy używasz go Stringsw instrukcjach switch i uważam, że to zła praktyka programowania. Jeśli zmienisz wartość, której używasz do przechowywania, musisz ją zmienić przy każdym przełączeniu lub w przypadku wystąpienia if-elseif. Nie jest to dobre, ponieważ umieszczasz wartości zakodowane w każdym pliku kodu. Co zrobisz, jeśli pewnego dnia zdecydujesz się zmienić jedną z tych wartości zakodowanych na stałe? Wyszukaj i zamień każdą jego kopię?

Jeśli masz pewne zakodowane wartości, które wykonujesz w instrukcjach if-elseif, używanie dla nich stałych wartości pierwotnych jest znacznie lepsze niż w Javie 1.5 tylko dlatego, że zapewnia bezpieczeństwo typu.

OK, pojawią się sytuacje, w których otrzymasz wartości ciągu (z żądania HTTP, plików itp.), A konwersja ich do wartości pierwotnych jest uciążliwa. Ale Enumw tym momencie wykonujemy dobrą robotę. Ponieważ jeśli chcesz zmienić wartość przechowywaną w Enum, po prostu zmień ją podczas deklarowania. Nie musisz zmieniać niczego w kodzie. (Oczywiście musisz zmienić wartości zapisane gdzie indziej).

Oczywiście, jeśli tylko implementujesz brudny i leniwy kod, jest idealny. Nie chcesz angażować się w kodowanie wielu Enums, ale w złożone oprogramowanie na dużą skalę, które cię zabije.


-1

Jeśli wiesz, jakie informacje nadchodzą i że może to być tylko 5 stanów, użyj Enum. Jeśli jednak możliwe stany mogą być większe niż 9000, a wystarczy tylko znaleźć 42, to użycie przełącznika jest lepsze, ponieważ nie chcesz wpisywać wszystkich tych stanów.

Zazwyczaj Enum jest wyborem w większości przypadków, chyba że możliwe stany są nieznane lub jest ich wiele i zależy ci tylko na kilku.

Ale dlaczego wprowadzili to teraz? To była tylko zmiana, która pozwoliła na czystszy kod.

W wersji 1.5 pozwoliły również na tworzenie niestandardowych obiektów dla Enums.


Ale czy nie przypisalibyście innych 8958 możliwych wartości do jednego stanu Enum?
anotherdave

@anotherdave W tym defaultmiejscu switchwkracza gra. Nie wiem, czy możesz mieć stan domyślny Enum, chyba że wprowadzisz stan domyślny, ale wtedy ten kod jest po prostu rozdęty, podczas gdy switchużycie programu jest znacznie prostsze.

3
Dziwne wydaje się stwierdzenie, że UNKNOWNstan na Enum jest rozdęty, ale defaultprzypadek na przełączniku tak nie jest.
anotherdave
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.