Express killers, cz. V
Przykład pierwszy
W dzisiejszym cyklu zaczniemy od krótkiego kodu dla spostrzegawczych. Pytanie brzmi: zakładając, że poniższa klasa się kompiluje, a jej uruchomienie nie spowoduje rzucenie wyjątkiem, jakiej klasy jest referencja obj
?
public class ClassType { // definicja typu obj public static void main(String[] args) { Object pattern = ClassType.class; for (int i = 0; i < obj.length(); i++) { if (pattern.equals(obj)) obj += 5; else obj += -5; System.err.println(obj); } } }
Jeśli zadanie jest zbyt trudne, skopiuj klasę do swojego IDE. Pomoże Ci to znaleźć rozwiązanie.
Przykład drugi
W drugiej części (już bez pomocy IDE!) przypomnijmy sobie, czym grozi brak enkapsulacji oraz przesłanianie zmiennych. Co będzie wynikiem uruchomienia poniższej metody main()
?
public class AccessingToOverridenAttribute { public int a; public void add() { a = 10; } public static void main(String[] args) { AccessingToOverridenAttribute f = new Bar(); f.add(); System.err.println(++f.a); System.err.println(++((Bar) f).a); } } class Bar extends AccessingToOverridenAttribute { public int a = 2; public void add() { a = 5; } }
Odpowiedzi:
Przykład pierwszy:
Kluczem do zagadki jest metoda length()
i operacje dodawania. Po pierwsze obj
nie może być tablicą, gdyż w pętli użyto funkcji length()
, a nie length
. Oznacza to, że obj
jest referencją do klasy, która implementuje metodę length()
. Żaden wrapper typów prymitywnych jak Integer
czy Double
nie implementuje tej metody. Ma ją natomiast String
. Czy jest możliwe, by była to inna klasa, którą użytkownik sam zaimplementował? Metoda length()
nie stanowiłaby problemu, gdyż można ją zaimplementować. Natomiast operator dodawania jest dozwolony tylko dla typów prymitywnych, wraperów oraz wspomnianej klasy String
. Java nie udostępnia mechanizmów przeciążania operatorów, jak to ma miejsce np. w języku C++. Klasa String
posiada modyfikator final
, zatem nie można jej także rozszerzyć. Wobec powyższego jedyną możliwością, by powyższy kod się kompilował jest, by obiekt obj był referencją do klasy String
. I nie był wartością null
, gdyż to spowoduje wygenerowanie wyjątku podczas uruchomienia.
Przykład drugi:
Po pierwsze metoda add()
zostanie wywołana zależnie od typu obiektu f
, a nie deklaracji tej zmiennej. Zatem sterowanie zostanie przekazane do klasy Bar
. Oznacza to, że to wartość atrybutu a
klasy bazowej nie ulegnie zmianie.
Po drugie użycie wartości f.a
spowoduje wydrukowanie atrybutu wskazanego typem zmiennej niezależnie od tego, na co wskaże referencja. Zostanie to określone na etapie kompilacji i nie ma znaczenia, że klasa pochodna deklaruje atrybut o tej samej zmiennej. Nie warto zatem deklarować zmiennej o takiej samej nazwie, jak w klasie nadrzędnej i nie należy pozostawiać dostępu do tej zmiennej w sposób, który umożliwi użycie konstrukcji, jak we wspomnianym przykładzie.
Po trzecie wydrukowanie zmiennej poprzez wywołanie ((Bar) f).a
zadziała wg powyższej zasady: kompilator na etapie kompilacji wskaże atrybut klasy pochodnej, gdyż jawnie tego oczekiwano poprzez rzutowanie.
Wnioski: atrybuty powinny być niewidoczne poza klasą i dostępne poprzez metody get
i set
, co skutecznie minimalizuje opisane powyżej problemy oraz ułatwia debugowanie.
Damian,
I know that the article is over a year old, but I only discovered exPress.
In your first example you say that String is immutable, hence it cannot be extended.
It is true that String is immutable and it is also true that it cannot be extended, but former does not imply the latter.
String cannot be extended because it is final class.
It cannot be modified, because is immutable.
Other than this, good article!
Thanks!
FORMATTING FAIL
Daniel, you are completely right.