още една гатанка

13 отговора 689 прегледа
#1

още една гатанка

какво става и защо?

class Parent {
public Parent() {
init();
}

protected void init() {}
}

public class Puzzling {
private Child child = new Child();

public boolean isAllowedToPrint() {
return true;
}
private class Child extends Parent {

protected void init() {
if (isAllowedToPrint()) {
System.out.println("Child");
}
}
}

public static void main(String[] args) {
new Puzzling();
}
}


Отговор
в отговор на purist #2

Re: още една гатанка

При мен гръмна с NullPointerException на реда с:
if (isAllowedToPrint())

Явно нямам Puzzling обект, на когото да извикам съответния метод. Като направя метода isAllowedToPrint статичен нещата се оправят и работят като хората. Та сега въпроса ми, като на неопитен джавист, е: защо компилатора го пуска това? Няма как вътрешния клас да държи референция към външния, че и да му вика методи нали? Или има някаква магия дето дава тихичко такъв референс, но в по-късен момент?

Всъщност пробвах и късния момент. Ако създавам Child обекта в метод, който се вика след създаването на Puzzling, гърмежа си е същият.

Отговор
в отговор на purist #3

Re: още една гатанка

Викаш наследен метод от конструктора на парент класа. Доста често срещан проблем. Преди да е свършила инициализацията на парент-а, наследника му още няма референс към оутер инстанцията. Интересен случай. Тъй като викането на наследен метод от конструктора е дупка в сигурността е желателно да се избягва и не сам го наблюдавал точно в този му вариант. Добър въпрос

Johny Cage

------------------------------------------------
DOOM Will Never Die, Just The Players
Отговор
в отговор на Johny Cage #4

Re: още една гатанка

В отговор на:

викането на наследен метод от конструктора е дупка в сигурността




Защо? Знам няколко езици дето го позволяват тоя номер. Дай някой пример за несигурна ситуация свързана с викането на предефиниран метод от конструктора.

Отговор
в отговор на Дeшeв #5

Re: още една гатанка

В момента ме мързи за пример, но в "Effective Java" е обяснено доста подробно.

Johny Cage

------------------------------------------------
DOOM Will Never Die, Just The Players
Отговор
в отговор на Johny Cage #6

Re: още една гатанка

Аха. Значи "викането на наследен метод от конструктора е дупка в сигурността" е така само за Джава?

Отговор
в отговор на Дeшeв #7

Re: още една гатанка

хм. може би трябваше да добавя "теоретечна". Естествено при правилна употреба не е проблем.

Johny Cage

------------------------------------------------
DOOM Will Never Die, Just The Players
Отговор
в отговор на Johny Cage #8

Re: още една гатанка

викането на наследени методи от конструктор по принцип си е нормална практика; тази гатанка демонстрира разчитане на това че външния клас ще е създаден напълно когато се създава вътрешен клас.

Това е лошата практика, и най-подвеждащото в случая е че можеш от конструктор да регистрираш callback s this, т.е имаш референция към създавания обект от преди конструктора да е завършил, но тази референция не се лепва към вътрешните класове преди конструктора да завърши. Това според мен е недостатък на езика и някой от Сън го е домързяло да си свърши работата докрай. Дешев, как е в С#?

Отговор
в отговор на purist #9

Re: още една гатанка

В C# няма проблеми да викаш виртуални методи от конструктор. При това ако са предефинирани от наследници се викат методите на наследниците. В Ruby също е така.

Виж другите магии с вътрешните класове не съм ги пробвал. И ако ги видя някъде първо ще изнеса класовете като външни, за да направя кода по-ясен.

Отговор
в отговор на Дeшeв #10

Re: още една гатанка

Това че C# може да вика виртуални методи в конструктора не е вярно. Ситуацията е е много подобна и със C#. Това е пример от книгата The C# Programming Language на Anders Hejlsberg:
using System;
class A
{
public A() {
PrintFields();
}
public virtual void PrintFields() {}
}

class B: A
{
int x = 1;
int y;
public B() {
y = -1;
}
public override void PrintFields() {
Console.WriteLine("x = {0}, y = {1}", x, y);
}
}

Та според този който е измислил C# new B() ще отпечата x = 1, y = 0, което е почти същото като "проблема" описан тук.

Със С++ нещата са още по-сложни. Например стойността на х в следващия пример е неопределена и само GCC когато компилира с -Wall ще предупреди за проблема:
struct Fatal
{
Fatal(int i)
: y(i)
, x(y*2)
{}
int x;
int y;
}

Не си струва да се впускаме в подробности за виртуални методи в конструктора, защото все пак това е Джава група.

Инициализацията на обекти е доста добре специфицирана в тези езици и това са неща което всеки уважаващ себе си програмист трябва да знае, а не проблеми поради недоглеждане.

Отговор
в отговор на antijb #11

Re: още една гатанка

В отговор на:

Това че C# може да вика виртуални методи в конструктора не е вярно.




Нещо не се разбираме май. Ц# примера ти показва такова извикване. Имах предвид, че ако в Ц# конструктор викнеш виртуален метод, то ако имаш предефиниран метода в дъщерни класове, ще извикаш предефинирания.

Това за Ц++ съм сигурен, че не е така. Мисля, че и за Джава. Нали?

Отговор
в отговор на Дeшeв #12

Re: още една гатанка

Имах предвид че и при двата езика ефекта е почти еднакъв.
Естествено че ще се извика виртуалнлният метод който е дефиниран в последния наследяващ клас. Пробламът е че този клас не е още инициализиран поради което резултата е неочакван.
Това е така по дефиниция и в двата езика.

Отговор
в отговор на purist #13

Re: още една гатанка

В 1.5 това нещо е оправено .

Отговор
в отговор на purist #14

Re: още една гатанка

Всъщност действително има някакъв проблем когато класа се компилира за 1.3. От баиткода на Child конструктора се вижда че this$0 се инициализира по-късно отколкото е необходимо. Изглежда че проблема го няма в 1.4, а в JDK 1.5 инициализацията явно се поема от VM-a.

JDK 1.3
Puzzling$Child(Puzzling);
Code:
0: aload_0
1: invokespecial #13; //Method Parent."<init>":()V
4: aload_0
5: aload_1
6: putfield #15; //Field this$0:LPuzzling;
9: return

JDK 1.4
Puzzling$Child(Puzzling);
Code:
0: aload_0
1: aload_1
2: putfield #12; //Field this$0:LPuzzling;
5: aload_0
6: invokespecial #15; //Method Parent."<init>":()V
9: return

JDK 1.5
Puzzling$Child(Puzzling, Puzzling$1);
Code:
0: aload_0
1: aload_1
2: invokespecial #1; //Method "<init>":(LPuzzling;)V
5: return

Отговор