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

StackOverflow https://stackoverflow.com/questions/5328

Вопрос

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

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

Это было полезно?

Решение

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

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

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

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

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

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

Это сделано для того, чтобы помешать кому-то создать новый 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, какова была бы ценность конечного int переменная b?

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

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

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

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

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

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

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

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

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

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

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

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

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

Один из способов обойти это - вызвать частную статическую функцию.Затем функция 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);
     }
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top