Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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