Сведения о вопросе

MAT

16:03, 1st July, 2020

Теги

java   exception   mocking   try-catch    

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

Просмотров: 505   Ответов: 7

Итак, в Java первая строка вашего конструктора HAS должна быть вызовом 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 не позволяет мне сделать первое?


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

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



  Сведения об ответе

ЯЯ__4

18:03, 1st July, 2020

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

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

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

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

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


  Сведения об ответе

P_S_S

18:03, 1st July, 2020

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

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


  Сведения об ответе

appple

18:03, 1st July, 2020

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

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

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

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


  Сведения об ответе

piter

18:03, 1st July, 2020

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

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


  Сведения об ответе

padenie

18:03, 1st July, 2020

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

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

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


  Сведения об ответе

screen

18:03, 1st July, 2020

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

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

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

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


  Сведения об ответе

P_S_S

18:03, 1st July, 2020

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться