Zagadka Java #5

Zagadki Java zawierają różnego rodzaju niuanse języka i „podchwytliwe” fragmenty kodu. Każda następna zagadka będzie zawierała odpowiedź i wyjaśnienie do poprzedniej. Lista wszystkich zagadek.

Jeżeli znasz odpowiedź, podziel się nią w komentarzu!

Zagadka Java #5

Jaki, oraz dlaczego, będzie wynik kompilacji i uruchomienia klasy MyExceptionTest?

class MyException extends Exception {}

public class MyExceptionTest {
  public static void m() throws MyException {}
  public static void m2() {}
  public static void m3() {}

  public static void main(String[] args) {
    try {
      m();
    } catch (MyException e) {}

    try {
      m2();
    } catch (MyException e) {}

    try {
      m3();
    } catch (Exception e) {}
  }
}

Odpowiedź do poprzedniej zagadki #4

Poniżej znajdziesz odpowiedź do poprzedniej zagadki.

Uruchomienie zagadki #4 spowoduje wyświetlenie na ekranie dwa razy liczby 5000, a także treści wyjątku UnsupportedOperationException:

5000 5000 Exception in thread "main" java.lang.UnsupportedOperationException at java.base/java.util.AbstractList.add(AbstractList.java:153) at java.base/java.util.AbstractList.add(AbstractList.java:111) at zagadki.zagadka4.TabToList.main(TabToList.java:15)

Dlaczego zmiana pierwszego elementu listy spowodowała, że pierwszy element tablicy także się zmienił? I dlaczego próba dodania nowego elementu do listy kończy się rzuceniem wyjątku?

Okazuje się, że metoda toList klasy Arrays tworzy nową listę, która bazuje na tablicy otrzymanej jako argument. Elementy tej tablicy nie są kopiowane do nowej tablicy – wykorzystywana jest oryginalna tablica. Skutkuje to tym, że wszelkie zmiany w tablicy bądź na liście powodują zmianę tego samego obiektu (tej samej tablicy) w pamięci.

Typ listy, jaka zwracana jest z metody Arrays.toList, to ArrayList. Nie jest to jednak klasa znana z pakietu java.util, lecz prywatna, zagnieżdżona klasa, zdefiniowana w klasie Arrays. Ta klasa ArrayList implementuje tylko część z metod z abstrakcyjnej klasy AbstractList, którą rozszerza. Nie ma wśród nich metody add, która pozwalałaby na dodawanie do listy nowych elementów, więc wywołując metodę add na liście zwróconej przez metodę Arrays.toList, wywoływana jest wersja tej metody odziedziczona z klasy bazowej, a ta rzuca w efekcie wyjątek UnsupportedOperationException.

Komentarze (4):

  1. Klasa MyException" dziedziczy bezpośrednio po klasie "Exception", zatem jest typu Checked Potencjał rzutu wyjątku typu Checked przez metodę musi być zasygnalizowany słowem kluczowym "throws" w sygnaturze tej metody. Wówczas kompilator spodziewa się w metodzie main klasy MyExceptionTest bloku try...catch, który obsłuży wyjątek i tak dzieje się w pierwszym bloku try...catch kiedy wywołujemy metodę m(). Jednakże w drugim bloku try...catch pojawia się wywołanie metody m2(), która w swej sygnaturze w części deklaracyjnej nie posiada słowa kluczowego throws a zatem nie sygnalizuje rzucenia wyjątku typu checked, który próbujemy obsłużyć. Jest to błąd, który zostanie wychwycony przez kompilator już na etapie próby kompilacji tego kodu i zasygnalizowany komunikatem: "MyException is never thrown in body of corresponding try statement" i tego należało się spodziewać patrząc na powyższy kod programu.
    Natomiast nie do końca rozumiem, dlaczego podobny błąd się nie pojawia w przypadku obsługi wyjątku w trzecim bloku try...catch, kiedy wywołujemy metodę m3(). Intuicja podpowiada mi, że nie jest to metoda, która rzuca wyjątek typu Checked, gdyż obsługiwany wyjątek jest typu Exception, która jest bazową klasą wszystkich wyjątków i m3() nie musi w swej sygnaturze zawierać słowa throws. Ale dlaczego tak jest? Czy domyślnie każda metoda potencjalnie rzuca wyjątek typu Exception? Znowu proszę o komentarz i krytyczne uwagi. Z góry dziękuję i pozdrawiam.

    1. Pierwsza część odpowiedzi się zgadza. Co do powodu, dlaczego kompilator nie protestuje przy metodzie m3, to w powyższym opisie zawarł Pan wyjaśnienie, tylko nie zdaje Pan sobie z tego sprawy 🙂

        1. Odpowiedź na zagadkę będzie w przyszłą środę. Odpowiedzią na poprzedni komentarz chciałem naprowadzić na rozwiązanie 😉 W przypadku metody m3 łapany jest wyjątek typu Exception, który jest bazową klasą dla wyjątków checked i runtime. Potencjał rzucenia wyjątku typu runtime przez metodę m3 nie musi być deklarowany, ale może być obsłużony przez try z klauzulą catch definiującą Exception jako łapany wyjątek - dlatego kod w tym przypadku się kompiluje. W przypadku metody m2 kompilator zgłasza błąd, bo łapanie wyjątku własnego typu nie daje takiej możliwości.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Nie musisz podawać swojego imienia, e-mailu, ani strony www, aby opublikować komentarz. Komentarze muszą zostać zatwierdzone, aby były widoczne na stronie.