Чому не можна переосмислити методи викидів винятків ширше, ніж метод, який перекрито?


104

Я переглядав книгу SCJP 6 від Кетрі Сієрри і натрапив на це пояснення кидання винятків у перекритому способі. Я цілком цього не зрозумів. Чи може хтось мені це пояснити?

Метод переосмислення НЕ повинен кидати перевірені винятки, які є новими або ширшими, ніж ті, які оголошені методом переопределення. Наприклад, метод, який оголошує FileNotFoundException, не може бути замінений методом, який оголошує SQLException, Exception або будь-який інший виняток, що не виконується, якщо це не підклас FileNotFoundException.


1
ось сайт, який може вам стати корисним: javapractices.com/topic/TopicAction.do?Id=129
Тім Біш

Відповіді:


155

Це означає, що якщо метод оголошує кинути даний виняток, метод переосмислення в підкласі може оголосити лише викинути цей виняток або його підклас. Наприклад:

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, але SQLExceptionне робить.

Це через поліморфізм:

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

Якщо ви Bвирішили кинути SQLException, тоді компілятор не міг змусити вас зловити це, тому що ви посилаєтесь на екземпляр Bйого суперкласу - A. З іншого боку, будь-який підклас IOExceptionбуде оброблятися пунктами (улов або кидки), які обробляютьIOException

Правило про те, що вам потрібно вміти посилатися на предмети за їх суперкласом - це принцип заміни Ліскова.

Оскільки неперевірені винятки можуть бути кинуті куди завгодно, це правило не поширюється Ви можете додати неперевірений виняток до пункту кидків як форми документації, якщо хочете, але компілятор нічого про це не виконує.


Це також стосується впровадження інтерфейсів? Я не впевнений, чи реалізація інтерфейсу все ще називається "переосмислення".
Мухаммед Гельбана

Як щодо @Override public void foo () {..} Я знаю, що це дозволено, але пояснення не ясно для цього випадку.
наскар

4
@danip метод переосмислення може викинути будь-яку підмножину винятків, викинутих із методу overriden. Порожній набір теж є підмножиною. Ось чому @Override public void foo() {...}законно.
розробник Marius Žilėnas

@Bozho не повинно бути, якщо метод оголошує кинути заданий виняток, метод, що
Раман Сахасі

То як же подолати в реальному світі? Мені потрібно змінити метод із реалізованого інтерфейсу, але моя реалізація включає уповільнення кидків, а інтерфейс - ні. Яка тут стандартна процедура?
ар

22

Метод переосмислення МОЖЕ викинути будь-який неперевірений (час виконання) виняток, незалежно від того, чи метод переопределення оголошує виняток

Приклад:

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()");
    }
}

Як ви викликаєте помилку чи попередження, коли ваш інтерфейс не оголошує виняток із виконання часу, який робить підклас? Я намагаюся примусити узгодженість для цілей документації. Простіше перевірити тип інтерфейсу на всі винятки, перевірити чи не перевірити, а не знайти тип інтерфейсу, а потім заглибитися у реалізацію, щоб побачити, чи він кидає IOException чи IllegalArgumentException.
anon58192932

14

На мою думку, це невдача в дизайні синтаксису Java. Поліморфізм не повинен обмежувати обробку винятків. Насправді інші комп'ютерні мови цього не роблять (C #).

Більше того, метод переосмислюється в підкласі більш спеціалізованого типу, щоб він був складнішим і, з цієї причини, більш імовірним викиданням нових винятків.


8

Я даю цю відповідь тут на старе запитання, оскільки жодна відповідь не говорить про те, що метод, що переосмислює, нічого не може кинути ось ось що може перекинути метод переосмислення:

1) кинути той самий виняток

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) кинути підклас викинутого виняткового методу

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) нічого не кидати.

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) Виконання RuntimeExceptions в кидках не потрібно.

Можуть бути RuntimeExceptions в кидках чи ні, компілятор не буде скаржитися на це. Винятки RuntimeExceptions не перевіряються. Лише перевірені винятки повинні відображатися в кидках, якщо їх не ловлять.


6

Щоб проілюструвати це, врахуйте:

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);
  }
}

Припустимо, ви напишете:

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

Це дасть вам помилку компіляції, оскільки r.close () викидає IOException, який ширший, ніж FileNotFoundException.

Щоб виправити це, якщо ви пишете:

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

Ви отримаєте іншу помилку компіляції, оскільки ви реалізуєте операцію виконувати (...), але кидаєте виняток, не включений у визначення методу інтерфейсу.

Чому це важливо? Ну а споживач інтерфейсу може мати:

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

Якщо IOException було дозволено кинути, код клієнта не є правильним.

Зауважте, що ви можете уникнути подібних проблем, якщо використовуєте неперевірені винятки. (Я не пропоную вам робити чи ні, це філософське питання)


3

Візьмемо питання інтерв'ю. Існує метод, який кидає NullPointerException у надклас. Чи можемо ми перемогти це методом, який кидає RuntimeException?

Щоб відповісти на це запитання, дайте нам знати, що таке неперевірений і перевірений виняток.

  1. Перевірені винятки повинні бути чітко викладені або розповсюджені, як описано в Basic Handling-виключно обробці винятків. Неперевірені винятки не мають цієї вимоги. Їх не потрібно ловити чи оголошувати кинутими.

  2. Перевірені винятки в Java поширюють клас java.lang.Exception. Неперевірені винятки поширюють java.lang.RuntimeException.

публічний клас NullPointerException розширює RuntimeException

Неперевірені винятки поширюють java.lang.RuntimeException. Тому, чому NullPointerException є виключеним без виключення.

Візьмемо приклад: Приклад 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
    }
}

Програма буде складено успішно. Приклад 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
    }
}

Програма також буде успішно складена. Тому очевидно, що нічого не відбувається у разі неперевірених винятків. Тепер давайте подивимося, що відбувається у випадку перевірених винятків. Приклад 3: Коли базовий клас і дочірній клас обидва кидають перевірений виняток

    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);
        }

    }
}

Програма буде складено успішно. Приклад 4: Коли метод дочірнього класу перекидає виняток, що перевіряється межею, порівняно з тим же методом базового класу

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);
        }

    }
}

Програма не вдасться компілювати. Отже, ми повинні бути обережними, коли використовуємо перевірені винятки.


2

скажімо, що у вас є клас супер A з методом M1 кидок E1 і клас B, що походить від A з методом M2, що перекриває M1. M2 не може кинути нічого РІЗНОГО або МЕНШЕ СПЕЦІАЛІЗОВАНО, ніж E1.

Через поліморфізм клієнт, що використовує клас A, повинен мати можливість ставитися до B так, як якщо б це було A. Inharitance ===> Is-a (B is-a A). Що робити, якщо цей код, що стосується класу A, обробляв виняток E1, оскільки M1 заявляє, що він кидає цей перевірений виняток, але потім був викинутий інший тип виключення? Якщо M1 кидав IOException, M2 цілком може кинути FileNotFoundException, як це - IOException. Клієнти компанії A могли б впоратися з цим без проблем. Якби викинутий виняток був ширшим, клієнти A не мали б шансів дізнатися про це, і тому не мали б шансів його зловити.


Perhac :: це правда як для перевірених, так і для знятих прапорців виключень? чи це змінюється?
ілнсагар

@ylnsagar це лише для перевірених винятків. Неперевірені винятки (підтипи RuntimeException) також можуть називатися «помилками програміста», і, як правило, не слід намагатися переслідувати, отже, не потрібно оголошувати у пункті про кидки. Неперевірений виняток може так статися, будь-коли, з будь-якого коду. Вищевказана дискусія стосується лише перевірених винятків
Петро Пергач

@Perhac :: так, ти маєш рацію, але з мого розуміння, читаючи статті. Це також справедливо для неперевірених винятків. наприклад, якщо метод суперкласу кидає Null Pointer Exception і якщо підклас, що переосмислює метод, видаляє Exception. тут Виняток - це супер клас Null Pointer Exception. Тоді компілятор цього не дозволив.
ілнсагар

1

Ну java.lang.Exception поширює java.lang.Throwable. java.io.FileNotFoundException розширює java.lang.Exception. Отже, якщо метод кидає java.io.FileNotFoundException, тоді в методі переосмислення ви не можете кинути щось вище, ніж ієрархію, ніж FileNotFoundException, наприклад, ви не можете кинути java.lang.Exception. Ви можете кинути підклас FileNotFoundException, хоча. Однак ви будете змушені обробляти FileNotFoundException методом переопределення. Збийте якийсь код і спробуйте!

Правила існують, щоб ви не втратили оригінальну декларацію кидків, розширивши специфіку, оскільки поліморфізм означає, що ви можете посилатись на переохоплений метод надкласу.


1

Метод переосмислення НЕ повинен кидати перевірені винятки, які є новими або ширшими, ніж ті, які оголошені методом переопределення.

Приклад:

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

Метод переосмислення НЕ повинен кидати перевірені винятки, які є новими або ширшими, ніж ті, які оголошені методом переопределення.

Це просто означає, що ви переосмислюєте існуючий метод, виняток, що цей перевантажений метод має бути або тим самим винятком, який кидає оригінальний метод, або будь-який з його підкласів .

Зауважте, що перевірка оброблення всіх перевірених винятків проводиться під час компіляції, а не під час виконання. Отже, під час компіляції сам компілятор Java перевіряє тип винятку, який перекидає метод, що перекривається. Оскільки який переосмислений метод буде виконуватися, можна вирішити лише під час виконання, ми не можемо знати, який виняток нам належить спіймати.


Приклад

Скажімо, у нас є клас Aта його підклас B. Aмає метод m1і клас Bзамінив цей метод (давайте називати його, m2щоб уникнути плутанини ..). Тепер скажімо, m1кидає E1і m2кидає E2, що є E1суперкласом. Тепер ми пишемо наступний фрагмент коду:

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

Зауважте, що m1це не що інше, як заклик до m2(знову ж таки, підписи методів однакові у перевантажених методах, тому не плутайте їх m1і m2.. у цьому прикладі вони просто розмежуються ... вони обоє мають однакову підпис). Але під час компіляції все компілятор Java - це перейти до еталонного типу (Клас Aу цьому випадку) перевіряє метод, якщо він присутній, і очікує, що програміст це впорається. Так що очевидно, ви кинете або зловите E1. Тепер, під час виконання, якщо перевантажений метод кидає E2, що є E1суперкласом, то ... ну, це дуже неправильно (з тієї ж причини ми не можемо сказати B myBObj = new A()). Отже, Java цього не дозволяє. Неперевірені винятки, викинуті перевантаженим методом, повинні бути однаковими, підкласами або неіснуючими.


клас Parent {void method () кидає IndexOutOfBoundsException {System.out.println ("Метод батьків"); }} клас Child розширює Parent {void method () кидає RuntimeException {System.out.println ("Дочірній метод"); } Якщо батьківський клас кидає дитину винятку з виконання та дитина викидає сам виняток із виконання. Чи дійсно це?
abhiagNitk

1

Для розуміння цього розглянемо приклад, коли у нас є клас, Mammalякий визначає readAndGetметод, який читає якийсь файл, виконує деяку операцію над ним і повертає екземпляр класу Mammal.

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

Клас Humanрозширює метод класу Mammalта замінює, readAndGetщоб повернути екземпляр Humanзамість екземпляра Mammal.

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

Щоб зателефонувати, readAndGetнам потрібно буде впоратися, IOExceptionтому що це перевірене виняток, і ссавці readAndMethodкидають його.

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

І ми знаємо, що для компілятора mammal.readAndGet()викликається виклик з об'єкта класу, Mammalале в, час виконання JVM вирішить mammal.readAndGet()виклик методу на виклик з класу, Humanтому що mammalвін утримує new Human().

Метод readAndMethodвід Mammalкидає , IOExceptionі тому , що це перевіряється компілятор виключення змусить нас зловити його всякий раз , коли ми викликаємо readAndGetнаmammal

Тепер припустимо , що readAndGetв Humanкидає будь-який інший перевірив виняток , наприклад , виключення , і ми знаємо , readAndGetбуде викликаний з примірника , Humanтому що mammalтримає new Human().

Тому що для компілятора метод викликається Mammal, тому компілятор змусить нас обробляти тільки, IOExceptionале під час виконання ми знаємо, що метод буде викидати Exceptionвиняток, який не отримується, і наш код порушиться, якщо метод викине виняток.

Ось чому це заважає на самому рівні компілятора, і нам не дозволяється викидати будь-який новий або ширший перевірений виняток, оскільки він не буде оброблятися JVM в кінці.

Також існують інші правила, яких нам потрібно дотримуватися, переосмислюючи методи, і ви можете прочитати докладнішу інформацію про те, чому нам слід дотримуватися правил переосмислення методу, щоб знати причини.


0

Яке пояснення ми приписуємо нижче

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");
    }
}

Клас DerivedClass.java видає виняток із часу компіляції, коли метод друку видає виняток, метод print () baseclass не кидає жодного винятку

Я можу віднести це до того, що виняток вужчий, ніж RuntimeException, він може бути або Без винятку (помилка виконання), RuntimeException та їх винятковими винятками


0

Метод переопределення підкласу може викидати лише кілька перевірених винятків, які є підкласами перевірених винятків методу надкласу, але не можуть викидати кілька перевірених винятків, які не мають відношення до перевіреного винятку методу надкласу


0

Java надає вам вибір обмеження винятків у батьківському класі, оскільки передбачає, що клієнт обмежить те, що потрапило . ІМХО вам по суті ніколи не слід використовувати цю "функцію", оскільки вашим клієнтам може знадобитися гнучкість у дорозі.

Java - стара мова, яка погано розроблена. Сучасні мови не мають таких обмежень. Найпростіший спосіб подолати цей недолік - зробити базовий клас throw Exceptionзавжди. Клієнти можуть кидати більш конкретні винятки, але зробити базові класи дійсно широкими.


0

Правило поводження з перевірочними та неперевіреними винятками для перекритих методів

- Якщо метод батьківського класу оголошує не виняток, тоді метод переопределення дочірнього класу може оголосити ,

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

-Коли метод батьківського класу оголошує неперевірений виняток, тоді метод переопределення дочірнього класу може оголосити ,

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

- Коли метод батьківського класу оголошує перевірений виняток, тоді метод переопределення дочірнього класу може оголосити ,

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

Всі вищевикладені висновки справедливі, навіть якщо поєднання як перевірених, так і неперевірених винятків оголошено методом батьківського класу

Реф

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.