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

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

Мой конкретный случай - у меня есть фиктивный класс для теста. Нет конструктора по умолчанию, но я хочу, чтобы он облегчал чтение тестов. Я также хочу обернуть исключения, сгенерированные конструктором, в 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.08.2008 21:17:56
Одним интересным примечанием является то, что это чисто ограничение языка Java. Эквивалентный байт-код совершенно действителен.
Antimony 4.08.2012 23:41:57
Вы уверены, что байт-код все еще действителен? Я помню, что он стал недействительным после того, как кто-то использовал полученную дыру в безопасности, которую я продемонстрировал ниже.
Joshua 4.12.2012 00:08:40
Потому что правила не позволяют этого. Прочитайте спецификацию JDK . Даже если вы пройдете мимо компилятора, верификатор отклонит его.
Hot Licks 6.11.2014 01:51:04
7 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

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

15
7.08.2008 21:32:34
Почему бы просто не помешать вам использовать это в блоке catch? Это покрывает общий случай исключения исключений.
Antimony 4.08.2012 23:40:40

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

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

1
8.11.2011 19:14:00

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

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

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

1
7.08.2008 21:52:45

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

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}
8
1.11.2011 12:11:27

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

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

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

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 не может быть создан, он является поддельным, это мусор, он не полностью инициализирован.

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

6
17.04.2017 00:46:36

Одним из способов обойти это является вызов частной статической функции. 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);
     }
  }
}
-1
6.11.2014 03:39:06
Хотите уточнить, почему?
Unheilig 6.11.2014 02:39:32
Я разработал немного. Довольно?
aliteralmind 6.11.2014 03:41:42
Почему вызов частной статической функции работает здесь? А что не так с кодом от OP, который, по вашему мнению, не работает?
Unheilig 6.11.2014 03:43:34

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

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

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

Если super()в tryблоке происходит сбой , он ДОЛЖЕН быть выполнен первым в catchблоке, чтобы он выполнялся superраньше всего в конструкторе вашего подкласса. Это оставляет вас с той же проблемой, что и у вас в начале: если выдается исключение, оно не перехватывается. (В этом случае его просто снова бросают в блок catch.)

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

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

0
27.06.2019 15:18:32