Dlaczego przesłanianie metod nie może generować wyjątków szerszych niż metoda przesłonięta?


108

Przeglądałem książkę SCJP 6 autorstwa Kathe sierra i natknąłem się na wyjaśnienia dotyczące rzucania wyjątków w metodzie nadpisanej. Zupełnie tego nie rozumiem. Czy ktoś może mi to wyjaśnić?

Metoda przesłaniająca NIE może generować sprawdzonych wyjątków, które są nowe lub szersze niż te zadeklarowane przez zastąpioną metodę. Na przykład metoda, która deklaruje FileNotFoundException, nie może zostać zastąpiona przez metodę, która deklaruje SQLException, Exception ani inny wyjątek inny niż czas wykonywania, chyba że jest to podklasa FileNotFoundException.


1
oto witryna, która może okazać się pomocna: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Odpowiedzi:


161

Oznacza to, że jeśli metoda deklaruje zgłoszenie danego wyjątku, metoda przesłaniająca w podklasie może zadeklarować tylko zgłoszenie tego wyjątku lub jego podklasy. Na przykład:

class A {
   public void foo() throws IOException {..}
}

class B extends A {
   @Override
   public void foo() throws SocketException {..} // allowed

   @Override
   public void foo() throws SQLException {..} // NOT allowed
}

SocketException extends IOException, ale SQLExceptionnie.

Dzieje się tak z powodu polimorfizmu:

A a = new B();
try {
    a.foo();
} catch (IOException ex) {
    // forced to catch this by the compiler
}

Gdyby Bzdecydował się rzucić SQLException, to kompilator nie mógłby zmusić cię do przechwycenia go, ponieważ odnosisz się do wystąpienia Bprzez jego nadklasę - A. Z drugiej strony każda podklasa IOExceptionbędzie obsługiwana przez klauzule (catch lub throws), które obsługująIOException

Zasada, według której musisz mieć możliwość odwoływania się do obiektów za pomocą ich nadklasy, to Zasada Zastępowania Liskova.

Ponieważ niezaznaczone wyjątki mogą być rzucane w dowolnym miejscu, nie podlegają tej regule. Jeśli chcesz, możesz dodać niezaznaczony wyjątek do klauzuli throws jako formę dokumentacji, ale kompilator niczego nie wymusza.


Dotyczy to również implementacji interfejsów? Nie jestem pewien, czy implementowanie interfejsu nadal nazywa się „przesłonięciem”.
Muhammad Gelbana

A co z @Override public void foo () {..} Wiem, że jest to dozwolone, ale wyjaśnienie w tym przypadku nie jest jasne.
nascar

4
@danip Metoda przesłaniająca może zgłosić dowolny podzbiór wyjątków zgłoszonych z metody przesłaniającej. Pusty zbiór również jest podzbiorem. Dlatego @Override public void foo() {...}jest legalne.
Deweloper Marius Žilėnas

@Bozho nie powinno tak być, jeśli metoda deklaruje zgłoszenie danego wyjątku, metoda przesłaniająca w podklasie może jedynie zadeklarować wyrzucenie tego wyjątku lub jego podklasy lub zadeklarować klauzulę NO throws w ogóle
Raman Sahasi,

Jak więc można pokonać w prawdziwym świecie? Muszę zastąpić metodę z zaimplementowanego interfejsu, ale moja implementacja obejmuje zwalnianie rzutów, a interfejs nie. Jaka jest tutaj standardowa procedura?
AP

22

Metoda przesłaniająca CAN może zgłosić każdy niesprawdzony (runtime) wyjątek, niezależnie od tego, czy nadpisana metoda deklaruje wyjątek

Przykład:

class Super {
    public void test() {
        System.out.println("Super.test()");
    }
}

class Sub extends Super {
    @Override
    public void test() throws IndexOutOfBoundsException {
        // Method can throw any Unchecked Exception
        System.out.println("Sub.test()");
    }
}

class Sub2 extends Sub {
    @Override
    public void test() throws ArrayIndexOutOfBoundsException {
        // Any Unchecked Exception
        System.out.println("Sub2.test()");
    }
}

class Sub3 extends Sub2 {
    @Override
    public void test() {
        // Any Unchecked Exception or no exception
        System.out.println("Sub3.test()");
    }
}

class Sub4 extends Sub2 {
    @Override
    public void test() throws AssertionError {
        // Unchecked Exception IS-A RuntimeException or IS-A Error
        System.out.println("Sub4.test()");
    }
}

Jak wymusić błąd lub ostrzeżenie, gdy interfejs nie deklaruje wyjątku czasu wykonywania, który robi podklasa? Próbuję wymusić spójność dla celów dokumentacyjnych. Łatwiej jest sprawdzić typ interfejsu dla wszystkich wyjątków, zaznaczonych i niezaznaczonych, zamiast znaleźć typ interfejsu, a następnie zagłębić się w implementację, aby zobaczyć, czy zgłasza IOException, czy IllegalArgumentException.
anon58192932

14

Moim zdaniem jest to błąd w projektowaniu składni Javy. Polimorfizm nie powinien ograniczać użycia obsługi wyjątków. W rzeczywistości inne języki komputerowe tego nie robią (C #).

Ponadto metoda jest nadpisywana w bardziej wyspecjalizowanej podklasie, dzięki czemu jest bardziej złożona iz tego powodu bardziej prawdopodobne jest rzucanie nowych wyjątków.


8

Podaję tutaj tę odpowiedź na stare pytanie, ponieważ żadne odpowiedzi nie mówią o tym, że metoda nadrzędna nie może niczego rzucać, oto ponownie to, co może rzucić metoda nadpisująca:

1) rzuć ten sam wyjątek

public static class A 
{
    public void m1()
       throws IOException
    {
        System.out.println("A m1");
    }

}

public static class B 
    extends A
{
    @Override
    public void m1()
        throws IOException
    {
        System.out.println("B m1");
    }
}

2) throw podklasę zgłoszonego wyjątku metody overriden

public static class A 
{
    public void m2()
       throws Exception
    {
        System.out.println("A m2");
    }

}

public static class B 
    extends A
{
    @Override
    public void m2()
        throws IOException
    {
        System.out.println("B m2");
    }
}

3) nic nie rzucać.

public static class A 
{   
    public void m3()
       throws IOException
    {
        System.out.println("A m3");
    }
}

public static class B 
    extends A
{   
    @Override
    public void m3()
        //throws NOTHING
    {
        System.out.println("B m3");
    }
}

4) Uwzględnianie wyjątków RuntimeExceptions w rzutach nie jest wymagane.

W rzutach mogą występować wyjątki RuntimeExceptions lub nie, kompilator nie będzie na to narzekał. RuntimeExceptions nie są sprawdzanymi wyjątkami. Tylko zaznaczone wyjątki muszą pojawiać się w rzutach, jeśli nie zostaną złapane.


6

Aby to zilustrować, rozważ:

public interface FileOperation {
  void perform(File file) throws FileNotFoundException;
}

public class OpenOnly implements FileOperation {
  void perform(File file) throws FileNotFoundException {
    FileReader r = new FileReader(file);
  }
}

Przypuśćmy, że napiszesz:

public class OpenClose implements FileOperation {
  void perform(File file) throws FileNotFoundException {
    FileReader r = new FileReader(file);
    r.close();
  }
}

Spowoduje to błąd kompilacji, ponieważ r.close () zgłasza wyjątek IOException, który jest szerszy niż wyjątek FileNotFoundException.

Aby to naprawić, jeśli napiszesz:

public class OpenClose implements FileOperation {
  void perform(File file) throws IOException {
    FileReader r = new FileReader(file);
    r.close();
  }
}

Otrzymasz inny błąd kompilacji, ponieważ wykonujesz operację perform (...), ale zgłosisz wyjątek nieuwzględniony w definicji metody interfejsu.

Dlaczego to jest ważne? Cóż, konsument interfejsu może mieć:

FileOperation op = ...;
try {
  op.perform(file);
}
catch (FileNotFoundException x) {
  log(...);
}

Jeśli zezwolono na zgłoszenie wyjątku IOException, kod klienta nie jest już poprawny.

Pamiętaj, że możesz uniknąć tego rodzaju problemów, jeśli używasz niezaznaczonych wyjątków. (Nie sugeruję, żebyś zrobił lub nie, to kwestia filozoficzna)


4

Zróbmy wywiad Pytanie. Istnieje metoda, która zgłasza wyjątek NullPointerException w nadklasie. Czy możemy to zastąpić metodą, która zgłasza RuntimeException?

Aby odpowiedzieć na to pytanie, daj nam znać, co to jest wyjątek Niezaznaczony i Zaznaczony.

  1. Zaznaczone wyjątki muszą być jawnie przechwytywane lub propagowane zgodnie z opisem w podstawowej obsłudze wyjątków try-catch-final. Niezaznaczone wyjątki nie mają tego wymogu. Nie trzeba ich łapać ani ogłaszać, że zostaną rzucone.

  2. Zaznaczone wyjątki w Javie rozszerzają klasę java.lang.Exception. Niezaznaczone wyjątki rozszerzają wyjątek java.lang.RuntimeException.

klasa publiczna NullPointerException rozszerza RuntimeException

Niezaznaczone wyjątki rozszerzają wyjątek java.lang.RuntimeException. Właśnie dlatego NullPointerException jest nie sprawdzanym wyjątkiem.

Weźmy przykład: Przykład 1:

    public class Parent {
       public void name()  throws NullPointerException {
           System.out.println(" this is parent");
       }
}

public class Child  extends Parent{
     public  void name() throws RuntimeException{
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();
        parent.name();// output => child
    }
}

Program zostanie pomyślnie skompilowany. Przykład 2:

    public class Parent {
       public void name()  throws RuntimeException {
           System.out.println(" this is parent");
       }
}

public class Child  extends Parent{
     public  void name() throws  NullPointerException {
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();
        parent.name();// output => child
    }
}

Program również pomyślnie się skompiluje. Dlatego jest oczywiste, że w przypadku wyjątków niezaznaczonych nic się nie dzieje. Przyjrzyjmy się teraz, co dzieje się w przypadku wyjątków sprawdzonych. Przykład 3: gdy klasa bazowa i klasa podrzędna jednocześnie zgłaszają sprawdzony wyjątek

    public class Parent {
       public void name()  throws IOException {
           System.out.println(" this is parent");
       }
}
public class Child  extends Parent{
     public  void name() throws IOException{
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();

        try {
            parent.name();// output=> child
        }catch( Exception e) {
            System.out.println(e);
        }

    }
}

Program zostanie pomyślnie skompilowany. Przykład 4: Gdy metoda klasy podrzędnej zgłasza wyjątek sprawdzony na obramowaniu w porównaniu z tą samą metodą klasy bazowej.

import java.io.IOException;

public class Parent {
       public void name()  throws IOException {
           System.out.println(" this is parent");
       }
}
public class Child  extends Parent{
     public  void name() throws Exception{ // broader exception
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();

        try {
            parent.name();//output=> Compilation failure
        }catch( Exception e) {
            System.out.println(e);
        }

    }
}

Program się nie skompiluje. Dlatego musimy być ostrożni, gdy używamy wyjątków zaznaczonych.


doskonała odpowiedź!
Gaurav

2

powiedzmy, że masz super klasę A z metodą M1 rzucającą E1 i klasą B pochodzącą z A z metodą M2 przesłaniającą M1. M2 nie może rzucać niczym INNYM lub MNIEJ SPECJALISTYCZNYM niż E1.

Ze względu na polimorfizm, klient używający klasy A powinien być w stanie traktować B tak, jakby to było A. Inharitance ===> Is-a (B is-a A). Co by było, gdyby ten kod dotyczący klasy A obsługiwał wyjątek E1, ponieważ M1 deklaruje, że zgłasza ten sprawdzony wyjątek, ale następnie został zgłoszony wyjątek innego typu? Gdyby M1 wyrzucał IOException, M2 mógłby równie dobrze zgłosić wyjątek FileNotFoundException, ponieważ jest to wyjątek IOException. Klienci A poradziliby sobie z tym bez problemu. Gdyby zgłoszony wyjątek był szerszy, klienci A nie mieliby szansy się o tym dowiedzieć, a zatem nie mieliby szansy go złapać.


Perhac :: czy to prawda dla wyjątków zaznaczonych i odznaczonych? czy to się różni?
ylnsagar

@ylnsagar to jest tylko dla zaznaczonych wyjątków. Niezaznaczone wyjątki (podtypy RuntimeException) mogą być również nazywane „błędami programisty” i co do zasady nie powinny być wyłapywane, dlatego nie muszą być deklarowane w klauzuli throws. Niezaznaczony wyjątek może się tak zdarzyć w dowolnym momencie z dowolnego kodu. Powyższa dyskusja dotyczy tylko zaznaczonych wyjątków
Peter Perháč

@Perhac :: tak, masz rację, ale z mojego zrozumienia czytając artykuły. Dotyczy to również niezaznaczonych wyjątków. na przykład, jeśli metoda superklasy zgłasza wyjątek wskaźnika zerowego, a podklasa przesłaniająca metodę zgłasza wyjątek. tutaj Wyjątek to super klasa Wyjątku zerowego wskaźnika. Wtedy kompilator by na to nie pozwolił.
ylnsagar

1

Cóż, java.lang.Exception rozszerza java.lang.Throwable. java.io.FileNotFoundException rozszerza java.lang.Exception. Jeśli więc metoda zgłasza wyjątek java.io.FileNotFoundException, to w metodzie override nie można wrzucić niczego wyżej w hierarchii niż FileNotFoundException, np. Nie można wrzucić wyjątku java.lang.Exception. Możesz jednak zgłosić podklasę FileNotFoundException. Jednak konieczne byłoby obsłużenie wyjątku FileNotFoundException w metodzie nadpisanej. Podrzuć trochę kodu i spróbuj!

Reguły istnieją, aby nie utracić pierwotnej deklaracji rzutów poprzez rozszerzenie specyficzności, ponieważ polimorfizm oznacza, że ​​można wywołać metodę nadpisaną w nadklasie.


1

Metoda przesłaniająca NIE może generować sprawdzonych wyjątków, które są nowe lub szersze niż te zadeklarowane przez zastąpioną metodę.

Przykład:

class Super {
    public void throwCheckedExceptionMethod() throws IOException {
        FileReader r = new FileReader(new File("aFile.txt"));
        r.close();
    }
}

class Sub extends Super {    
    @Override
    public void throwCheckedExceptionMethod() throws FileNotFoundException {
        // FileNotFoundException extends IOException
        FileReader r = new FileReader(new File("afile.txt"));
        try {
            // close() method throws IOException (that is unhandled)
            r.close();
        } catch (IOException e) {
        }
    }
}

class Sub2 extends Sub {
    @Override
    public void throwCheckedExceptionMethod() {
        // Overriding method can throw no exception
    }
}

1

Metoda przesłaniająca NIE może generować sprawdzonych wyjątków, które są nowe lub szersze niż te zadeklarowane przez zastąpioną metodę.

Oznacza to po prostu, że gdy zastępujesz istniejącą metodę, wyjątek, który zgłasza ta przeciążona metoda, powinien być tym samym wyjątkiem, który zgłasza oryginalna metoda, albo dowolną z jej podklas .

Zwróć uwagę, że sprawdzanie, czy wszystkie zaznaczone wyjątki są obsługiwane, odbywa się w czasie kompilacji, a nie w czasie wykonywania. Tak więc w czasie kompilacji kompilator języka Java sprawdza typ wyjątku zgłaszanego przez zastąpioną metodę. Ponieważ nadpisana metoda zostanie wykonana, można zdecydować tylko w czasie wykonywania, nie możemy wiedzieć, jaki rodzaj wyjątku musimy złapać.


Przykład

Powiedzmy, że mamy klasę Ai jej podklasę B. Ama metodę, m1a klasa Bprzesłała tę metodę (nazwijmy ją, m2aby uniknąć nieporozumień ...). Powiedzmy teraz, że m1rzuca E1i m2rzuca E2, co jest E1klasą nadrzędną. Teraz piszemy następujący fragment kodu:

A myAObj = new B();
myAObj.m1();

Zauważ, że m1jest to nic innego jak wywołanie m2(ponownie, sygnatury metod są takie same w przeciążonych metodach, więc nie myl ich z m1i m2... w tym przykładzie służą one tylko do rozróżnienia ... obie mają ten sam podpis). Ale w czasie kompilacji wszystko, co robi kompilator java, to przechodzi do typu referencyjnego ( Aw tym przypadku Class ) sprawdza metodę, jeśli jest obecna i oczekuje, że programista ją obsłuży. Więc oczywiście rzucisz lub złapiesz E1. Teraz, w czasie wykonywania, jeśli przeciążona metoda rzuca E2, która jest E1nadklasą, to ... cóż, jest to bardzo złe (z tego samego powodu, którego nie możemy powiedzieć B myBObj = new A()). Dlatego Java na to nie pozwala. Niezaznaczone wyjątki generowane przez przeciążoną metodę muszą być takie same, podklasy lub nieistniejące.


class Parent {void method () rzuca IndexOutOfBoundsException {System.out.println ("Metoda nadrzędna"); }} class Child extends Parent {void method () throws RuntimeException {System.out.println ("Metoda potomna"); } Jeśli klasa nadrzędna wyrzuca element potomny wyjątku czasu wykonania, a element potomny zgłasza sam wyjątek środowiska wykonawczego. Czy to jest ważne?
abhiagNitk

1

Aby to zrozumieć, rozważmy przykład, w którym mamy klasę, Mammalktóra definiuje readAndGetmetodę, która odczytuje jakiś plik, wykonuje na nim jakąś operację i zwraca instancję klasy Mammal.

class Mammal {
    public Mammal readAndGet() throws IOException {//read file and return Mammal`s object}
}

Klasa Humanrozszerza klasę Mammali przesłania readAndGetmetodę, aby zwrócić wystąpienie Humanzamiast wystąpienia Mammal.

class Human extends Mammal {
    @Override
    public Human readAndGet() throws FileNotFoundException {//read file and return Human object}
}

Aby zadzwonić readAndGet, będziemy musieli obsłużyć, IOExceptionponieważ jest to sprawdzony wyjątek i readAndMethodrzuca go ssak .

Mammal mammal = new Human();
try {
    Mammal obj = mammal.readAndGet();
} catch (IOException ex) {..}

Wiemy, że kompilator mammal.readAndGet()jest wywoływany z obiektu klasy, Mammalale w czasie wykonywania JVM rozpozna mammal.readAndGet()wywołanie metody na wywołanie z klasy, Humanponieważ mammaljest wstrzymane new Human().

Metoda readAndMethodz Mammalrzuca IOExceptiona ponieważ jest to sprawdzone kompilator wyjątek zmusi nas złapać go, gdy wzywamy readAndGetnamammal

Teraz przypuśćmy, że readAndGetin Humanrzuca dowolny inny sprawdzony wyjątek, np. Exception i wiemy, że readAndGetzostanie wywołany z instancji, Humanponieważ mammaltrzyma new Human().

Ponieważ dla kompilatora metoda jest wywoływana z Mammal, więc kompilator zmusi nas do obsługi, IOExceptionale w czasie wykonywania wiemy, że metoda będzie rzucać Exceptionwyjątek, który nie jest obsługiwany, a nasz kod się zepsuje, jeśli metoda wyrzuci wyjątek.

Dlatego jest to zabronione na poziomie samego kompilatora i nie możemy zgłosić żadnego nowego lub szerszego sprawdzonego wyjątku, ponieważ na końcu nie będzie on obsługiwany przez JVM.

Istnieją również inne zasady, których musimy przestrzegać, zastępując metody, i możesz przeczytać więcej na temat Dlaczego powinniśmy przestrzegać reguł zastępujących metody, aby poznać przyczyny.


0

Jakie wyjaśnienie przypisujemy poniższemu

class BaseClass {

    public  void print() {
        System.out.println("In Parent Class , Print Method");
    }

    public static void display() {
        System.out.println("In Parent Class, Display Method");
    }

}


class DerivedClass extends BaseClass {

    public  void print() throws Exception {
        System.out.println("In Derived Class, Print Method");
    }

    public static void display() {
        System.out.println("In Derived Class, Display Method");
    }
}

Klasa DerivedClass.java zgłasza wyjątek w czasie kompilacji, gdy metoda print zgłasza wyjątek, metoda print () klasy bazowej nie zgłasza żadnego wyjątku

Jestem w stanie przypisać to faktowi, że wyjątek jest węższy niż wyjątek RuntimeException, może to być brak wyjątku (błąd czasu wykonania), wyjątek RuntimeException i ich wyjątki podrzędne


0

Metoda przesłaniająca podklasy może zgłosić tylko wiele sprawdzonych wyjątków, które są podklasami sprawdzonego wyjątku metody nadklasy, ale nie może zgłosić wielu sprawdzonych wyjątków, które nie są związane z zaznaczonym wyjątkiem metody nadklasy


0

Java daje ci możliwość ograniczenia wyjątków w klasie nadrzędnej, ponieważ zakłada, że klient ograniczy to, co zostanie przechwycone . IMHO zasadniczo nigdy nie powinieneś używać tej „funkcji”, ponieważ Twoi klienci mogą potrzebować elastyczności w przyszłości.

Java to stary język, który jest źle zaprojektowany. Współczesne języki nie mają takich ograniczeń. Najłatwiejszym sposobem obejścia tej luki jest ustawienie klasy bazowej throw Exceptionzawsze. Klienci mogą rzucać bardziej szczegółowe wyjątki, ale sprawiają, że twoje klasy podstawowe są naprawdę szerokie.


0

Reguła obsługi sprawdzania i niezaznaczonych wyjątków w metodach przesłoniętych

- Gdy metoda klasy nadrzędnej nie deklaruje wyjątku, wówczas metoda przesłaniająca klasy potomnej może zadeklarować ,

 1. No exception or
 2. Any number of unchecked exception
 3. but strictly no checked exception

-Gdy metoda klasy nadrzędnej deklaruje niezaznaczony wyjątek, metoda przesłaniająca klasy potomnej może zadeklarować ,

 1. No exception or
 2. Any number of unchecked exception 
 3. but strictly no checked exception

- Gdy metoda klasy nadrzędnej deklaruje sprawdzony wyjątek, wówczas metoda przesłaniająca klasy potomnej może zadeklarować ,

 1. No exception or
 2. Same checked exception or
 3. Sub-type of checked exception or
 4. any number of unchecked exception

Wszystkie powyższe wnioski są prawdziwe, nawet jeśli kombinacja zarówno zaznaczonego, jak i niezaznaczonego wyjątku jest zadeklarowana w metodzie klasy nadrzędnej

Nr ref

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.