още една гатанка
13 отговора
689 прегледа
още една гатанка
какво става и защо?
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();
}
}
Re: още една гатанка
При мен гръмна с NullPointerException на реда с:
if (isAllowedToPrint())
Явно нямам Puzzling обект, на когото да извикам съответния метод. Като направя метода isAllowedToPrint статичен нещата се оправят и работят като хората. Та сега въпроса ми, като на неопитен джавист, е: защо компилатора го пуска това? Няма как вътрешния клас да държи референция към външния, че и да му вика методи нали? Или има някаква магия дето дава тихичко такъв референс, но в по-късен момент?
Всъщност пробвах и късния момент. Ако създавам Child обекта в метод, който се вика след създаването на Puzzling, гърмежа си е същият.
if (isAllowedToPrint())
Явно нямам Puzzling обект, на когото да извикам съответния метод. Като направя метода isAllowedToPrint статичен нещата се оправят и работят като хората. Та сега въпроса ми, като на неопитен джавист, е: защо компилатора го пуска това? Няма как вътрешния клас да държи референция към външния, че и да му вика методи нали? Или има някаква магия дето дава тихичко такъв референс, но в по-късен момент?
Всъщност пробвах и късния момент. Ако създавам Child обекта в метод, който се вика след създаването на Puzzling, гърмежа си е същият.
Re: още една гатанка
Викаш наследен метод от конструктора на парент класа. Доста често срещан проблем. Преди да е свършила инициализацията на парент-а, наследника му още няма референс към оутер инстанцията. Интересен случай. Тъй като викането на наследен метод от конструктора е дупка в сигурността е желателно да се избягва и не сам го наблюдавал точно в този му вариант. Добър въпрос
Johny Cage
------------------------------------------------
DOOM Will Never Die, Just The Players
Johny Cage
------------------------------------------------
DOOM Will Never Die, Just The Players
Re: още една гатанка
В отговор на:викането на наследен метод от конструктора е дупка в сигурността
Защо? Знам няколко езици дето го позволяват тоя номер. Дай някой пример за несигурна ситуация свързана с викането на предефиниран метод от конструктора.
Re: още една гатанка
В момента ме мързи за пример, но в "Effective Java" е обяснено доста подробно.
Johny Cage
------------------------------------------------
DOOM Will Never Die, Just The Players
Johny Cage
------------------------------------------------
DOOM Will Never Die, Just The Players
Re: още една гатанка
Аха. Значи "викането на наследен метод от конструктора е дупка в сигурността" е така само за Джава?
Re: още една гатанка
хм. може би трябваше да добавя "теоретечна". Естествено при правилна употреба не е проблем.
Johny Cage
------------------------------------------------
DOOM Will Never Die, Just The Players
Johny Cage
------------------------------------------------
DOOM Will Never Die, Just The Players
Re: още една гатанка
викането на наследени методи от конструктор по принцип си е нормална практика; тази гатанка демонстрира разчитане на това че външния клас ще е създаден напълно когато се създава вътрешен клас.
Това е лошата практика, и най-подвеждащото в случая е че можеш от конструктор да регистрираш callback s this, т.е имаш референция към създавания обект от преди конструктора да е завършил, но тази референция не се лепва към вътрешните класове преди конструктора да завърши. Това според мен е недостатък на езика и някой от Сън го е домързяло да си свърши работата докрай. Дешев, как е в С#?
Това е лошата практика, и най-подвеждащото в случая е че можеш от конструктор да регистрираш callback s this, т.е имаш референция към създавания обект от преди конструктора да е завършил, но тази референция не се лепва към вътрешните класове преди конструктора да завърши. Това според мен е недостатък на езика и някой от Сън го е домързяло да си свърши работата докрай. Дешев, как е в С#?
Re: още една гатанка
В C# няма проблеми да викаш виртуални методи от конструктор. При това ако са предефинирани от наследници се викат методите на наследниците. В Ruby също е така.
Виж другите магии с вътрешните класове не съм ги пробвал. И ако ги видя някъде първо ще изнеса класовете като външни, за да направя кода по-ясен.
Виж другите магии с вътрешните класове не съм ги пробвал. И ако ги видя някъде първо ще изнеса класовете като външни, за да направя кода по-ясен.
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;
}
Не си струва да се впускаме в подробности за виртуални методи в конструктора, защото все пак това е Джава група.
Инициализацията на обекти е доста добре специфицирана в тези езици и това са неща което всеки уважаващ себе си програмист трябва да знае, а не проблеми поради недоглеждане.
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;
}
Не си струва да се впускаме в подробности за виртуални методи в конструктора, защото все пак това е Джава група.
Инициализацията на обекти е доста добре специфицирана в тези езици и това са неща което всеки уважаващ себе си програмист трябва да знае, а не проблеми поради недоглеждане.
Re: още една гатанка
В отговор на:Това че C# може да вика виртуални методи в конструктора не е вярно.
Нещо не се разбираме май. Ц# примера ти показва такова извикване. Имах предвид, че ако в Ц# конструктор викнеш виртуален метод, то ако имаш предефиниран метода в дъщерни класове, ще извикаш предефинирания.
Това за Ц++ съм сигурен, че не е така. Мисля, че и за Джава. Нали?
Re: още една гатанка
Имах предвид че и при двата езика ефекта е почти еднакъв.
Естествено че ще се извика виртуалнлният метод който е дефиниран в последния наследяващ клас. Пробламът е че този клас не е още инициализиран поради което резултата е неочакван.
Това е така по дефиниция и в двата езика.
Естествено че ще се извика виртуалнлният метод който е дефиниран в последния наследяващ клас. Пробламът е че този клас не е още инициализиран поради което резултата е неочакван.
Това е така по дефиниция и в двата езика.
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
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