Почему переопределяющие методы не могут вызывать исключения шире, чем переопределенный метод?

107

Я просматривал книгу SCJP 6, написанную Кэте Сьерра, и наткнулся на эти объяснения создания исключений в переопределенном методе. Я совершенно не понял. Кто-нибудь может мне это объяснить?

Переопределяющий метод НЕ должен генерировать проверенные исключения, которые являются новыми или более широкими, чем те, которые были объявлены переопределенным методом. Например, метод, который объявляет FileNotFoundException, не может быть переопределен методом, который объявляет SQLException, Exception или любое другое исключение, не относящееся к времени выполнения, если оно не является подклассом FileNotFoundException.

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

Ответы:

160

Это означает, что если метод объявляет выброс данного исключения, метод переопределения в подклассе может только объявить выброс этого исключения или его подкласса. Например:

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будет обрабатываться с помощью предложений (catch или throws), которые обрабатываютIOException

Правило, которое вам нужно, чтобы иметь возможность ссылаться на объекты по их суперклассу, - это принцип подстановки Лискова.

Поскольку непроверенные исключения могут быть выброшены где угодно, они не подпадают под это правило. Вы можете добавить непроверенное исключение в предложение throws как форму документации, если хотите, но компилятор ничего не применяет к этому.

Божо
источник
Это также применимо при реализации интерфейсов? Я не уверен, что реализация интерфейса все еще называется «переопределением».
Мухаммад Гелбана
Как насчет @Override public void foo () {..} Я знаю, что это разрешено, но объяснение для этого случая неясно.
nascar
4
@danip метод переопределения может генерировать любое подмножество исключений, выброшенных из метода переопределения. Пустой набор тоже является подмножеством. Вот почему @Override public void foo() {...}это законно.
Разработчик Мариус Жиленас,
@Bozho не должно быть, если метод объявляет, что выбрасывает данное исключение, метод переопределения в подклассе может только объявить выброс этого исключения или его подкласса или объявить предложение NO throws вообще
Раман Сахаси
Так как же преодолеть в реальном мире? Мне нужно переопределить метод из реализованного интерфейса, но моя реализация включает замедление выброса, а интерфейс - нет. Какая здесь стандартная процедура?
ap
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()");
    }
}
syrus.phoenix
источник
Как вы вызываете ошибку или предупреждение, если ваш интерфейс не объявляет исключение времени выполнения, которое делает подкласс? Я пытаюсь добиться согласованности в документации. Проще проверить тип интерфейса на наличие всех исключений, отмеченных и не отмеченных, чем найти тип интерфейса, а затем углубиться в реализацию, чтобы увидеть, генерирует ли он исключение 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) throw подкласс выброшенного исключения переопределенного метода

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

Вы получите другую ошибку компиляции, потому что вы реализуете операцию perform (...), но генерируете исключение, не включенное в определение метода интерфейса.

Почему это важно? Ну, у потребителя интерфейса могут быть:

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

Если было разрешено генерировать исключение IOException, код клиента больше не является правильным.

Обратите внимание, что вы можете избежать такого рода проблем, если используете непроверенные исключения. (Я не предлагаю вам делать или не делать этого, это философский вопрос)

Дилум Ранатунга
источник
4

Возьмем вопрос для интервью. Есть метод, который выдает исключение NullPointerException в суперклассе. Можем ли мы переопределить его с помощью метода, который генерирует исключение RuntimeException?

Чтобы ответить на этот вопрос, дайте нам знать, что такое исключение Unchecked и Checked.

  1. Проверенные исключения должны быть явно перехвачены или распространены, как описано в разделе Базовая обработка исключений try-catch-finally. Для неотмеченных исключений это требование отсутствует. Их не нужно ловить или объявлять брошенными.

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

Программа также успешно скомпилируется. Поэтому очевидно, что в случае исключений Unchecked ничего не происходит. Теперь давайте посмотрим, что происходит в случае отмеченных исключений. Пример 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);
        }

    }
}

Программа не скомпилируется. Итак, мы должны быть осторожны, когда используем проверенные исключения.

Судипта Дутта
источник
отличный ответ!
Gaurav
2

скажем, у вас есть суперкласс A с методом M1 throwin 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
@ylnsagar это только для проверенных исключений. Непроверенные исключения (подтипы RuntimeException) также могут называться «ошибками программиста», и, как правило, их не следует перехватывать, поэтому их не нужно объявлять в предложении throws. Непроверенное исключение может произойти в любое время из любого кода. Вышеупомянутое обсуждение касается только отмеченных исключений
Петер Перхач
@Perhac :: да вы правы, но из моего понимания по чтению статей. Это также верно для непроверенных исключений. например, если метод суперкласса выбрасывает исключение нулевого указателя и если подкласс, переопределяющий метод, вызывает исключение. здесь Exception - это суперкласс исключения Null Pointer Exception. Тогда компилятор этого не допустит.
ylnsagar
1

Ну, java.lang.Exception расширяет java.lang.Throwable. java.io.FileNotFoundException расширяет java.lang.Exception. Поэтому, если метод генерирует исключение java.io.FileNotFoundException, тогда в методе переопределения вы не можете выбросить что-либо выше по иерархии, чем FileNotFoundException, например, вы не можете выбросить java.lang.Exception. Однако вы можете создать подкласс FileNotFoundException. Однако вам придется обрабатывать исключение FileNotFoundException в методе переопределения. Придумайте код и попробуйте!

Правила существуют, поэтому вы не потеряете исходное объявление throw из-за расширения специфичности, поскольку полиморфизм означает, что вы можете вызвать метод overriden в суперклассе.

PlanetJones
источник
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
    }
}
syrus.phoenix
источник
1

Переопределяющий метод НЕ должен генерировать проверенные исключения, которые являются новыми или более широкими, чем те, которые были объявлены переопределенным методом.

Это просто означает, что когда вы переопределяете существующий метод, исключение, которое генерирует этот перегруженный метод, должно быть либо тем же исключением, что и исходный метод, либо любым из его подклассов .

Обратите внимание, что проверка того, все ли отмеченные исключения обработаны, выполняется во время компиляции, а не во время выполнения. Таким образом, во время самой компиляции компилятор Java проверяет тип исключения, которое генерирует переопределенный метод. Поскольку решение о том, какой переопределенный метод будет выполняться, можно решить только во время выполнения, мы не можем знать, какое исключение мы должны поймать.


пример

Допустим, у нас есть класс Aи его подкласс B. Aимеет метод, m1а класс Bпереопределил этот метод (позвольте вызвать его, m2чтобы избежать путаницы ..). Теперь скажем m1throws E1и m2throws E2, который является E1суперклассом пользователя. Теперь напишем следующий фрагмент кода:

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

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

Аникет Такур
источник
class Parent {void method () выбрасывает IndexOutOfBoundsException {System.out.println ("Родительский метод"); }} class Child extends Parent {void method () throws RuntimeException {System.out.println ("Child method"); } Если родительский класс генерирует дочернее исключение времени выполнения, а потомок сам генерирует исключение времени выполнения. Это действительно так?
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

Теперь предположим, что readAndGetin Humanвыбрасывает любое другое проверенное исключение, например Exception, и мы знаем, что оно 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 () базового класса не генерирует никаких исключений

Я могу связать это с тем, что Exception уже, чем RuntimeException, это может быть либо No Exception (ошибка времени выполнения), либо RuntimeException и их дочерние исключения

абхи
источник
0

Метод переопределения подкласса может генерировать только несколько проверенных исключений, которые являются подклассами проверенного исключения метода суперкласса, но не может генерировать несколько проверенных исключений, которые не связаны с проверяемым исключением метода суперкласса

Fego
источник
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

Все вышеприведенные выводы верны, даже если в методе родительского класса объявлено сочетание отмеченного и непроверенного исключения.

Ссылка

Рамеш Папаганти
источник