Почему я не могу использовать блок 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 жалуется, что super - не первое утверждение.

Мое обходное решение:

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

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

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

Antimony 05.08.2012 03:41

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

Joshua 04.12.2012 04:08

Потому что правила этого не позволяют. Прочтите Спецификация JDK. Даже если вы пройдете мимо компилятора, верификатор его отклонит.

Hot Licks 06.11.2014 04:51
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
44
3
10 179
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

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

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

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

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

Почему бы просто не запретить вам использовать это в блоке catch? Это охватывает общий случай обертывания исключений.

Antimony 05.08.2012 03:40

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

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

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

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

Обновлено: в вашем случае Мой класс становится базовым объектом, а MyClassMock - подклассом.

Это сделано, чтобы предотвратить создание нового объекта SecurityManager из ненадежного кода.

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}

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

Начну с примера неудачной конструкции объекта.

Давайте определим класс 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 даже не инициализирована, она сохраняется как null.

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

Взгляните на этот более сложный пример

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 возникает исключение, каким будет значение последней переменной intb?

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

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

Один из способов обойти это - вызвать частную статическую функцию. После этого 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);
     }
  }
}

Уточните, почему?

Unheilig 06.11.2014 05:39

Я немного уточнил. Довольно?

aliteralmind 06.11.2014 06:41

Почему здесь работает вызов частной статической функции? А что не так с кодом OP, который, по вашему мнению, не работает?

Unheilig 06.11.2014 06:43

Задан вопрос о наследовании, а в этом коде вместо композиции

Daniel Pinyol 29.03.2016 18:04

Я знаю, что на этот вопрос есть множество ответов, но я хотел бы дать свой небольшой лакомый кусочек о том, почему это не разрешено, в частности, чтобы ответить, почему 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() для объекта вашего подкласса, и, возможно, потому, что исключение может принять ваш объект в качестве параметра и попытаться изменить значение унаследованных переменных экземпляра, которые еще не были инициализированы.

Было бы полезно сделать try { super(); ... } catch (SomeException e) { throw new SomeOtherException(e); }, чтобы конструктор мог иметь совершенно другой спецификатор исключения.

Joshua 10.12.2020 20:31

Другие вопросы по теме