Java конструктор не так интуитивно понятен. Или, возможно, это не Java, это C#, который не интуитивно понятен
Учитывая этот код Java, это выводит 0 и 4:
class A{
A() { print(); }
void print() { System.out.println("A"); }
}
class B extends A{
int i = Math.round(3.5f);
public static void main(String[] args){
A a = new B();
a.print();
}
void print() { System.out.println(i); }
}
И с этим идентичным кодом C# это выводит 4 и 4
используя Систему;
class A{
internal A() { print(); }
virtual internal void print() { Console.WriteLine("A"); }
}
class B : A{
int i = (int) Math.Round(3.5f);
public static void Main(string[] args){
A a = new B();
a.print();
}
override internal void print() { Console.WriteLine(i); }
}
Хотя я понимаю, что выходные данные должны быть 4 и 4 на Java, но ответ на самом деле 0 и 4 на Java. Тогда я попробовал это в C#, ответ 4 и 4
Что дает? Логическое обоснование Java заключается в том, что во время построения B A все еще инициализируется (следовательно, я утверждаю, что B все еще инициализируется, если Java сказал, что A все еще инициализируется), поэтому значение по умолчанию должно быть 0. Следовательно, выходные данные равны 0 и 4 в Java.
Почему поведение конструктора C# отличается от Java или наоборот?
2 ответа
Это происходит из-за различий в порядке инициализации объекта в конструкторах.
Что происходит в Java:
- (пустой, неявный) Конструктор B называется
- Вызывается конструктор суперкласса A (выводит 0, поскольку я не инициализирован)
- я инициализирован после конструктора суперкласса
- вызывается print() (печатает 4)
Что происходит в C#:
- (пустой, неявный) Конструктор B называется
- я инициализируется перед вызовом конструктора суперкласса
- Вызывается конструктор суперкласса A (печатает 4, поскольку я уже инициализирован)
- вызывается print() (печатает 4)
Ни то, ни другое, ни то, ни другое - это просто разница в том, как компилятор упорядочивает операции построения. Лично я считаю, что упорядочение Java немного более логично, потому что для меня имеет смысл, что суперкласс полностью создан до того, как произойдет инициализация подкласса.
В любом случае, поскольку логика может быть довольно сложной, я бы посоветовал вам избегать вызова виртуальных методов во время конструирования объекта в целом.
Порядок инициализации в Java:
1. Хранилище экземпляра стирается в ноль, автоматически устанавливая все примитивы в объекте в их значения по умолчанию (ноль для чисел и эквивалент для логического и char) и ссылки на ноль.
2. Конструктор базы class A
называется. Это будет вызов print
метод в class B
, поскольку это переопределенный метод. i
0 в данный момент.
3. Выполняется инициализация члена класса B. Так i
сейчас 4
Чтобы не создавать такого рода сюрпризы, не вызывайте какие-либо нестатические или не приватные методы в конструкторе, поскольку они могут быть переопределены в производных классах.