30 marca 2008

SCJP - Typy wyliczeniowe

Typy wyliczeniowe, czyli streszczenie sekcji "Declaring Enums" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć". Znowu przyjdzie mi jednak czytać specyfikację języka Java, jako że informacje w Książce są mocno nie kompletne.

Typy wyliczeniowe deklarujemy za pomocą słówka kluczowego enum bezpośrednio w pliku, w klasie, interfejsie albo wewnątrz innego typu wyliczeniowego, ale nie w metodzie. Typ wyliczeniowy, oprócz tego, że definiuje stałe wyliczeniowe może także zawierać konstruktory, zmienne i metody oraz klasy wewnętrzne i inne typy wyliczeniowe – prawie jak klasa, ale prawie robi ogromną różnicę.

Zacznijmy od stałych wyliczeniowych. Poniżej przykład deklaracji typu wyliczeniowego, który zawiera trzy takie stałe.

enum Size {
SMALL, LARGE, HUGE
}

Na tego typu deklaracjach kończy się wiedza wielu programistów, a to dopiero początek. Zacznijmy powoli, od modyfikatorów, jakich możemy użyć w deklaracji typu wyliczeniowego – dla deklaracji bezpośrednio w pliku są to tylko public oraz strictfp, a dla deklaracji wewnątrz innych typów dodatkowo private, protected i static. Ich znaczenie jest takie jak w przypadku klas i opisałem je w artykule "SCJP - Modyfikatory dla deklaracji klas". Idźmy dalej. Poniżej przykład deklaracji zawierającej stałe wyliczeniowe, metodę, zmienną i konstruktor. Zwróćmy uwagę na średnik po deklaracjach stałych wyliczeniowych – jest obowiązkowy, jeśli po tych deklaracjach znajduje się coś jeszcze. Zapamiętajmy też, że stałe wyliczeniowe muszą być zadeklarowane jako pierwsze, dopiero potem, po średniku następują kolejne deklaracje!

enum Size {
SMALL(1), LARGE(2), HUGE(3);

Size(int size) {
this.size = size;
}

public int getSize() {
return size;
}

private int size;
}

To, czym typy wyliczeniowe różnią się zdecydowanie od klas jest, że mogą one zawierać jedynie konstruktory prywatne. Nie jest możliwe utworzenie instancji typu wyliczeniowego w inny sposób, jak tylko poprzez deklarację stałej wyliczeniowej. Tak, stałe wyliczeniowe są takiego typu, w jakim je zadeklarowano i są to jedyne instancje tego typu. W powyższym przykładzie stałe Size.SMALL, Size.LARGE i Size.HUGE są zatem typu Size i są to jedyne instancje tego typu jakie kiedykolwiek będą występowały w przyrodzie, tj. w JVM – innych utworzyć się nie da, także używając refleksji czy operacji clone(). Oznacza to między innymi, że możemy używać operatora == zamiast operacji equals(). Mimo, że tak czy inaczej konstruktory typów wyliczeniowych są prywatne, możemy oznaczyć je słówkiem kluczowym private explicite. Jest to jedyny modyfikator, jakiego możemy użyć w deklaracji takiego konstruktora.

Zerknijmy teraz ponownie na deklarację stałych wyliczeniowych w powyższym przykładzie. Są to stałe i do dobrych praktyk należy, aby ich nazwy były pisane samymi wielkimi literami, ale nie jest to wymóg bezwzględny, może to być dowolny poprawny identyfikator – co to znaczy poprawny opisałem w artykule "SCJP - Poprawne identyfikatory i konwencje nazewnicze". Tuż po nazwie stałej możemy podać parametry dla wywołania konstruktora. Jeśli argumentów nie podano – tj. użyto samej nazwy, np. SMALL – to dla utworzenia instancji (stałej) wywoływany jest konstruktor bez-argumentowy. Tak jak w przypadku klas, możemy zdefiniować i użyć dowolny inny konstruktor.

Typy wyliczeniowe mogą implementować interfejsy, ale nie mogą dziedziczyć z innych typów wyliczeniowych. Istnieje jednak sposób na przedefiniowanie metod dla wybranych stałych wyliczeniowych, zupełnie jakby były one podtypami zawierającego typu wyliczeniowego. Po deklaracji stałej i jej ewentualnych parametrach dla konstruktora można umieścić ciało klasy anonimowej, która dla tej jednej instancji określi typ na bazie typu zawierającego. Spójrzmy na poniższy przykład.

enum Size {
SMALL {
public String getDescription() {
return "Taki calkiem maly";
}
},

LARGE {
public String getDescription() {
return "Dosyc duzy";
}
};

public abstract String getDescription();
}

Mimo, że nie możemy zadeklarować, że dany typ wyliczeniowy dziedziczy z innego typu, to jak najbardziej typy wyliczeniowe również należą do wspólnej hierarchii z klasą Object w korzeniu. Każdy typ wyliczeniowy E dziedziczy bowiem implicite z typu Enum<E>, a ten z typu Object. Oprócz metod odziedziczonych z Enum<E> każdy typ wyliczeniowy E ma zdefiniowane metody ‘public static E[] values()’ oraz ‘public static E valueOf(String name)’. Metoda values() zwraca tablicę stałych wyliczeniowych zdefiniowanych w typie E a metoda valueOf() służy do konwersji z typu String – zwraca instancję typu E (jedną ze stałych wyliczeniowych) o podanej nazwie.

Jeśli chodzi o deklaracje metod, to dozwolone są wszystkie te modyfikatory, co dla deklaracji w klasach, z jednym tylko obostrzeniem dla modyfikatora abstract – jeśli w typie wyliczeniowym zadeklarowano metodę abstrakcyjną, to musi być ona zdefiniowana przez każdą ze stałych wyliczeniowych w tym typie. Musi przy tym być zadeklarowana co najmniej jedna taka stała. Zmienne obowiązują te same zasady, co zmienne w klasach.

Było ciężko, ale się udało – zakończyliśmy niniejszym lekturę pierwszego rozdziału referowanej książki "SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055)", a to już mały sukces.

25 marca 2008

Adnotacje kontra XML

Byłem jakiś czas temu uczestnikiem dyskusji, a może nawet sporu, na temat tytułowy, czyli adnotacje czy XML? Koronnym argumentem tych, którzy optują za XML’em jest, że można dokonywać zmian bez rekompilacji kodu, ale ja powiadam, co z tego? Nie jest to argument sam w sobie, trzeba się jeszcze zastanowić właśnie nad tym - co z tego, że można? Co z tego, że w przypadku użycia adnotacji nie mogę dokonywać zmian bez rekompilacji kodu. Czy rzeczywiście jest to argument i czy zawsze? Nie twierdzę, że adnotacje są zawsze „lepsze”, ale często są łatwiejsze w użyciu, podczas gdy nie ma powodów by ich nie używać. Jak na wpis, którego celem jest polecenie lektury innego wpisu robi się już dużo tekstu, więc przechodzę do sedna - polecam lekturę artykułu "Annotations vs. XML" i czekam na komentarze.

22 marca 2008

SCJP - Deklaracja zmiennych i stałych

Deklaracja zmiennych i stałych, czyli streszczenie sekcji "Variable Declarations" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć".

Java definiuje dwa rodzaje zmiennych: zmienne typu prostego (ang. primitives) i zmienne będące referencjami (ang. reference variables). Jest 8 typów prostych, są to: char, boolean, byte, short, int, long, double oraz float. Referencje są de facto wskaźnikami na obiekty, ale nie na wszystkie obiekty a tylko na te zgodne z typem referencji. Jeśli więc zadeklarujemy referencję na obiekty klasy A, to może ona wskazywać tylko na obiekty klasy A oraz na obiekty klas pochodnych. W języku Java – w przeciwieństwie do np. C++ – wszystkie klasy uczestniczą jednak we wspólnej hierarchii dziedziczenia z klasą Object w korzeniu, tak więc możemy zadeklarować referencję do obiektów klasy Object, która będzie mogła wskazywać na obiekt dowolnego typu.

Zmienna typu boolean może przyjmować jedną z dwu wartości: ‘true’ albo ‘false’. Już to pisałem, ale przypomnę, że Java jest językiem tzw. case-sensitive, a więc ‘true’ to co innego niż ‘TRUE’ i co innego niż ‘True’ a wartością zmiennej typu boolean może być tylko ‘true’ albo ‘false’. Typ znakowy char reprezentowany jest na dwu bajtach i odpowiada jednemu znakowi UTF-16. Zmienne typów numerycznych zakodowane są bitowo w tak zwanej notacji uzupełniającej do dwu z najbardziej znaczącym bitem oznaczającym znak liczby (0 to plus a 1 to minus). Poniższa tabelka pokazuje liczbę bajtów użytą do reprezentacji poszczególnych typów.

byte

short

int

long

float

double

1

2

4

8

4

8


Zmienne można deklarować w czterech różnych kontekstach. Zmienne (nie statyczne) zadeklarowane w klasie to zmienne instancyjne. Jeśli do zmiennej zadeklarowanej w klasie dodamy modyfikator static to staje się ona zmienną klasową. W ciele metody możemy zadeklarować zmienną lokalną. Ostatnim sposobem na deklarowanie zmiennych są parametry metod. W zależności od tego, z którą z powyższych deklaracji mamy do czynienia możliwe jest zastosowanie innego zestawu modyfikatorów. Przypomnę jeszcze, że możemy zadeklarować (i zainicjalizować) kilka zmiennych w jednej deklaracji, poprawne są więc deklaracje tej postaci:


int a = 0, b, c = 2;

Integer i = 5, j;


Przyjrzyjmy się teraz bliżej zmiennym instancyjnym. Są to te zmienne, które zostały zadeklarowane bezpośrednio w klasie i nie zostały oznaczone jako static. Zmienne instancyjne są inicjalizowane na domyślne wartości w chwili tworzenia instancji (a zmienne klasowe w momencie wczytywania klasy), tak więc nie muszą być inicjalizowane explicite. Zmienne te mogą mieć każdy z czterech zakresów widoczności, a więc dozwolony jest każdy z modyfikatorów: public, protected i private. Modyfikator widoczności może być również pominięty i wtedy obowiązywał będzie zakres domyślny. Zakresy widoczności działają analogicznie jak dla metod, co opisałem w artykule "SCJP - Modyfikatory widoczności w deklaracji metod i zmiennych". Ze zmiennej takiej możemy zrobić stałą dodając modyfikator final. Możemy też użyć słówka kluczowego transient oraz volatile, o których za chwilkę. Legalne jest także użycie względem zmiennej zadeklarowanej w klasie słówka kluczowego static, ale oznacza ono właśnie tyle, że nie jest to zmienna instancyjna tylko klasowa. Zmienne klasowe mogą być oznaczone tymi samymi modyfikatorami, co zmienne instancyjne.

Modyfikator transient jest znaczący w kontekście serializacji. Zmienne instancyjne oznaczone jako transient są ignorowane, tj. nie są serializowane i ich stan nie jest odtwarzany przy deserializacji. Modyfikator volatile jest istotny w kontekście programów współbieżnych a jego znaczenie wymaga dobrego zrozumienia zagadnień współbieżności - zadanie to odkładamy na później. Póki co zapamiętajmy tylko, że modyfikator volatile nie może być użyty w parze z modyfikatorem final, innymi słowy - stała nie może być volatile.

Zmienne lokalne, to zmienne zadeklarowane w ciele metod. W przeciwieństwie do zmiennych instancyjnych nie są one inicjalizowane na domyślne wartości, tak więc to programista ma obowiązek przypisać jakąś wartość zanim zmienna zostanie użyta. Zmienne lokalne mogą być oznaczone jedynie modyfikatorem final, co ma ten skutek, że wartość takiej zmiennej nie będzie mogła być zmieniona – będzie to stała. Zmienne będące parametrami metody również mogą być oznaczone tylko jako final. Ze zmiennymi lokalnymi wiąże się jeszcze jedno zagadnienie - przysłanianie zmiennych. Jeśli parametr metody, albo zmienna lokalna ma tą samą nazwę (typ nie ma znaczenia), co zmienna zadeklarowana w klasie, to zmienna zadeklarowana w klasie jest przysłonięta, tj. nazwa odnosi się do zmiennej lokalnej lub parametru metody, a odwołanie się do zmiennej zadeklarowanej w klasie wymaga użycia operatora ‘this’. Poniżej przykład ilustrujący to zagadnienie – efektem uruchomienia metody jest wypisanie cyfry 5 a następnie napisu "Ola ma kota".

public class TestClass {
String var = "Ola ma kota";

void test() {
int var = 5;

System.out.println(var);
System.out.println(this.var);
}
}

Pewnym szczególnym przypadkiem zmiennych są zmienne typu tablicowego (ang. array). Tablice mogą być zarówno jedno jak i wielo wymiarowe. Deklarując tablicę, która będzie zawierała elementy danego typu dodajemy po nazwie typu albo po nazwie zmiennej nawiasy kwadratowe ‘[]’, przy czym zdecydowanie zaleca się umieszczać je po nazwie typu. Poniżej kilka przykładów poprawnych deklaracji tablic, przy czym pierwsze dwie deklaracje są zgodne z zaleceniami a kolejne, mimo że poprawne stanowią przykład deklaracji nie eleganckich.

String[] names;

boolean[] [] allowances;

int codes[];

String[] names[];

Dla tych, którzy zanim zaczęli swą przygodę z Javą, programowali w takich językach jak C czy C++ powiem jeszcze wprost, że w Javie nie jest poprawnym określenie rozmiaru tablicy w deklaracji, nie jest np. poprawną deklaracja ‘int[2] codes’. Deklaracja jest tylko deklaracją - pamięć na tablicę jest alokowana dopiero wówczas, gdy tworzony jest obiekt tablicy i to wówczas istotny jest jej rozmiar.

18 marca 2008

JAX-WS i Maven 2 w podejściu Contract First Development

Serwisy sieciowe, WebService’y, po przejściu fali marketingu i wyczerpaniu się na slajdach prezentacji odnajdują wreszcie swe nisze i zaczynają zyskiwać znaczenie praktyczne. Jedno jest pewne - o WebService’ach już nie tylko się mówi, coraz częściej przychodzi je zaimplementować. I pojawia się pytanie - jak to robić, żeby było – nie powiem, że łatwo jak na prezentacjach – ale chociaż znośnie. Próbowałem już wielu możliwości i śmiało mogę stwierdzić, że nie ma to jak JAX-WS (zwłaszcza 2.1) w parze z Maven 2. WebService’y z użyciem JAX-WS można implementować na dwa sposoby: zaczynając od klas Javy z odpowiednimi adnotacjami i na ich podstawie generując WSDL, albo na odwrót, zaczynając od WSDL’a i generując klasy. WSDL jest kontraktem, definicją interfejsu serwisu a podejście, w którym zaczynamy od wygenerowania klas na podstawie otrzymanego lub uprzednio napisanego WSDL’a nazywa się stąd, z angielskiego, Contract First Development. Tym właśnie stylem tworzenia serwisów sieciowych zajmę się dzisiaj.

Zakładamy więc, że mamy definicję serwisu w postaci pliku WSDL i naszym zadaniem jest implementacja takiego serwisu. Nasz WSDL (plik math.wsdl), definiujący operację dodawania wygląda następująco:

<definitions targetNamespace="http://jaxws.centric.pl/math"
xmlns:tns="http://jaxws.centric.pl/math"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:math="http://jaxws.centric.pl/math/domain">

<types>
<xsd:schema>
<xsd:import namespace="http://jaxws.centric.pl/math/domain"
schemaLocation="math.xsd"/>
</xsd:schema>
</types>

<message name="addRequestMsg">
<part name="addRequest" element="math:addRequest"/>
</message>

<message name="addResponseMsg">
<part name="addResponse" element="math:addResponse"/>
</message>

<portType name="mathService">
<operation name="add">
<input message="tns:addRequestMsg"/>
<output message="tns:addResponseMsg"/>
</operation>
</portType>

<binding name="soapBinding" type="tns:mathService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="add">
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>

<service name="mathServiceProxy">
<port name="mathService" binding="tns:soapBinding">
<soap:address location="http://localhost:8080/mathService"/>
</port>
</service>
</definitions>

Fakt, plik jest dosyć długi, ale tak to już być musi, a doskonałą przecież wymówką jest, że język WSDL nie był tworzony z myślą o tym by być czytelnym dla ludzi. Co więcej, kto zerknął na powyższego WSDL’a zauważył, że brakuje w nim definicji typów. Owszem, zostały one zdefiniowane w osobnym pliku XML Schema a w WSDL’u tylko ten plik wskazujemy. Oto więc brakująca definicja typów (plik math.xsd):

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://jaxws.centric.pl/math/domain"
xmlns:tns="http://jaxws.centric.pl/math/domain"
elementFormDefault="qualified">

<xsd:element name="addRequest" type="tns:addRequestType"/>

<xsd:complexType name="addRequestType">
<xsd:sequence>
<xsd:element name="a" type="xsd:double" />
<xsd:element name="b" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>

<xsd:element name="addResponse" type="tns:addResponseType"/>

<xsd:complexType name="addResponseType">
<xsd:sequence>
<xsd:element name="c" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

No dobrze, mamy już kontrakt, zatem przystępujemy do generowania klas. W tym celu utworzymy projekt Maven 2. Nie chcę zbyt dużo czasu poświęcić na podstawy Maven’a więc powiem krótko - tworzymy projekt główny, tj. typu pom o nazwie math i konfigurujemy go w następujący sposób (plik pom.xml):

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math</artifactId>
<packaging>pom</packaging>
<version>1.1</version>
<name>math</name>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<encoding>US-ASCII</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>math-schema</module>
<module>math-ws</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<artifactId>math-schema</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<pluginRepositories>
<pluginRepository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>maven-repository.dev.java.net</id>
<url>http://download.java.net/maven/1/</url>
<layout>legacy</layout>
</repository>
<repository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2/</url>
</repository>
</repositories>
</project>

Teraz tworzymy projekt potomny typu jar o nazwie math-schema. Jedynym zadaniem tego podprojektu będzie generowanie na podstawie WSDL’a i XML Schema’y odpowiednich klas, kompilowanie ich i pakowanie do postaci pliku .jar. Plik pom.xml tego projektu poniżej:

<project>
<parent>
<artifactId>math</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math-schema</artifactId>
<name>math-schema</name>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<xdebug>true</xdebug>
<verbose>true</verbose>
<target>2.0</target>
</configuration>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

Kluczowe jest tutaj wykorzystanie wtyczki (ang. plugin) jaxws-maven-plugin - https://jax-ws-commons.dev.java.net/jaxws-maven-plugin/, to ona generuje klasy i w jej dokumentacji powinniśmy szukać informacji, gdy chcemy zmodyfikować standardowe ustawienia. W katalogu src projektu math-schema tworzymy podkatalog wsdl i kopiujemy tam pliki WSDL i XML Schema, czyli w naszym przypadku math.wsdl i math.xsd. Jest to domyślna lokalizacja, w której wtyczka jaxws-maven-plugin poszukuje definicji serwisów, dla których ma wygenerować klasy.

Utwórzmy teraz podprojekt typu war o nazwie math-ws. Będzie to projekt, w którym zaimplementujemy nasz serwis. Oto plik pom.xml dla tego projektu:

<project>
<parent>
<artifactId>math</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math-ws</artifactId>
<name>math-ws</name>
<packaging>war</packaging>
<dependencies>
<dependency>
<artifactId>math-schema</artifactId>
<groupId>pl.centric.jaxws</groupId>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

Ponieważ mamy do czynienia bądź, co bądź z aplikacją internetową (ang. web application) nie obędzie się bez pliku web.xml, oto on:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" />

I wreszcie implementacja serwisu, tj. interfejsu wygenerowanego przez narzędzie jaxws-maven-plugin. Oto ona:

package pl.centric.jaxws.math.service;

import javax.jws.WebService;

import pl.centric.jaxws.math.MathService;
import pl.centric.jaxws.math.domain.AddRequestType;
import pl.centric.jaxws.math.domain.AddResponseType;

@WebService(endpointInterface="pl.centric.jaxws.math.MathService")
public class MathServiceBean implements MathService {

public AddResponseType add(AddRequestType addRequestType) {
AddResponseType addResponseType = new AddResponseType();

addResponseType.setC(addRequestType.getA() + addRequestType.getB());

return addResponseType;
}
}

Budujemy projekt i przystępujemy do testów. Zaczynamy od serwera GlassFish V2. Do testów używam narzędzia soapUI - http://www.soapui.org/. Prosty test pokazuje, że działa.




Ale na tym nie koniec, sprawdzamy jeszcze czy zadziała na innych serwerach. W końcu się przekonałem i wziąłem do ręki Apache Geronimo - http://geronimo.apache.org/ - w wersji 2.1. Działa bez zarzutu a serwer robi dobre wrażenie, zwłaszcza szybkość działania konsoli. Co prawda dostajemy ostrzeżenie, że nie znaleziono pliku geronimo-web.xml i może to skutkować błędami, ale nic w tym dziwnego jako że rzeczywiście takiego pliku nie ma. Błędów też nie ma. Na koniec sprawdzam jeszcze na serwerze BEA WebLogic Server 10.2. Działa i tu, co nie koniecznie musiało mieć miejsce. O ile moje doświadczenia z GlassFish’em są takie, że łyknie wszystko, co tylko można to z WLS’em jest już zupełnie na odwrót – wszystko musi być idealnie. Jak mamy WLS’a to mamy też konsolę do testowania WebService’ów, zamiast soapUI używam więc tejże konsoli. Wygląda to tak jak poniżej.


12 marca 2008

SCJP - Deklaracja konstruktora

Deklaracja konstruktora, czyli streszczenie sekcji "Constructor Declarations" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć". Tym razem wybiegnę jednak zakresem wiedzy sporo poza Książkę, będę się wspomagał specyfikacją języka Java.

Zacznijmy od tego, że każda klasa ma konstruktor. Jeśli nie zdefiniowaliśmy konstruktora explicite to jest on generowany przez kompilator, jest to tak zwany konstruktor domyślny. Konstruktor ten generowany jest tylko wtedy, gdy nie zdefiniowano żadnego explicite. Domyślny konstruktor, to konstruktor bezparametrowy. Jedyne, co robi konstruktor domyślny wygenerowany przez kompilator to wywołanie konstruktora bezparametrowego klasy nadrzędnej, a więc instrukcja ‘super()’. Jeśli chcemy zmienić to zachowanie to możemy konstruktor domyślny po prostu jawnie zaimplementować. Pytanie sprawdzające – czy poprawnym jest kod:

public class NewClass {
private int x;

public NewClass(int x) {
this.x = x;
}
}

public class OtherClass extends NewClass {
}

Odpowiedź brzmi NIE. Dla klasy OtherClass nie zdefiniowano żadnego konstruktora a więc kompilator wygenerował konstruktor domyślny. Konstruktor domyślny – jak napisałem powyżej – zawiera wywołanie ‘super()’. Dla klasy NewClass zdefiniowano konstruktor jednoargumentowy, więc kompilator nie wygenerował konstruktora domyślnego, nie został też taki konstruktor zdefiniowany explicite a więc wywołanie ‘super()‘ nie jest prawidłowe.

Konstruktor może mieć dowolną listę parametrów, która jest legalna dla zwykłych metod. Konstruktor deklarujemy generalnie tak jak metodę, tyle, że nie podajemy zwracanego typu - jeśli określimy typ to jest to już metoda a nie konstruktor. Konstruktor musi się nazywać dokładnie tak jak klasa, ale uwaga, metoda także może się nazywać dokładnie tak jak klasa. Zanim więc orzekniemy, że dana deklaracja jest konstruktorem – sugerując się nazwą – zwróćmy uwagę czy zadeklarowany jest zwracany typ.

Jeśli chodzi o modyfikatory, to dozwolone są wszystkie modyfikatory widoczności, jak i brak modyfikatora widoczności, co oznacza zasięg domyślny. Konstruktory nigdy nie są dziedziczone. Znaczenie modyfikatorów widoczności, z dokładnością do dziedziczenia, jest takie jak dla metod i opisałem je w artykule "SCJP - Modyfikatory widoczności w deklaracji metod i zmiennych". Inaczej niż dla metod, dla konstruktorów nie są dozwolone żadne inne modyfikatory. Jeszcze tylko jeden komentarz co do widoczności. Zdawałoby się, że skoro konstruktory nie podlegają dziedziczeniu, to nie ma różnicy między zakresem protected i zakresem domyślnym. Poniższy przykład pokazuje, że jednak taka różnica jest. Jeśli poniższe klasy są w różnych pakietach, to kod jest poprawny dla konstruktora protected, a dla zakresu domyślnego już nie.

public class NewClass {
protected NewClass() {
}
}

public class OtherClass extends NewClass {
public OtherClass() {
super();
}
}

11 marca 2008

SCJP - Deklaracje metod

Deklaracje metod, czyli streszczenie sekcji "Nonaccess Member Modifiers" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć".

Modyfikatory widoczności w kontekście metod opisałem w artykule "SCJP - Modyfikatory widoczności w deklaracji metod i zmiennych", zatem znaczenie słów kluczowych private, public i protected w tym kontekście mamy już wyjaśnione. Pozostało jeszcze jednak kilka modyfikatorów których możemy użyć w deklaracji metod, są to: final, abstract, synchronized, native, strictfp oraz static.

Metoda abstrakcyjna, tj. zadeklarowana jako abstract to metoda nie posiadająca implementacji. Jeśli klasa zawiera choć jedną metodę abstrakcyjną to sama także musi być zadeklarowana jako abstract – więcej na ten temat w artykule "SCJP - Modyfikatory dla deklaracji klas". Jeśli metoda jest zadeklarowana jako abstract to jednocześnie może być zadeklarowana tylko jako public lub protected, modyfikator abstract nie może być użyty w parze z żadnym innym modyfikatorem. Przy okazji uświadommy sobie jak to jest z metodami i klasami abstrakcyjnymi w świetle dziedziczenia. Załóżmy, że klasa A jest klasą abstrakcyjną i zawiera abstrakcyjne metody a() i b(). Jeśli klasa B dziedziczy z klasy A, to są dwie możliwości: albo klasa B również jest abstrakcyjna i wtedy może implementować dowolną, obie albo żadną z metod a() i b(), albo klasa B nie jest abstrakcyjna i wtedy musi implementować zarówno a() i b(). Ogólna zasada jest taka, że jeśli klasa nie abstrakcyjna dziedziczy z klasy abstrakcyjnej to musi implementować wszystkie abstrakcyjne metody odziedziczone z tej nadklasy.

Modyfikator final oznacza, że metoda nie będzie mogła być przedefiniowana w podklasie. Generalnie, modyfikator final jest po to właśnie, aby zagwarantować sobie, że cokolwiek zostało raz zdefiniowane i oznaczone jako final nigdy nie będzie się mogło zmienić. Zmiana w kontekście metody oznacza właśnie przedefiniowanie w podklasie. Ale w kontekście deklaracji metod modyfikator final pojawia się raz jeszcze. Otóż modyfikatorem tym możemy także oznaczyć parametr formalny metody i ma to ten skutek, że do zmiennej lokalnej określonej tym parametrem nie będzie można przypisać żadnej wartości. Nawiasem mówiąc, final jest jedynym modyfikatorem, jakim można oznaczyć zmienne lokalne, a więc i parametry metod. I jeszcze przypomnienie - tak jak już napisałem powyżej, modyfikator final nie może wystąpić razem z modyfikatorem abstract.

Metodę statyczną, nazywaną także metodą klasową, deklarujemy używając słówka kluczowego static. Jak już napisałem, modyfikator static nie może wystąpić w parze z modyfikatorem abstract.

Słówko kluczowe synchronized wskazuje, że oznaczona nim metoda może być wykonywana w danym czasie co najwyżej przez jeden wątek. Mówiąc precyzyjniej, metoda taka działa jak monitor (pojęcie z programowania współbieżnego, nie chodzi o monitor w sensie ekranu). Jeśli metoda jest statyczna to monitor ten synchronizuje się na obiekcie klasy a jeśli nie to na obiekcie, dla którego metoda została wywołana. Naturalnie, słówko kluczowe synchronized nie może wystąpić w parze ze słówkiem abstract.

Modyfikator native oznacza, że metoda została zaimplementowana w sposób charakterystyczny dla danej platformy, czyli najpewniej przy użyciu innego języka programowania, np. C czy C++. Autorzy Książki twierdzą, że na potrzeby egzaminu SCJP nie trzeba wiedzieć, jak implementować tego typu metody, wystarczy znajomość hasłowa. Trzeba zatem wiedzieć, że deklarując metodę jako native kończymy jej deklarację średnikiem, zupełnie tak jakbyśmy deklarowali metodę abstrakcyjną. Jakże mogłoby być inaczej, w końcu nie będziemy przecież podawać implementacji w języku C. Naturalnie, metoda oznaczona modyfikatorem native nie może być jednocześnie abstract, ale uwaga! Nie może być także strictfp, choć tą informację znalazłem w specyfikacji języka, w Książce jej nie widziałem. Jest natomiast w Książce podkreślone dla lepszego uświadomienia, że modyfikator native może być użyty tylko i wyłącznie w deklaracji metody. W deklaracji klasy czy zmiennej nie! Tylko metody!

O strictfp już pisałem, ale przypomnę. Modyfikator strictfp oznacza, że wszystkie operacje zmiennoprzecinkowe w danej metodzie będą zgodne ze standardem IEEE 754, tj. wirtualna maszyna Javy wykona instrukcje zmiennoprzecinkowe w sposób zgodny z tym standardem. Modyfikator ten może być zastosowany przy deklaracji metody oraz klasy, ale nigdy przy deklaracji zmiennej. Jeśli klasa jest zadeklarowana jako strictfp to oznacza to tyle, że wszystkie jej metody oraz klasy zagnieżdżone również są strictfp. Także zmienne zadeklarowane w klasie są inicjalizowane zgodnie z arytmetyką strictfp (choć same zmienne nie mogą być strictfp). Modyfikator strictfp nie może być użyty razem z modyfikatorem abstract ani native.

I jeszcze jedna rzecz dotycząca deklaracji metod – metody ze zmienną liczbą parametrów (ang. var-args). Począwszy od Javy 5 możemy zadeklarować, że metoda akceptuje dowolną liczbę argumentów pewnego ustalonego typu, przy czym dopuszczone są wszystkie typy, zarówno prymitywne jak i obiektowe. Możemy więc zadeklarować ‘fun(int… x)’ co będzie oznaczało, że metoda akceptuje dowolną liczbę argumentów typu ‘int’. Sładnia jest właśnie taka, że po nazwie typu umieszczamy 3 kropeczki, po czym podajemy nazwę zmiennej, która de facto będzie tablicą. Metoda może akceptować także inne parametry, czyli poprawną jest deklaracja ‘fun(char a, long… x)’. Ważne jest, że metoda może zawierać tylko jeden parametr tego rodzaju i że musi on być zadeklarowany na końcu listy parametrów. Poniższy kod ilustruje to zagadnienie.

public class AnyClass {
void testFun() {
fun("any", 1, 2, 3, 4);
}

void fun(String str, Integer... ints) {
System.out.println(str + ints.length);

for (Integer x : ints) {
System.out.println(x);
}
}
}

9 marca 2008

SCJP - Modyfikatory widoczności w deklaracji metod i zmiennych

Modyfikatory widoczności w deklaracji metod i zmiennych, czyli streszczenie sekcji "Access Modifiers" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć".

Metody i zmienne w deklaracji klas mogą mieć każdy z czterech zakresów widoczności. Trzy zakresy widoczności odpowiadają trzem słowom kluczowym: public, protected i private a czwarty to zakres domyślny, który obowiązuje, jeśli nie określimy żadnego z powyższych modyfikatorów.

Przyszła pora na to, by zastanowić się dokładniej nad tym, co to właściwie oznacza "widoczność". Otóż ma ona dwa aspekty. Po pierwsze interesuje nas, czy da się odwołać do metody lub zmiennej klasy za pośrednictwem jej instancji i po drugie, czy da się do nich odwołać z kodu klasy pochodnej, a więc czy podlegają dziedziczeniu. Poniższy kawałek kodu ilustruje to zagadnienie.

public class Parent {
protected int x = 5;
}

public class Child extends Parent {
int parentInstanceAccess() {
Parent parent = new Parent();

// poniższa instrukcja nie zawsze jest poprawna
return parent.x;
}

int inheritanceAccess() {
return x;
}
}

Tak jak zasygnalizowałem w komentarzu, powyższy kod nie zawsze jest poprawny, a to czy jest zależy od tego czy obie klasy znajdują się w tym samym pakiecie. Więcej na ten temat za parę zdań, póki co ważne jest żeby zrozumieć dwojakość pojęcia widoczności.

Zmienne i metody oznaczone jako private są widoczne tylko i wyłącznie w klasie, która je definiuje i są widoczne zarówno przy dostępie "lokalnym" jak i dostępie za pośrednictwem innej instancji tej klasy. Zmienne i metody prywatne nie są dziedziczone. Poniżej przykład poprawnego kodu ilustrującego zagadnienie.

public class SomeClass {
private int x = 5;

int instanceAccess() {
SomeClass instance = new SomeClass();

return instance.x;
}

int thisAccess() {
return x;
}
}

Jeszcze słówko komentarza, aby ułatwić zrozumienie zagadnienia. Instrukcja ‘return x’ to de facto instrukcja ‘return this.x’ gdzie ‘this’ to nic innego jak wskaźnik na instancję naszej klasy dla której wywołano metodę. Instrukcja ‘return instance.x’ jest więc z punktu widzenia widoczności tym samym co ‘return this.x’ - obie te instrukcje odwołują się do tej samej zmiennej poprzez instancję tej samej klasy i w tej samej klasie.

Zmienne i metody oznaczone jako public są widoczne mówiąc najogólniej wszędzie. Oczywiście trzeba jeszcze rozważyć, czy klasa, która definiuje taką metodę lub zmienną też jest widoczna - jeśli nie, to naturalnie metoda czy zmienna też nie jest. Metody i zmienne publiczne są dziedziczone i nie ma tu żadnych udziwnień.

Zmienne i metody nieoznaczone żadnym modyfikatorem widoczności, tj. o widoczności domyślnej są widoczne tylko w klasach z tego samego pakietu, co klasa zawierająca deklaracje. Takie metody i zmienne są również dziedziczone tylko przez klasy z tego samego pakietu.

Z modyfikatorem protected jest najciekawiej – tj. najtrudniej, – więc zostawiłem go sobie na koniec. Modyfikator ten jest rozszerzeniem domyślnego zakresu widoczności w tym sensie, że również powoduje ograniczenie widoczności do pakietu, ale ograniczenie to nie dotyczy klas, które z klasy deklarującej metodę lub zmienną protected dziedziczą. Jeśli więc klasa A deklaruje metodę o widoczności protected to metoda ta jest dostępna w klasie B tylko i wyłącznie wtedy, gdy klasa B znajduje się w tym samym pakiecie albo, gdy dziedziczy (wprost lub przechodnio) z klasy A. Zakończmy przykładem obrazującym to zagadnienie.

public class Parent {
protected int x = 5;
}

public class Child extends Parent {
int parentInstanceAccess() {
Parent parent = new Parent();

// działa tylko, jeśli klasy są w tym samym pakiecie
return parent.x;
}

int thisInstanceAccess() {
Child child = new Child();

// to zawsze jest ok
return child.x;
}

int inheritanceAccess() {

// zawsze ok
return x;
}
}

6 marca 2008

SCJP - Deklaracja interfejsu

Deklaracja interfejsu, czyli streszczenie sekcji "Declaring an Interface" oraz "Declaring Interface Constants" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć". Pisząc te rozdziały Książki autor miał najwyraźniej słaby dzień. Napisane są dosyć chaotycznie i wyjątkowo nie precyzyjnie, będę się więc posiłkował specyfikacją języka Java - "The Java Language Specification". Zaznaczę jeszcze, czego zapomniałem zrobić ostatnio pisząc o klasach, że pomijamy póki co interfejsy zagnieżdżone (ang. non top-level), do nich dojdziemy później. Pomijamy także adnotacje, które również są specjalnym rodzajem interfejsów.

Podobnie jak w przypadku klas, interfejsy (nie zagnieżdżone) możemy oznaczać modyfikatorem widoczności public, lub nie używać w ogóle modyfikatora widoczności – interfejs będzie wówczas widoczny tylko w swoim pakiecie. Modyfikatory private oraz protected nie są dozwolone, bo też nie mają w tym kontekście sensu.

Interfejs możemy także oznaczyć słówkiem kluczowym abstract, jednak - choć jest poprawne - nie ma to żadnego efektu i nie powinniśmy tego robić. Każdy interfejs i tak jest abstrakcyjny.

Ostatnim modyfikatorem, jaki możemy zastosować dla interfejsu nie zagnieżdżonego jest strictfp. Stałe zmiennoprzecinkowe wyliczane w czasie kompilacji są zawsze wyliczane zgodnie z zasadami strictfp, tak więc modyfikator ten ma bodaj tylko ten skutek, że propaguje się na klasy zdefiniowane wewnątrz interfejsu.

Dodam jeszcze, choć chyba jest to jasne dla tych, którzy programują w Javie, a nie tylko przygotowują się do SCJP, że interfejsy są sposobem, aby skorzystać z wielodziedziczenia. Zarówno interfejs może dziedziczyć (słówko kluczowe extends) z wielu interfejsów jak i klasa może implementować (słówko kluczowe implements) wiele interfejsów. Zdaje się zresztą, że właśnie po to wprowadzono interfejsy do języka Java, aby umożliwić bezproblemowe użycie wielodziedziczenia (choć pewne problemy nadal pozostają).

Interfejs może zawierać deklaracje metod abstrakcyjnych, stałych, klas oraz innych interfejsów. Deklarując w interfejsie metodę nie podajemy żadnych modyfikatorów. Co prawda kod skompiluje się jeśli podamy explicite modyfikatory public albo abstract, ale nie mają one żadnego skutku, jako że tak czy inaczej wszystkie metody zadeklarowane w interfejsie są oznaczone tymi modyfikatorami implicite. Specyfikowanie tych modyfikatorów jest w złym stylu i nie należy tego robić – tak mówi specyfikacja języka Java 5. Deklarując metodę w interfejsie nie możemy zastosować żadnego innego modyfikatora, ale już implementując zadeklarowane metody w klasie możemy dodać modyfikatory final, strictfp lub native, dotyczą one bowiem implementacji a nie samego kontraktu.

Każda zmienna zadeklarowana w interfejsie musi być de facto stałą i jest implicite public, static oraz final (chociaż wydaje mi się, że stała nie publiczna też mogłaby mieć tu sens). Możemy podać dowolną kombinację tych modyfikatorów explicite, jednak nie ma to żadnego skutku – tak czy inaczej one tam są i nie da się tego zmienić. Nie są dozwolone żadne inne modyfikatory.

Klasy oraz interfejsy zadeklarowane wewnątrz deklaracji interfejsu są implicite oznaczone jako static i public. Typy zagnieżdżone są jednak poza zakresem dyskusji tego artykułu – dojdziemy do nich później.