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

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

Ответов (7)

Решение

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

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

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

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

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

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

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

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

Таким образом, объект 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);
     }
  }
}

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

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

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

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

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

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

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

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