czwartek, 13 listopada 2008

Name clash: has the same erasure but does not override it

Pisząc aplikacje wykorzystujące generyki, możecie natrafić na przedziwny błąd. Wyobraźcie sobie interfejs IDao (z pojedyncza metoda updateStubs(List<T>) oraz klasę, która go implementuje AbstractDao. I nagle kompilator krzyczy:

Name clash: The method updateStubs(List) of type AbstractDao<T> has the same erasure as updateStubs(List) of type IDao but does not override it.


Zaraz zaraz, metoda z klasy ma taką samantykę i erasure jak nadrzędna metoda z interfejsu, ale jej nie nadpisuje? O co chodzi?
Spójrzmy na początek definicji interfejsu:


public interface IDao<T extends IStub> {


a następnie na początek defnicji klasy AbstractDao:


public abstract class AbstractDao<T extends IStub> implements IDao {


Co jest nie tak? Otóż wprawna osoba odrazu zauważy (a nieroztropny [czytaj ja] programista przegapi) fakt, iż przy IDao brakuje odwołania do typu generycznego! Poprawnie powyższa definicja powinna wyglądać następująco:


public abstract class AbstractDao<T extends IStub> implements IDao<T> {


Po tej małej zmianie wszystko powino być już w porządku, a kompilator nie krzyczy, że ma metode w podklasie, ale jej nie nadpisze.

poniedziałek, 10 listopada 2008

Java Killers #009

Jakiś czas temu odezwał się do mnie dobry znajomy Paweł Badeński, który uhahany rozpoczął rozmowę na gadu z tekstem "Patrz na to: ..." po czym wkleił kawałek kodu. Kod przerzuciłem na szybko do ulubionego IDE i już wtedy wiedziałem, że te kilka lini ma predyspozycje na kolejny odcinek killersów. Problem pozostawał jednak wciąż, gdyż za diabła nie wiedziałem, czemu kod zachowywał się właśnie tak a nie inaczej. Szybkie przestudiowania Java Language Specification nie dało odpowiedzi, google na szybko też nie były takie cwane, jak zwykle. Dopiero po dłuższej chwili grzebania udało mi się dotrzeć do buga na bugs.sun.com, gdzie po dogłebnej lekturze i ponownym otworzeniu Java Language Specification, wszystko stało się jasne. Jestem ciekaw czy i dla Was dziewiąta odsłona Java Killers będzie taką samą łamigłówką jak była dla mnie. A oto ona:

Mając poniższy kod, jakiego spodziewamy się outputu:


public class Foo
{
public int loo(List<String> a)
{
return 1;
}

public double loo(List<Integer> a)
{
return -1;
}

public static void main(String[] args)
{
List list = new ArrayList<Integer>();
System.out.println("output: " + new Foo().loo(list));
}

}
Odpowiedź poprawna tylko jedna, a kilka do wyboru:


A. Klasa nie skompiluje się, javac poinformuje nas, że "name clash: loo(java.util.List) and loo(java.util.List) have the same signature and erasure"
B. Klasa nie skompiluje się, javac poinformuje nas, że "reference to loo is ambiguous"
C. Skompiluje się, ale wyrzucony zostanie RuntimeException po uruchomieniu programu
D. Program skompiluje się i uruchomi się bez wyjątków i zostanie wypisane na ekranie 'output: -1'
E. Program skompiluje się i uruchomi się bez wyjątków i zostanie wypisane na ekranie 'output: 1'



I uwaga uwaga, poprawna odpowiedź to B!

Osoby, które przekonane były, że oby dwie metody loo miały taką samą sygnaturę i przez to kod się nie kompilował, nie martwcie się, nie byliście jedyni, którzy zaznaczyli odpowiedź A. Otóż okazuje się, że jednak sygnatury obu metod się różnią i występuje najprostszy w świecie method overloading. Później w czasie wywołania metody loo w ciele metody main, następuje konsternacja, gdyż kompilator widzi zmienną niegenneryczną List list i nie ma pojęcia do której metody loo ma się odwołać, stąd jego krzyk
"reference to loo is ambiguous".
Ok, ktoś się spyta, jak to metody loo mają taką samą sygnaturę? Patrząc na Java Language Specification punkt 8.4.9, widzimy, że:

* obie metody mają taką samą nazwę
* obie metody mają taką samą liczbę formalnych parametrów oraz parametrów typów prostych
* ALE typy tych parametrów się różnią, List<String> to co innego niż List<Integer>! Mimo, że przed samą kompilacją wszystkie generyki zostają zrzucone i nie są brane pod uwagę podczas kompilacji (pisałem o tym w poprzednich killersach) o tyle są uwzględniane podczas operacji zwanej "type erasure" - dlatego właśnie List<String> to co innego niż List<Integer>.

No i to tyle na dziś. Kto znał poprawną odpowiedź?

PS. Wspomniana strona do której dotarłem (i musiałem się nieźle nagooglać) znajduje się tutaj: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6182950

środa, 5 listopada 2008

Maven javaee-api-5

Jeśli dane jest Wam pisać aplikacje JEE5 z użyciem mavena, dość paradoksalnie możecie się spotkać z brakiem w dostępnych repozytoriach pliku javaee-api-5.jar (definiującego API standardu JEE5). Jeśli więc kiedykolwiek dostaniecie informacje od mavena, że nie może za żadne skarby znaleźć API JEE5, polecam zajrzeć tutaj:

https://maven-repository.dev.java.net/nonav/repository/javaee/jars/

W repozytorium java.net jar z API standardu leży sobie grzecznie, czekając, aż go ktoś ściągnie.

wtorek, 4 listopada 2008

Zmiana portu JBoss Application Server z 8080 na inny

JBoss Application Server uruchamia się standardowo na porcie 8080. W odróżnieniu od Glassfisha nie ma admina webowego (co jest zastanawiające czemu) i żeby cokolwiek zmienić w konfiguracji, trzeba poedytować pliki konfigów.

W tym poście zakładam, że JBoss zainstalowany jest w katalogu /opt/jboss/. Aby zmienić port HTTP z 8080 na jakiś inny należy dostać się do pliku server.xml zlokalizownego w /opt/jboss/server/default/deploy/jboss-web.deployer/ i wyedtować go. Odnajdujem poniższy wpis i w miejscu XXX wpisujemy intersujący nas port (na przykład 80):


<Connector port="XXX" address="${jboss.bind.address}"
maxThreads="250" maxHttpHeaderSize="8192"
emptySessionPath="true" protocol="HTTP/1.1"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />

poniedziałek, 3 listopada 2008

Oracle Apex widoczny poza localhost'em

Ostatnio pisałem jak zainstalować Oracla na Ubuntu, dziś pora przyjrzeć się aplikacji webowej Apex (pozwalająca zarządzać bazą danych), która standardowo instaluje się wraz z Oraclem i dostępna jest pod adresem http://localhost:8080/apex. Wszystko działa fajnie, do czasu gdy chcielibyśmy dostać się do aplikacji przez inny adres niż http://localhost, czyli zdalnie. Poniżej krótka instrukcja jak to zrobić:

1. zainstaluj sqlplus jeśli (jeszcze nie masz):

apt-get install sqlplus

2. ustaw ORACLE_HOME (jesli jeszcze tego nie zrobiles):

ORACLE_HOME=/usr/lib/oracle/xe/app/oracle/product/10.2.0/server

3. zaloguj się do sqlplusa jako user SYSTEM

sqlplus system

4. gdy pojawi się sql prompt (ktoś zna polski odpowiednik słówka prompt?) wpisz:

SQL> EXEC DBMS_XDB.SETLISTENERLOCALACCESS(FALSE);


Powinno zacząć działać.

niedziela, 2 listopada 2008

Where is everybody?


"Where is everybody???" No właśnie, dobre pytanie. Blog trochę podumarł i przycichł. Czemu? Otóż zaczynamy z kolegami mały skromny start-up (o którym może kiedyś, jak już coś będzie wydane na świat) i przez ostatnie dwa miesiące poświęciłem się mu bezgranicznie. Teraz troszkę się już uspokoiło i ponownie mogę wrócić do zajęcia, które sprawia mi też sporo radości - blogowania.

Do zobaczenia niebawem, trochę się nazbierało dziwnych sytuacji, o których warto by tu napisać.

czwartek, 28 sierpnia 2008

Wyszedł pierwszy numer JAVA exPress!

Wyszedł pierwszy numer JAVA exPress, jakieś kilka godzin temu, a w polskiej blogosferze wciąż cicho (nawet odziwo na blogu głównego pomysłodawcy), więc pozwalam sobie poinformować o wydaniu pierwszego (i mam nadzieje nie ostatniego) numeru JAVA exPress, który w postaci pdf'a można za darmo sciągnąć stąd.
Coż można powiedzieć po wstępnym przejrzeniu gazetki? Widać, że zarówno pomysłodawca (Grzegorz Duda) jak i autorzy (Bartek Kuczyński, Jakub Jurkiewicz, Mariusz Sieraczkiewicz, Jackiw Pospychała oraz Marek Kliś) bardzo mocno się postarali, aby pierwszy numer wypadł ciekawie. Jeśli kolejne numery utrzymają formę, zapowiada się bardzo ciekawy zin w polskim światku javowym.
Boli tylko troszkę, że nie można takiej gazetki nigdzie kupić w formię drukowanej. Piękno przecież takich artykułów polega na tym, że można je sobie spokojnie poczytać w pociągu, samolocie czy też w domu leżąc wygodnie na kanapie. Niestesty na papierową wersję JAVA exPress trzeba będzie nam jeszcze sporo poczekać i narazie zadowolić się będzie trzeba SDJ (gdzie poziom artykułów zwykle bliżej dna niż powierzchni).

środa, 27 sierpnia 2008

Instalacja Oracle na Ubuntu

For English press http://paulszulc.wordpress.com/2009/05/25/installing-oracle-on-ubuntu/

Oracle to baza danych przez duże B i duże D. Wystarczy poczytać sobię trochę o architekturze Oracla, aby przekonać się czemu jest to jedna z najczęściej wykorzystywanych baz w biznesie. Dotychczas (na studiach :) ) wiedza o bazach danych była mi na tyle potrzebna, co założyć bazę, stworzyć strukture (tabele, widoki, indexy) i na końcu zmapować to wszystko w jakis ORM (Hibernate czy inny TopLink). Ponieważ w pracy wymagają ode mnie troszkę większej wiedzy na temat Oracla i baz danych w ogólności, postanowiłem po godzinach przyjrzeć się tej bazie u siebie na mojej lokalnej maszynie.
Aby to uczynić należało wpierw bazę tą zainstalować, co okazało się względnie prostym zadaniem na systemie Ubuntu (obecnie w wersji 8.04). Kolejne kroki w wersji skróconej podaje poniżej:

1. Dodać nowe repozytorium

1.1 Do listy należy dodać deb http://oss.oracle.com/debian unstable main non-free. Można to zrobić wyklikując System->SoftwareSources->Third-Party Software (jak na poniższym obrazku) lub dodać wpis ręcznie do /etc/apt/sources.list - jak kto woli.

1.2 Dodać klucz publiczny, najprościej w konsoli wpisując

wget http://oss.oracle.com/el4/RPM-GPG-KEY-oracle  -O- | sudo apt-key add -
ale można oczywiście również wyklikać przez System->SoftwareSources->Authentication

2. Uruchomić instalację Oracla

Wystarczy teraz już tylko standardowe:

sudo apt-get install oracle-xe

i po wykonanej instalacji uruchomić program konfigurujący:

sudo /etc/init.d/oracle-xe configure

gdzie odpowiada się na cztery proste pytania: o port dla Oracle Application Express (defaultowo 8080), port do połączeń z bazą danych (defaultowo 1521), hasło dla administratora (użytkownika SYSTEM) oraz określa się czy baza ma uruchamiać się wraz z uruchamianiem się systemu.

I to tyle! Mamy działającą instancję Oracla. Możemy zacząć zabawę z systemem uruchamiając SQL*Plus'a lub application express (http://127.0.0.1:8080/apex).

poniedziałek, 25 sierpnia 2008

java.util.Date vs java.sql.Date

Natknąłem się na ciekawe rozwiązanie architektoniczne w JDBC: obiekt java.sql.Date. Jest to obiekt, który dziedziczy bezpośrednio po java.util.Date, pozwalający nam mapować w naszych beanch pola typu Date z naszych baz danych.
Czy wszystko jest takie piekne? Nie do końca, gdyż w naszych beanach zwykle trzymamy obiekty typu java.util.Date.
Wyboraźmy sobie prosty przykład:

Mamy klase User w postaci

public class User
{
protected String userName;
protected Date birthDate; // tutaj java.util.Date czyli standard

//odpowiednie gettery i settery
}
Posiadamy również odpowiadającą jej tabelę w bazie danych:

CREATE TABLE Users (
userName VARCHAR2(150),
birthDate DATE );

Sytuacja jak najbardziej standardowa. Teraz jeśli wyciągnięte dane z bazy (przy pomocy ResultSeta) dodamy do naszego beana:

User u = new User();
u.setUserName(rSet.getString(1));
u.setBirthDate(rSet.getDate(2));

nic się nie stanie. W prawdzie metoda User.setBirthDate oczekuję java.util.Date, a ResultSet.getDate zwraca java.sql.Date, to jednak wszystko jest w porządku, gdyż java.sql.Date dziedziczy po java.util.Date. Jaja zaczynają się, gdy informacje chcemy ponownie przekazać do bazy danych.
Teraz gdy updateujemy jakies informacje przy pomocy jakies procedury skladowanej czy poprzez wywolanie SQL'owego UPDATE, zawsze ale to zawsze poprosza nas o Date w postaci java.sql.Date. Czyli kod
statement.setDate(2,user.getBirthDate());
sie po prostu nie skompiluje. Rzutowanie nic tu nie da, gdyż przykładowo poniższy kod:
user.setBirthDate(new Date());
statement.setDate(2,(java.sql.Date)user.getBirthDate()); // tu exception
się poprostu wywali przy pierwszym wywołaniu. Trzeba utworzyć explicite obiekt java.sql.Date, ale żeby nie było za prosto, nie ma żadnego konstruktora typu public Date(java.util.Date utilDate). Nie, przecież to by było zbyt proste. Zamiast tego musimy zrobic new java.sql.Date(utilDate.getTime()). Konstruktor java.sql.Date przyjmuje longa jako liczbę milisekund od roku 1970, a metoda getTime z java.util.Date taka informacje zwraca. Wszystko działa okej, ale czy na pewno to jest dobre rozwiązanie? Szczerze mówiąc, ja nie wiem. Skoro sami ludzie z Sun Microsystems mówią, że java.sql.Date to tylko "thin wrapper", to po co w ogóle został wprowadzony? Czy istnieje jakiś lepszy (w sensie nie polegający na roku 1970) sposób na konwertowanie dat z java.util na java.sql? Googlałem pobieżnie, nie znalazłem. Może ktoś zna?

środa, 20 sierpnia 2008

Po przerwie wakacyjnej

Minał okres wakacji i laby. Po obronie pracy magisterskiej nie mogłem przez jakiś czas dojść do siebie - w czym z pewnością usilnie pomagał mi alkohol ;). Potem wyjechałem ze znajomymi na długo wyczekiwany rejs po Mazurach (długi, bo aż ponad trzy tygodniowy). No a teraz..., teraz znów wróciłem do świata żywych i czas odświeżyć wiele spraw, między innymi tego bloga :)

Rozpocząłem prace w ifirma.pl, po moim długim maratonie po firmach IT we Wrocławiu. Zatrudnony jestem jako programista Java oraz PL/SQL, zatem spodziewać się można, że sporo zagadnień na blogu związanych będzie teraz z Oraclem. Obecnie w pracy rozgryzam sprawy związane z webserwisami, więc zapewne jakieś relacje z moich walk z Axis'em (implementacja z Apache'a) się tu pojawią.

Generalnie każdego dnia czegoś nowego się uczę, nierzadko na własnych błedach. Chciałbym aby ten blog był też zapiskiem z tych moich walk i pojedynków z technologiami, tak żeby w przyszłości mógł ktoś kiedyś z tego skorzystać.

Aby zatem nie przedłużać, w wielkim skrócie: I am back :)