2008-10-06 11 views
35

Widząc, że Java nie ma typów zerujących, ani nie ma TryParse(), jak radzić sobie z sprawdzaniem danych wejściowych bez zgłaszania wyjątków?Ciąg do Int w java - Prawdopodobnie złe dane, należy unikać wyjątków

Zwykłym sposobem:

String userdata = /*value from gui*/ 
int val; 
try 
{ 
    val = Integer.parseInt(userdata); 
} 
catch (NumberFormatException nfe) 
{ 
    // bad data - set to sentinel 
    val = Integer.MIN_VALUE; 
} 

mogę użyć wyrażenia regularnego, aby sprawdzić, czy to parsowalnym, ale to wydaje się dużo napowietrznych, jak również.

Jaka jest najlepsza praktyka w radzeniu sobie z tą sytuacją?

EDYCJA: Uzasadnienie: Dużo się mówi na temat obsługi wyjątków, a ogólna opinia jest taka, że ​​wyjątki powinny być używane tylko w przypadku nieoczekiwanych scenariuszy. Jednak uważam, że złe dane wejściowe użytkownika są OCZEKIWANE, a nie rzadkie. Tak, to naprawdę jest punkt akademicki.

Kolejne edycje:

Niektóre z odpowiedzi wykazać dokładnie to, co jest nie tak z SO. Ignorujesz pytanie, które zadajesz, i odpowiadasz na inne pytanie, które nie ma z tym nic wspólnego. Pytanie nie pyta o przejście między warstwami. Pytanie nie pyta, co zwrócić, jeśli liczba nie jest parsowana. Dla wszystkich, których znasz, val = Integer.MIN_VALUE; jest dokładnie odpowiednią opcją dla aplikacji, z której powstał ten całkowicie wolny od kontekstu fragment kodu.

+5

wejścia Bad użytkownika jest zawsze oczekiwano. Masz całkowitą rację. Z tego powodu nie powinien mieć próby/catch. C#> Java; przykładem! – core

+1

http://msdn.microsoft.com/en-us/library/bb397679.aspx // ToInt32 może wygenerować wyjątek FormatException lub OverflowException. spróbuj { numVal = Convert.ToInt32 (wejście); } } catch (FormatException e) { Console.WriteLine ("Ciąg wejściowy nie jest ciągiem cyfr."); } } catch (OverflowException e) { Console.WriteLine ("Liczba nie mieści się w Int32."); } wreszcie { } niiiiiceeee! –

+1

@AndrewFink Oh, ale nawet jeśli był to ważny punkt (OP mówił o TryParse, który nie ma) liczba, która nie pasuje * jest * nieoczekiwana. –

Odpowiedz

16

To prawie wszystko, chociaż powrót do wartości MIN_VALUE jest wątpliwy, chyba że masz pewność, że używasz tego, co w zasadzie używasz jako kodu błędu. Ale przynajmniej udokumentowałem zachowanie kodu błędu.

Może być również przydatny (w zależności od aplikacji) do rejestrowania złych danych wejściowych, aby można było śledzić.

+1

Cóż, przykład został wymyślony. Mogę również ustawić flagę w bloku catch - userInputIsGood = false; –

+0

Jeśli twoja aplikacja nie analizuje liczb całkowitych wprowadzanych przez użytkownika, a ci użytkownicy naprawdę źle wpisują liczby całkowite, nie oczekiwałbym znacznego narzutu wyjątku. – erickson

+0

na podstawie tego, co mówisz, że @erickson? chyba że wykonasz misję o znaczeniu krytycznym, zajmie to 8 milisekund, nawet jeśli popełni błąd, wykonując 100 takich prób dla różnych ciągów znaków, z których niektóre powodują błędy, które są rejestrowane w pliku. – tgkprog

-5

Umieść przed nim jakieś instrukcje if. if (null! = Userdata)

+3

Nie działa. Co się stanie, jeśli użytkownik wpisze "Eleventy" lub 6.2 –

1

Uważam, że najlepszą praktyką jest kod, który pokazujesz.

Nie wybrałbym alternatywy dla regex ze względu na obciążenie.

+0

Kompilacja Regex (raz wykonana) jest narzutem, ale wykonanie wyrażenia regularnego to O (n) w rozmiarze wejścia. – Notinlist

11

Jaki jest problem z twoim podejściem? Nie sądzę, aby robienie tego w ten sposób wpłynęło na wydajność twojej aplikacji. To jest właściwy sposób, aby to zrobić. Nie optymalizuj przedwcześnie.

+8

To nie jest przedwczesna optymalizacja, aby uniknąć rzucania wyjątków w lewo i prawo. Podczas programowania wymagane jest pewne zdrowie psychiczne, niezależnie od tego, kiedy chcemy "zoptymalizować". – Zenexer

6

Jestem pewien, że jest to zła forma, ale mam zestaw metod statycznych w klasie narzędzi, które robią takie rzeczy jak Utilities.tryParseInt(String value) który zwraca 0 jeśli łańcuch jest unparseable i Utilities.tryParseInt(String value, int defaultValue) który pozwala określić wartość do użycia, jeśli parseInt() zgłasza wyjątek.

Wierzę, że zdarzają się sytuacje, w których zwracanie znanej wartości na błędnym wejściu jest całkowicie dopuszczalne. Bardzo wymyślny przykład: pytasz użytkownika o datę w formacie YYYYMMDD i daje ci złe dane wejściowe. W zależności od wymagań programu może być całkowicie dopuszczalne wykonanie czegoś takiego jak Utilities.tryParseInt(date, 19000101) lub Utilities.tryParseInt(date, 29991231);.

+0

Mam tę samą metodę narzędzie, ale zamiast tego zwracaj liczbę całkowitą i użyj wartości null dla niepoprawnych liczb całkowitych. W pewnym momencie jeden z łańcuchów wywołań będzie musiał zająć się nieprawidłową liczbą całkowitą. –

0

Możesz użyć liczby całkowitej, która może mieć wartość null, jeśli masz złą wartość. Jeśli używasz java 1.6, zapewni Ci automatyczne boksowanie/rozpakowywanie.

-1

Powyższy kod jest nieprawidłowy, ponieważ jest równoważny poniższemu.

// this is bad 
int val = Integer.MIN_VALUE; 
try 
{ 
    val = Integer.parseInt(userdata); 
} 
catch (NumberFormatException ignoreException) { } 

Wyjątek jest całkowicie ignorowany. Ponadto magiczny token jest zły, ponieważ użytkownik może przejść w -2147483648 (Integer.MIN_VALUE).

Ogólne pytanie, które można przeanalizować, nie jest korzystne. Powinien raczej być odpowiedni do kontekstu. Twoja aplikacja ma określone wymagania. Można zdefiniować swoją metodę jako

private boolean isUserValueAcceptable(String userData) 
{ 
    return ( isNumber(userData)  
      && isInteger(userData) 
      && isBetween(userData, Integer.MIN_VALUE, Integer.MAX_VALUE) 
     ); 
} 

gdzie można dokumentacja wymóg i można stworzyć dobrze zdefiniowane i sprawdzalne zasad.

+0

Mówisz "Powyższy kod jest zły." Dlaczego właściwie lepiej jest przypisać wartość domyślną i zignorować wyjątek? –

+0

Och, rozumiem. Mówiłeś, że oba były złe. –

+1

Chciałbym zobaczyć, jak to jest metoda Beethoven() jest zaimplementowana bez analizowania łańcucha ... Więc kończysz przetwarzanie ciągu dwa razy - w scenariuszach walidacji jest to jednak normalne. Nie wiem, dlaczego ktokolwiek cię zmodelował, modyfikuję go z powrotem - jesteś na bieżąco z tym. –

17

Dla danych dostarczanych przez użytkownika, Integer.parseInt jest zwykle złą metodą, ponieważ nie obsługuje internacjonalizacji. Pakiet java.text jest Twoim (gadatliwym) przyjacielem.

try { 
    NumberFormat format = NumberFormat.getIntegerInstance(locale); 
    format.setParseIntegerOnly(true); 
    format.setMaximumIntegerDigits(9); 
    ParsePosition pos = new ParsePosition(0); 
    int val = format.parse(str, pos).intValue(); 
    if (pos.getIndex() != str.length()) { 
     // ... handle case of extraneous characters after digits ... 
    } 
    // ... use val ... 
} catch (java.text.ParseFormatException exc) { 
    // ... handle this case appropriately ... 
} 
+0

Formater jest dobrym pomysłem do analizowania surowych danych wprowadzanych przez użytkownika. Problem leży w implementacji. DecimalFormat może niecałkowicie analizować dane wejściowe, np. "123,99 + jeden tuzin". Czy użytkownik miał na myśli "123"? "124"? "136"? Jedyną bezpieczną rzeczą jest odrzucenie tego, ale program DecimalFormat po cichu zwróci 123. – erickson

+0

Tak. Naprawiony. Dzięki. –

+1

Myślę, że to było przed @ komentarzami i do tej pory nie widziałem twojej edycji. +2! – erickson

2

Oto jak to zrobić:

public Integer parseInt(String data) { 
    Integer val = null; 
    try { 
    val = Integer.parseInt(userdata); 
    } catch (NumberFormatException nfe) { } 
    return val; 
} 

Następnie sygnały zerowe nieprawidłowych danych. Jeśli chcesz mieć wartość domyślną, można go zmienić na:

public Integer parseInt(String data,int default) { 
    Integer val = default; 
    try { 
    val = Integer.parseInt(userdata); 
    } catch (NumberFormatException nfe) { } 
    return val; 
} 
+0

Niewielka optymalizacja: ustaw domyślną wartość w bloku catch –

+3

Steve Kuo - Nah - to nie jest optymalizacja - to tylko styl kodowania. javac wygeneruje ten sam kod bajtowy niezależnie od tego, co zrobisz z tym. –

+0

Lol. @SteveKuo Mimo to nadal znajduje się na stosie. – TheRealChx101

-1

Jeśli można uniknąć wyjątki testując wcześniej jak mówiłeś (isParsable()) może być lepiej - ale nie wszystkie biblioteki, które zostały zaprojektowane z na uwadze.

Kiedyś trick i to jest do bani bo ślady stos na moim wbudowanego systemu są drukowane niezależnie od tego, czy je złapać czy nie :(

-2

Mechanizm Wyjątkiem jest cenne, ponieważ jest to jedyny sposób, aby uzyskać status Wskaźnik w połączeniu z wartością odpowiedzi Ponadto, wskaźnik statusu jest ustandaryzowany Jeśli wystąpi błąd, otrzymasz wyjątek, w ten sposób nie musisz samodzielnie wymieniać wskaźnika błędu Kontrowersje to nie tyle wyjątki , ale z zaznaczonymi wyjątkami (np. te, które musisz złapać lub zadeklarować)

Osobiście uważam, że wybrałeś jeden z przykładów, w których wyjątki są prawdziwe ly cenny. Jest to powszechny problem, w którym użytkownik wprowadza błędną wartość, i zazwyczaj trzeba będzie zwrócić się do użytkownika, aby uzyskać poprawną wartość. Zwykle nie powracasz do domyślnej wartości, jeśli zapytasz użytkownika; daje to użytkownikowi wrażenie, że jego wkład ma znaczenie.

Jeśli nie chcesz zajmować się wyjątkiem, po prostu zapakuj go w wyjątek RuntimeException (lub klasę pochodną), co pozwoli ci zignorować wyjątek w kodzie (i zabić aplikację, gdy wystąpi, to też jest w porządku czasami).

kilka przykładów, jak bym sobie wyjątki numberFormat: W internetowej danych konfiguracyjnych aplikacji:

loadCertainProperty(String propVal) { 
    try 
    { 
    val = Integer.parseInt(userdata); 
    return val; 
    } 
    catch (NumberFormatException nfe) 
    { // RuntimeException need not be declared 
    throw new RuntimeException("Property certainProperty in your configuration is expected to be " + 
           " an integer, but was '" + propVal + "'. Please correct your " + 
           "configuration and start again"); 
    // After starting an enterprise application the sysadmin should always check availability 
    // and can now correct the property value 
    } 
} 

W GUI:

public int askValue() { 
    // TODO add opt-out button; see Swing docs for standard dialog handling 
    boolean valueOk = false; 
    while(!valueOk) { 
    try { 
     String val = dialog("Please enter integer value for FOO"); 
     val = Integer.parseInt(userdata); 
     return val; 
    } catch (NumberFormatException nfe) { 
     // Ignoring this; I don't care how many typo's the customer makes 
    } 
    } 
} 

w formie internetowej: powrót formę użytkownikowi z przydatnym komunikatem o błędzie i szansą na poprawne poprawienie wartości . Większość frameworków oferuje znormalizowany sposób sprawdzania poprawności.

3

mam zamiar przekształcenia tego stopnia, że ​​stinkyminky robił w kierunku dolnej części postu:

ogólnie dobrze przyjęte podejście Weryfikacja danych użytkownika (lub wejście z plików konfiguracyjnych, etc ...) jest w użyciu sprawdzanie poprawności przed faktycznym przetwarzaniem danych. W przypadku większości przypadków jest to dobry ruch konstrukcyjny, nawet jeśli może powodować wiele wywołań algorytmów analizy.

Po sprawdzeniu poprawności danych wprowadzonych przez użytkownika: , następnie, można bezpiecznie je przeanalizować i zignorować, zarejestrować lub przekonwertować na RuntimeException wyjątek NumberFormatException.

Należy zauważyć, że to podejście wymaga uwzględnienia modelu w dwóch elementach: modelu biznesowego (gdzie naprawdę zależy nam na wartościach w formacie int lub float) oraz modelu interfejsu użytkownika (gdzie naprawdę chcemy umożliwić użytkownikowi włożyć wszystko, co chcą).

Aby dane mogły migrować z modelu interfejsu użytkownika do modelu biznesowego, musi przejść przez etap sprawdzania poprawności (może się to zdarzyć w zależności od pola, ale większość scenariuszy wymaga sprawdzenia poprawności na całym obiekcie, jest konfigurowany).

Jeśli sprawdzanie poprawności nie powiedzie się, użytkownik otrzyma informację zwrotną informującą o tym, co zrobił źle i ma szansę go naprawić.

Biblioteki wiążące, takie jak JGoodies Binding i JSR 295, sprawiają, że tego typu rzeczy są łatwiejsze do wdrożenia, niż mogłoby się wydawać - a wiele frameworków internetowych udostępnia konstrukcje, które oddzielają dane użytkownika od rzeczywistego modelu biznesowego, a jedynie wypełnianie obiektów biznesowych po sprawdzeniu poprawności kompletny.

Jeśli chodzi o sprawdzanie poprawności plików konfiguracyjnych (drugi przypadek użycia przedstawiony w niektórych komentarzach), to jednoznaczne określenie wartości domyślnej, jeśli konkretna wartość nie jest określona w ogóle - ale jeśli dane są sformatowane źle (ktoś wpisuje "o" zamiast "zero" - lub skopiowano je z MS Worda, a wszystkie back-ticky mają funkową postać unicode), wtedy potrzebne są jakieś informacje zwrotne od systemu (nawet jeśli jest to po prostu nieudana aplikacja przez rzucanie wyjątek środowiska wykonawczego).

+0

wow - a down mod bez uzasadnienia. Jak przyjemnie ... –

+3

Nie odpowiada na pytanie – redben

+0

Myślę, że odpowiada na pytanie; mówi, że najpierw sprawdza poprawność ciągu znaków (np. z RegEx), a następnie nie musisz wychwytywać wyjątku NumberFormatException. Chociaż przykład może być wyraźniejszy niż ogromny blok tekstu. – AjahnCharles

-2

Integer.MIN_VALUE jako NumberFormatException to zły pomysł.

Możesz dodać propozycję projektu monety, aby dodać tę metodę Integer

@Nullable public static Integer parseInteger (src String) ... zwróci null dla złej wejścia

następnie umieścić link Twoja propozycja tutaj, a my wszyscy będziemy głosować na nią!

PS: Spójrz na to http://msdn.microsoft.com/en-us/library/bb397679.aspx ten sposób brzydki i wleczenia może być

+2

Chciałbym móc powtórzyć to dwa razy. – MedicineMan

1

Spróbuj org.apache.commons.lang.math.NumberUtils.createInteger(String s). To bardzo mi pomogło. Istnieją podobne metody dla podwójnych, długich itp.

+0

Dokumentacja dla createInteger mówi, że generuje wyjątek NumberFormatException, jeśli nie może przeanalizować łańcucha: https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/ org/apache/commons/lang/math/NumberUtils.html # createInteger% 28java.lang.String% 29 –

27

Zapytałem if there were open source utility libraries that had methods to do this parsing for you, a odpowiedź brzmi: tak!

Od Apache Commons Lang można użyć NumberUtils.toInt:

// returns defaultValue if the string cannot be parsed. 
int i = org.apache.commons.lang.math.NumberUtils.toInt(s, defaultValue); 

Od Google Guava można użyć Ints.tryParse:

// returns null if the string cannot be parsed 
// Will throw a NullPointerException if the string is null 
Integer i = com.google.common.primitives.Ints.tryParse(s); 

Nie ma potrzeby, aby napisać własne metody do analizowania liczb bez rzucania wyjątków.

+0

Zauważ, że drugi przykład z Guava DOES wyrzuca wyjątek, jeśli s ma wartość null, zamiast tego możesz użyć 'org.apache.commons .lang.math.NumberUtils.createInteger (s) 'jak zasugerowano w odpowiedzi @Zlosny. – MilesHampson

+0

Zgodnie z [javadoc for createInteger] (https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/math/NumberUtils.html # createInteger% 28java.lang.String% 29) wyświetli wyjątek formatu liczbowego, jeśli nie będzie mógł przeanalizować łańcucha. Dodałem notatkę o wyjątku wskaźnika pustego do przykładu Guava w odpowiedzi. –

0

Cleaner semantyka (Java 8 OptionalInt)

Dla Java 8+, prawdopodobnie użyć wyrażenia regularnego do filtra wstępnego (w celu uniknięcia wyjątek jak zauważył), a następnie owinąć wynik w prymitywny opcjonalne (do czynienia z „domyślnego” problem):

public static OptionalInt toInt(final String input) { 
    return input.matches("[+-]?\\d+") 
      ? OptionalInt.of(Integer.parseInt(input)) 
      : OptionalInt.empty(); 
} 

Jeśli masz wiele wejść ciąg, można rozważyć zwrócenie IntStream zamiast OptionalInt dzięki czemu można flatMap().

Referencje