10 lutego 2007

Ciekawostki języka Java

Pracuję z Javą od pierwszych dni jej opublikowania. Ostatnio zdobyłem certyfikat SCJP 5.0 i wydawałoby się, że to wystarczy do zagwarantowania dogłębnej znajomości języka. Ale jak pokazały wydarzenia dnia wczorajszego, niekoniecznie.

Pierwsza ciekawostka dotyczyła dostępu do zmiennych i metod prywatnych (składowych klasy oznaczonych słowem kluczowym private). Skoro prywatnych to wydawałoby się, że sprawa jasna - nikt poza ich klasą macierzystą nie ma prawa ich "dotknąć". Ale, jak się okazało, nie jest to precyzyjna definicja.

Możnaby zadać pytanie, jak bardzo utrzymywana jest prywatność składowych klasy (pól i metod)? Nie sądzę, aby nie znalazła się przynajmniej jedna osoba programująca na codzień w Javie, która nie złapałaby się za głowę, kiedy ujrzałaby działanie poniższego programu. Czy zapytana, potrafiłba odpowiedzieć jaki będzie wynik kompilacji i przyjmując, że kompilacja się zakończy poprawnie, jaki będzie wynik działania poniższego programu? Uwagę powinien przyciągnąć dostęp do prywatnego pola i metody w metodzie statycznej main.
  package accesscontrol;

public class Private {

private int privateMember;

public Private(int privateMember) {
this.privateMember = privateMember;
}

public static void modifyPrivateField(Private p) {
p.privateMember *= 2;
}

private void callPrivateMethod() {
System.out.println("Prywatna metoda wywołana! Na pewno?");
}

public static void main(String[] args) {
Private p = new Private(5);
System.out.println("p.privateMember (przed zmianą) = " + p.privateMember);
modifyPrivateField(p);
System.out.println("p.privateMember (po zmianie) = " + p.privateMember);
p.callPrivateMethod();
}
}
Spróbuj odpowiedzieć na pytania samodzielnie, zanim podam trochę dodatkowych informacji.

Skoro prywatne składowe klasy, to prywatne, tak? Otóż nie! Wszystko zależy od tego, kto dostępuje składowych prywatnych klasy. W Javie (mówi się, że podobnie jak w C++, ale przeciwnie do Smalltalk) dostęp do zmiennych prywatnych jest niemożliwy, za wyjątkiem sytuacji, w której wykonywane operacje są inicjowane przez klasę macierzystą. Dodać do tego należy, że dostęp do prywatnej składowej dowolnego egzemplarza klasy X wewnątrz metod klasy X jest również możliwy (!) Dla mnie taki dostęp był dostępem z zewnątrz, pomimo, że w klasie deklarującej ową prywatną składową.

Wytłumaczono mi to w ten sposób, że prywatne elementy służą ukrywaniu implementacji, wewnętrznej realizacji działania klasy i to ona sama podejmuje decyzje odnośnie prywatnych składowych. Może zatem podjąć decyzję o modyfikacji swoich własnych prywatnych składowych,w tym i dowolnego egzemplarza siebie samej.

Cała literatura nt. temat brzmi w takim stylu (wycinek z podręcznika Javy - The Java Tutorial sekcja Controlling Access to Members of a Class):

The private modifier specifies that the member can only be accessed in its own class.

Jak widać z zaprezentowanego przykładu, interpretacja powyższego wycinka może nie być satysfakcjonująca. Dla mnie nie była. Na szczęście specyfikacja języka Java - The Java Language Specification - w sekcji 6.6.1 Determining Accessibility zawiera precyzyjne wytłumaczenie:

If the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

Może ranga dokumentu przekonuje mnie do tego wytłumaczenia, albo faktyczna jego precyzyjność, ale dla mnie wystarczająco tłumaczy zachowanie prywatnych składowych klas i ich widoczności w składowych klasy, nawet jeśli dotykają one egzemplarzy, ale tylko typu takiego samego, do jakiego one same należą.

Można zadać pytanie, a jak będzie w przypadku interfejsów? ;-) Uwaga: kawał!

Druga ciekawostka związana jest z wszechobecnymi adnotacjami, a szczególnie ich wykorzystaniu w tworzeniu aplikacji opartych o Java EE. Przedstawia się ją, a szczególnie ostatnio mnie absorbującą specyfikację EJB3 (wraz z JPA 1.0), jako bezinwazyjną, tzn. wymagania specyfikacji nie wprowadzają do klas żadnych zmian, które uniemożliwiłyby ich użycie poza środowiskiem serwera aplikacyjnego Java EE - zero wymaganych interfejsów, włączania do zadanej hierarchi dziedziczenia, itp.

Pytanie jakie mi zadano podczas ostatniej prezentacji specyfikacji EJB 3.0 dotyczyło obecności adnotacji w klasie, będącej z punktu widzenia serwera aplikacyjnego komponentem EJB (użycie adnotacji @Stateless) i jej uruchomieniu poza serwerem aplikacyjnym (co zdejmuje jej "specjalność").

Adnotacje wymagają importu klas je realizujących, np. @EJB będzie wymagało importu klasy javax.ejb.EJB. Pytanie jakie się pojawia to obecność klasy podczas kompilacji i uruchomienia (można postawić ogólniejszy problem, nie dotykający adotacji, a poruszający jedynie same deklaracje import). W którym momencie, klasa w imporcie będzie wymagana? Podczas kompilacji i uruchamiana, czy wyłącznie podczas kompilacji, a może jednak wyłącznie podczas uruchamiania? Dla zobrazowania problemu przedstawiam klasę, która wykorzystuje adnotację @EJB.
  package annotation;

import javax.ejb.EJB;

public class MissingAnnotationAtRuntime {

@EJB
private Object ejbRef;

public static void main(String[] args) {
System.out.println("Prezentacja programowania z adnotacjami bez nich podczas uruchomienia.");
}
}
Pytanie 1: Czy klasa skompiluje się bez dostępności klasy EJB?

Pytanie 2: Czy klasa może zostać uruchomiona bez dostępności klasy EJB?

Odpowiedź dostarczają poniższe sesje.
  $ java -version
java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) Client VM (build 1.5.0_08-b03, mixed mode)

$ echo $CLASSPATH


$ javac annotation/MissingAnnotationAtRuntime.java
annotation/MissingAnnotationAtRuntime.java:3: package javax.ejb does not exist
import javax.ejb.EJB;
^
annotation/MissingAnnotationAtRuntime.java:7: cannot find symbol
symbol : class EJB
location: class annotation.MissingAnnotationAtRuntime
@EJB
^
2 errors
oraz
  $ echo $CLASSPATH


$ java -cp bin annotation.MissingAnnotationAtRuntime
Prezentacja programowania z adnotacjami bez nich podczas uruchomienia.
Zaskakujące? Dla mnie już nie!