Почему переопределенные методы не могут генерировать исключения шире, чем переопределенный метод? - PullRequest
90 голосов
/ 04 мая 2011

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

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

Ответы [ 14 ]

145 голосов
/ 04 мая 2011

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

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 как форму документации, если хотите, но компилятор ничего не навязывает.

22 голосов
/ 29 ноября 2012

Переопределяющий метод МОЖЕТ генерировать любое непроверенное (во время выполнения) исключение, независимо от того, переопределенный метод объявляет исключение

Пример:

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()");
    }
}
14 голосов
/ 11 октября 2013

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

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

6 голосов
/ 29 февраля 2016

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

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) throw ничего.

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) Наличие RuntimeException в бросках не обязательно.

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

5 голосов
/ 04 мая 2011

Чтобы проиллюстрировать это, рассмотрим:

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

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

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

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

Если IOException было разрешено выбрасывать, код клиента уже не правильный.

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

3 голосов
/ 07 февраля 2018

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

Чтобы ответить на этот вопрос, сообщите нам, что является исключением «Не проверено и проверено».

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

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

говорят, что у вас есть суперкласс A с методом M1, выбрасывающий E1, и класс B, производный от A с методом M2, переопределяющим M1.M2 не может выбросить что-либо РАЗНОЕ или МЕНЬШЕ СПЕЦИАЛИЗИРОВАННОЕ, чем E1.

Из-за полиморфизма клиент, использующий класс A, должен иметь возможность обрабатывать B так, как если бы это было A. Наследование ===> Is-a (B - это-а А).Что если этот код, работающий с классом A, обрабатывает исключение E1, так как M1 объявляет, что оно выдает это проверенное исключение, но затем генерируется исключение другого типа?Если M1 генерирует IOException, M2 вполне может генерировать FileNotFoundException, поскольку это - IOException.Клиенты А могут справиться с этим без проблем.Если бы выброшенное исключение было шире, клиенты A не имели бы возможности узнать об этом и поэтому не имели бы возможности его поймать.

1 голос
/ 22 декабря 2017

Чтобы понять это, давайте рассмотрим пример, где у нас есть класс 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 in Human вызывает любое другое проверенное исключение, например, Exception, и мы знаем,readAndGet позвонятиз экземпляра Human, потому что mammal содержит new Human().

Поскольку для компилятора метод вызывается из Mammal, поэтому компилятор заставит нас обрабатывать только IOException, но приво время выполнения мы знаем, что метод будет выдавать Exception исключение, которое не обрабатывается, и наш код прервется, если метод вызовет исключение.

Вот почему это предотвращается на уровне самого компилятора, и нам не разрешенобросить любое новое или более широкое проверенное исключение, потому что оно не будет обработано JVM в конце.

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

1 голос
/ 03 октября 2013

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

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

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


Пример

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

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

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

1 голос
/ 29 ноября 2012

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

Пример:

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
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...