2014-04-17 7 views
6

Dzisiaj pracowałem z klasą Java StringReader i uznałem to za bardzo irytujące, że wyrzuca ona wyjątek IOException na metodę read. Wiem, że to rozszerza klasę Reader, w której metoda read rzuca wyjątek IOException, ale myślę, że nie jest potrzebne dla StringReader. Ta klasa nie używa żadnych zewnętrznych zasobów, które mogą powodować błędy.Dlaczego Java StringReader zgłasza wyjątek IOException?

Po krótkim dochodzeniu okazało się, że StringReader#read rzuca IOException jeśli ciąg który brzmi ta klasa jest zerowy, ale de facto to nie może się zdarzyć ponieważ jeśli chcemy spróbować przekazać null StringReader konstruktora rzuca NPE.

Co sądzisz o tym, czy dobrą praktyką jest zawsze rzucać te same wyjątki, co super klasa?


Edit: Jak zauważył u mad Reader to klasa nie interfejs.

+1

Deklaracja rzutów w 'StringReader' jest obowiązkowa, ponieważ interfejs' Reader' wymaga tego, nawet jeśli klasa implementująca nigdy nie wyrzuci takiego wyjątku. –

+0

Jakie są powody oddawania głosów? –

+0

Czytnik jest klasą, a nie interfejsem i można zastąpić funkcję implementacją z mniejszą liczbą deklaracji lub bez wyjątków. I to zadziała. Więc nie ma powodu, żeby StringReader je zadeklarował. –

Odpowiedz

1

Proszę spojrzeć na StringReader#read().

Sprawdź kod źródłowy metody StringReader#read(). Wywołuje metodę ensureOpen(), która faktycznie rzuca IOException, ponieważ sprawdza, czy strumień nie został zamknięty.

Jeśli czytnik zostanie zamknięty, a następnie po read() zostanie ponownie wywołana, co się stanie?

kod źródłowy bezpośrednio z linku powyżej (spojrzeć na komentarze):

/** 
* Reads a single character. 
* 
* @return  The character read, or -1 if the end of the stream has been 
*    reached 
* 
* @exception IOException If an I/O error occurs 
*/ 
public int read() throws IOException { 
    synchronized (lock) { 
     ensureOpen(); 
     if (next >= length) 
      return -1; 
     return str.charAt(next++); 
    } 
} 

/** Check to make sure that the stream has not been closed */ 
private void ensureOpen() throws IOException { 
    if (str == null) 
     throw new IOException("Stream closed"); 
} 

/** 
* Closes the stream and releases any system resources associated with 
* it. Once the stream has been closed, further read(), 
* ready(), mark(), or reset() invocations will throw an IOException. 
* Closing a previously closed stream has no effect. 
*/ 
public void close() { 
    str = null; 
} 
+1

Zrobiłem to przed zadawaniem pytania :). Dowiedziałem się, że upewniam się, czy ciąg znaków wejściowych nie jest pusty, co NIE MOŻE SIĘ STAĆ. –

+0

Ale dla bezpieczniejszej strony, ekspert programowania Java umieścił to sprawdzenie. – Braj

+0

Jeśli czytnik jest "zamknięty", to po ponownym wywołaniu 'read', co się stanie? – Braj

3

Uważam, że nie jest dobrą praktyką rzucać takie same wyjątki, jak definicja super klasy lub interfejsu, jeśli twoja implementacja zapewnia, że ​​nigdy się nie wydarzy. Zawsze zredukuję podpis do wymaganego minimum.

Wymagana jest każda z możliwych implementacji, w tym źródeł plików, strumieni i gniazd itp. Bez tego takie implementacje nie mogą zgłosić swoich błędów jako sprawdzonego wyjątku. Ale jeśli implementacja nie musi wyrzucać sprawdzanego wyjątku (co często jest denerwujące dla kodu wywołującego), usunięcie go z klasy implementacyjnej nie szkodzi, ale usuwa pewne obciążenia.

UPDATE:

znalazłem powód dlaczego metoda read() musi rzucić IOException: ponieważ zamówienia określony dla metody close(). Z JavaDoc:

Zamyka strumień i zwalnia wszelkie zasoby systemowe z nim powiązane. Gdy strumień zostanie zamknięty, dalsze wywołania read(), ready(), mark() lub reset() wywołają wyjątek IOException. Zamknięcie poprzednio zamkniętego strumienia nie daje żadnego efektu.

+0

haah, doskonały świat sprawdzonego wyjątku :-) – zencv

+0

@Harmlezz Niestety nie możesz skopiować mojego pomysłu. – Braj

+0

Nie możesz tego zrobić tutaj. – Braj

1

Kiedy wdrożyć metodę interfejsu w klasie nie jest wymagane, aby zapewnić te same argumenty wyjątków. Ten sam przypadek ma zastosowanie, gdy zastąpisz deklarację metody z super klasy.

public class MyReader implements Readable { 

    @Override 
    public int read(CharBuffer cb) { 
     return 0; 
    } 

} 

Ale wtedy nie używasz interfejsu we właściwy sposób. Nie korzystasz z tego w przypadku, gdy kodujesz interfejs.

Readable readable = new MyReader(); 

     try { 
      readable.read(null); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

Nawet w MyReader nie wystawiać IOException nadal trzeba używać bloku try. Jeśli więc nie wyrzucisz wyjątku od metody, którą zastosujesz, możesz wskazać, że przegapiłeś coś w swojej implementacji tej metody. Tak więc, IMHO nie jest dobrą praktyką.

Powodem, dla którego StringBuilder rzuca IOException nie jest to, że implementuje interfejs Readable. Powodem tego jest sprawdzanie poprawności danych wejściowych w metodzie ensureOpen(), które wyrzucają IOException, gdy wejście ma wartość null. Następnie dane wejściowe mogą mieć wartość null, gdy wywoływana jest metoda close() lub przekazujesz wartość null do konstruktora. Ponieważ metoda close jest abstrakcyjna, musi mieć pewien wpływ na klasę potomną. Oczekiwane jest, że po wywołaniu close nie można już z niego czytać, a otrzymasz wyjątek IOException.

To jest idealna, czysta i solidna implementacja uwzględniająca wszystkie potencjalne przypadki użycia.