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




К сожалению, компиляторы не могут работать на теоретических принципах, и даже если вы знаете, что это безопасно в вашем случае, если бы они это позволяли, это должно быть безопасно для всех случаев.
Другими словами, компилятор останавливает не только вас, он останавливает всех, включая всех, кто не знает, что это небезопасно и требует особого обращения. Вероятно, для этого есть и другие причины, поскольку у всех языков обычно есть способы делать вещи небезопасно, если кто-то знает, как с ними бороться.
В C# .NET есть аналогичные положения, и единственный способ объявить конструктор, вызывающий базовый конструктор, таков:
public ClassName(...) : base(...)
при этом базовый конструктор будет вызываться перед телом конструктора, и вы не можете изменить этот порядок.
Почему бы просто не запретить вам использовать это в блоке catch? Это охватывает общий случай обертывания исключений.
Я не знаю, как 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);
}
}
}
Уточните, почему?
Я немного уточнил. Довольно?
Почему здесь работает вызов частной статической функции? А что не так с кодом OP, который, по вашему мнению, не работает?
Задан вопрос о наследовании, а в этом коде вместо композиции
Я знаю, что на этот вопрос есть множество ответов, но я хотел бы дать свой небольшой лакомый кусочек о том, почему это не разрешено, в частности, чтобы ответить, почему 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); }, чтобы конструктор мог иметь совершенно другой спецификатор исключения.
Одно интересное замечание заключается в том, что это чисто ограничение языка Java. Эквивалентный байт-код вполне допустим.