Почему я не могу использовать блок try вокруг моего вызова super ()? - PullRequest
37 голосов
/ 08 августа 2008

Итак, в Java первая строка вашего конструктора ДОЛЖНА быть вызовом super ... будь то неявный вызов super () или явный вызов другого конструктора. То, что я хочу знать, это то, почему я не могу поставить блок try вокруг этого?

Мой конкретный случай - у меня есть фиктивный класс для теста. Нет конструктора по умолчанию, но я хочу, чтобы тесты были проще для чтения. Я также хочу обернуть исключения, сгенерированные конструктором, в исключение RuntimeException.

Итак, что я хочу сделать, так это:

public class MyClassMock extends MyClass {
    public MyClassMock() {
        try {
            super(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // Mocked methods
}

Но Java жалуется, что супер не первое утверждение.

Мой обходной путь:

public class MyClassMock extends MyClass {
    public static MyClassMock construct() {
        try {
            return new MyClassMock();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public MyClassMock() throws Exception {
        super(0);
    }

    // Mocked methods
}

Это лучший обходной путь? Почему Java не позволяет мне сделать первое?


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

Я переопределяю любые методы, которые я использую в тестируемом классе, поэтому нет риска, что я использую неинициализированные переменные.

Ответы [ 7 ]

13 голосов
/ 08 августа 2008

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

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

В C # .NET есть аналогичные положения, и единственный способ объявить конструктор, который вызывает базовый конструктор, это:

public ClassName(...) : base(...)

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

7 голосов
/ 17 сентября 2008

Это сделано для того, чтобы кто-то не мог создать новый объект SecurityManager из ненадежного кода.

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}
6 голосов
/ 21 апреля 2012

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

Позвольте мне начать с примера неудачной конструкции объекта.

Давайте определим класс A, такой что:

class A {
   private String a = "A";

   public A() throws Exception {
        throw new Exception();
   }
}

Теперь предположим, что мы хотим создать объект типа A в блоке try...catch.

A a = null;
try{
  a = new A();
}catch(Exception e) {
  //...
}
System.out.println(a);

Очевидно, что вывод этого кода будет: null.

Почему Java не возвращает частично созданную версию A? В конце концов, к моменту отказа конструктора поле name объекта уже было инициализировано, верно?

Ну, Java не может вернуть частично построенную версию A, потому что объект не был успешно построен. Объект находится в несогласованном состоянии и поэтому отбрасывается Java. Ваша переменная A даже не инициализирована, она сохраняется как ноль.

Теперь, как вы знаете, чтобы полностью построить новый объект, все его суперклассы должны быть сначала инициализированы. Если один из суперклассов не будет выполнен, каково будет конечное состояние объекта? Это невозможно определить.

Посмотрите на этот более сложный пример

class A {
   private final int a;
   public A() throws Exception { 
      a = 10;
   }
}

class B extends A {
   private final int b;
   public B() throws Exception {
       methodThatThrowsException(); 
       b = 20;
   }
}

class C extends B {
   public C() throws Exception { super(); }
}

Когда вызывается конструктор C, если при инициализации B возникает исключение, каким будет значение конечной int переменной b?

Таким образом, объект C не может быть создан, это поддельный, это мусор, он не полностью инициализирован.

Для меня это объясняет, почему ваш код недопустим.

1 голос
/ 08 августа 2008

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

Так что это даже не опасность неинициализированных переменных или чего-то подобного вообще. Когда вы пытаетесь что-то сделать в конструкторе подкласса '1003 * до базового класса' конструктор , вы в основном просите компилятор расширить экземпляр базового объекта, который еще не существует.

Редактировать: В вашем случае MyClass становится базовым объектом, а MyClassMock является подклассом.

1 голос
/ 08 августа 2008

Я не знаю, как Java реализована внутри, но если конструктор суперкласса выдает исключение, то экземпляра класса, который вы расширяете, нет. Например, было бы невозможно вызвать методы toString() или equals(), поскольку в большинстве случаев они наследуются.

Java может разрешить попытку / перехватить вызов super () в конструкторе, если 1. вы переопределяете ВСЕ методы из суперклассов, и 2. вы не используете предложение super.XXX (), но все это звучит слишком сложный для меня.

0 голосов
/ 17 апреля 2017

Я знаю, что на этот вопрос есть многочисленные ответы, но я хотел бы рассказать немного о том, почему это не разрешено, в частности, о том, почему Java не позволяет вам делать это. Итак, поехали ...

Теперь имейте в виду, что super() должен вызываться раньше, чем что-либо еще в конструкторе подкласса, поэтому, если бы вы использовали блоки try и catch вокруг вашего вызова super(), блоки должны были бы выглядеть так:

try {
   super();
   ...
} catch (Exception e) {
   super(); //This line will throw the same error...
   ...
}

Если super () fails in the, попробуйте block, it HAS to be executed first in the catch block, so that super runs before anything in your subclass s конструктор. Это оставляет вас с той же проблемой, с которой вы столкнулись в начале: если выдается исключение, оно не перехватывается. (В этом случае он просто снова попадает в блок catch.)

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

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

0 голосов
/ 28 марта 2014

Одним из способов обойти это является вызов частной статической функции. Try-catch затем можно поместить в тело функции.

public class Test  {
  public Test()  {
     this(Test.getObjectThatMightThrowException());
  }
  public Test(Object o)  {
     //...
  }
  private static final Object getObjectThatMightThrowException()  {
     try  {
        return  new ObjectThatMightThrowAnException();
     }  catch(RuntimeException rtx)  {
        throw  new RuntimeException("It threw an exception!!!", rtx);
     }
  }
}
...