Наследование, подпись метода, переопределение метода и броски

Мой Parent класс:

import java.io.IOException;
public class Parent {
        int x = 0;
        public int getX() throws IOException{
        if(x<=0){
         throw new IOException();
        }
       return x;
      }
 }

I extend этот класс для записи подкласса Child:

public class Child1 extends Parent{
     public int getX(){
        return x+10;
   }
}

Обратите внимание, что при переопределении метода getX в классе Child я удалил предложение throws из определения метода. Теперь это приводит к аномальному поведению ожидаемого компилятора:

new Parent().getX() ;

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

new Child().getX() ;

компилируется без его включения в блок try-catch.

Но нижним строкам кода нужен блок try-catch.

Parent p = new Child();
p.getX();

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

Ответ 1

Нет, это уместно - переопределенный метод может быть более ограниченным в отношении того, что он выбрасывает (и возвращает), потому что это может быть полезно для тех, кто знает во время компиляции, что они будут использовать переопределенный метод, и не будут хочу беспокоиться об исключениях, которые не могут произойти и т.д. Однако это должно быть более ограничительным, а не более разрешительным, так что это не может удивить вызывающих абонентов, которые обращаются к нему через родительскую декларацию.

Использование переопределенного метода с помощью ссылки типа Parent никогда не нарушит контракт "он может выбросить IOException" - отсутствие исключения не нарушает контракт. Другой путь (если родитель не объявил исключение, но метод переопределения) будет нарушением контракта.

Ответ 2

Хорошо, что метод overriding вообще не может вызывать каких-либо исключений (или, по крайней мере, меньше исключений), поэтому вы можете удалить исключения из предложения throw (или предложение throw в целом).

Предположим, что метод overriding ловит все исключения, регистрирует их и возвращает специальное значение. Хотя это не очень хороший стиль (это изменило бы семантику метода), это все еще возможно, и поэтому вам не придется ловить исключения, которые никогда не бросаются, если вы знаете во время компиляции, что имеете дело с Child.

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

Ответ 3

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

Но помните одно: если вы хотите включить предложение throws в метод подкласса, то предложение throws должно ассоциироваться с исключением, которое должно быть таким же или подклассом исключения, который вызывается его методом суперкласса. например -

class Super{
    void a()throws IOException{
        ......
    }
}
class Sub extends Super{
    void a()throws IOException{
        ......
    }
}

Метод a() класса Sub должен вызывать IOException или любой подкласс IOException, иначе компилятор покажет ошибку.

Это означает, что если вы пишете

void a()throws Exception

в классе Sub, то это вызовет ошибку компиляции.

Ответ 4

Легко запомнить

  • Модификатор доступа может быть изменен с ограниченного до менее ограниченного,
    например, от защищенных до публичных, но не наоборот. Подпись
  • throws может быть изменена от родительского исключения до дочернего класса исключения, но не наоборот.

Этот код действителен

public class A  {

    protected String foo() throws Exception{
        return "a";
    }

    class B extends A {
        @Override
        public String foo() throws IOException{
            return "b";
        }
    }
}

Переопределенный метод foo имеет открытый доступ, не защищен и бросает IOException, что дочерний элемент Exception

Этот код недействителен

public class A  {

    public String foo() throws IOException{
        return "a";
    }

    class B extends A {
        @Override
        protected String foo() throws Exception{
            return "b";
        }
    }
}

Переопределенный метод foo имеет более ограниченный модификатор доступа и выбрасывает исключение, что NOT child из IOException

Кстати, вы можете переопределить метод из суперкласса и вообще не выбрасывать ecxeptions

Этот код действителен

public class A  {

    public String foo() throws IOException{
        return "a";
    }

    class B extends A {
        @Override
        public String foo(){
            return "b";
        }
    }
}