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!

9 komentarzy:

  1. Pola prywatne są po prostu dostępne tylko i wyłącznie dla danej klasy (nie obiektu!). Bez tego nie bylibyśmy przecież w stanie napisać poprawnie metody
    boolean equals(Object o)
    jeśli nasz obiekt posiada atrybut prywatny dla którego z jakiś przyczyn nie utworzyliśmy gettera (lub getter jest prywatny).

    Zwróć uwagę że bardzo podobny przykład do Twojego, różniący się przesunięciem metody main do osobnej klasy już się nie kompiluje:

    public class Private {
    public static void main(String[] args) {
    ActualPrivate p = new ActualPrivate(5);
    System.out.println("p.privateMember (przed zmianą) = " + p.privateMember); // blad kompilacji
    modifyPrivateField(p); // blad kompilacji
    System.out.println("p.privateMember (po zmianie) = " + p.privateMember); // blad kompilacji
    p.callPrivateMethod(); // blad kompilacji
    }
    }

    class ActualPrivate {
    private int privateMember;
    public ActualPrivate(int privateMember) {
    this.privateMember = privateMember;
    }
    public static void modifyPrivateField(ActualPrivate p) {
    p.privateMember *= 2;
    }
    private void callPrivateMethod() {
    System.out.println("Prywatna metoda wywołana! Na pewno?");
    }
    }


    czyli to nie jest kwestia tego że dostępujesz prywatnych metod z ciała klasy top level która deklaruje obiekt.
    Tak naprawdę powyższa definicja odnosi się do klas wewnętrznych (inner classes) i pozwala na coś takiego:

    public class Private {
    private class InnerPrivate {
    private int x;
    }
    public void testAccess() {
    new InnerPrivate().x = 3;
    }
    }

    OdpowiedzUsuń
  2. a przy okazji - Twój blog to świetna lektura, śledzę go od pewnego czasu i fajnie się go czyta.

    OdpowiedzUsuń
  3. Dzięki swiety! Potwierdza to, że dużo jeszcze czasu upłynie zanim pewnie stwierdzę, że znam Javę. Cieszę się, że mam SCJP 5 za sobą. Po Twoim komentarzu dopiero byłbym spietrany ;-)

    Jacek

    OdpowiedzUsuń
  4. Czyżby ciekawostka napotkana na JavaBlackBelt? Dużo jest tam takich cudów :) Ten konkretny przykład (private nie będący private :P ) akurat znałem, ale np. wybrane zadanka z kategorii "polimorfizm" z egzaminu "OO intermediate" mnie osobiście zwaliły z krzesła :P Można się ostro zdołować w pierwszym odruchu (jeżeli się akurat na takiego mordercę trafi) :) Człowiek już myśli, że nic go w podstawowej składni Javy nie może zaskoczyć, a tu okazuje się że pytania średnio zaawansowane potrafią go zniszczyć :P

    OdpowiedzUsuń
  5. Witaj Henryk!

    Nie było to na JBB, ale powinienem częściej tam zaglądać, bo faktycznie tam jest sporo takiego materiału.

    A jak Ci idzie pasowanie, tj. zdobywanie pasów?! Pochwal się ;-)

    Jacek

    OdpowiedzUsuń
  6. Hej

    A idzie nie najgorzej. :P Straszliwie mnie JBB wciągnął, więc zdaję i moderuję kiedy tylko mogę :P BTW linka do serwisu podpatrzyłem u Ciebie na blogu. Także masz na sumieniu zwerbowanie co najmniej jednego moderatora :P

    Celuję teraz w brązowy pas. Chcę go trochę na skróty zdobyć tzn. głównie zdając Spring Core warty, aż 9 ptk (ale kosztuje aż 50 contribution). Także książkę Wrox-a muszę pomęczyć, żeby nie stracić przypadkiem tej fortuny :P Aż takim kowbojem Springa się nie czuję, żeby z marszu go zdać :)

    Pozdrawiam

    OdpowiedzUsuń
  7. Miło! Koniecznie napisz, jak zdobędziesz pasa.

    Samemu przygotowuję egzaminy do EJB3 i JSF 1.2 na JBB, ale idzie marnie, niestety. Czuję, że zaraz mi odbiorą tematy, bo wleczę się niemiłosiernie.

    Jacek

    OdpowiedzUsuń
  8. Nie ukrywam, że moja znajomość JSF oraz EJB3 na ten moment ma raczej charakter przelotnego romansu, niż miłości do grobowej deski. Nie śmiałbym zatem nawet patrzeć organizację objective-ów do tych egzaminów :) Jakkolwiek co nieco wiem na temat, więc jeśli potrzebowałbyś na pewnym etapie kogoś do pisania pytań, żeby przyśpieszyć proces stabilizacji tych egzaminów to daj znać - chętnie pomogę :)

    OdpowiedzUsuń