Niesforny ClassCastException

Liczba odsłon: 107

Parę dni te­mu po­ma­ga­łem roz­wią­zać prob­lem, któ­ry nag­le po­ja­wił się w trak­cie dzia­ła­nia apli­kac­ji sie­cio­wej dzia­ła­ją­cej w śro­do­wis­ku Java EE. Ostatecz­nie oka­za­ło się, że źród­łem prob­le­mu by­ła kon­fi­gu­rac­ja ser­we­ra ba­zy da­nych. Jednym z tro­pów, ja­ki po­ja­wił się jed­nak w cza­sie śledz­twa by­ło kon­wer­to­wa­nie war­toś­ci nie­zgod­nych ty­pów, cze­go ob­ja­wem by­ło po­ja­wia­nie się w dzien­ni­kach zda­rzeń apli­kac­ji sytu­acji wy­jąt­ko­wej Class­Cast­Exception. I choć póź­niej do­wie­dzie­liś­my się, że był to tyl­ko ob­jaw, a nie źród­ło prob­le­mu, roz­poz­na­nie po­wo­du błę­du kon­wer­sji by­ło trud­niej­sze, niż moż­na się by­ło spo­dzie­wać.

Przyczy­ną ta­kie­go sta­nu rze­czy jest spryt­na opty­ma­li­zac­ja sto­so­wa­na przez ma­szy­nę wir­tu­al­ną Java oraz spo­sób, w ja­ki za­pi­su­je ona w dzien­ni­kach zda­rzeń ozna­cze­nia ty­pów pros­tych.

Aplikac­ja prze­sta­ła pra­co­wać po­praw­nie nad ra­nem, choć ta sa­ma wer­sja przez ca­ły po­przed­ni dzień dzia­ła­ła bez za­rzu­tu. Zasypała przy tym swo­je dzien­ni­ki zda­rzeń ra­por­ta­mi z wy­stę­po­wa­nia sytu­acji wy­jąt­ko­wej o na­stę­pu­ją­cej po­sta­ci:

java.lang.ClassCastException: null

Niestety, nie­wie­le to mó­wi­ło. Programis­ta Java spo­dzie­wa się po bi­blio­te­ce stan­dar­do­wej ra­czej dość jed­no­znacz­nych ko­mu­ni­ka­tów, opi­su­ją­cych przy­czy­nę prob­le­mu, a na­wet pod­po­wia­da­ją­cych spo­so­by roz­wią­za­nia. Tutaj za ca­łą in­for­mac­ję mu­sia­ło wy­star­czyć… null oraz zna­jo­mość kla­sy sytu­acji wy­jąt­ko­wej.

Okazuje się jed­nak, że nie stoi za tym błąd, lecz jed­na z tech­nik opty­ma­li­za­cyj­nych sto­so­wa­nych przez ma­szy­nę wir­tu­al­ną Java. Dopóki kod jest in­ter­pre­to­wa­ny, przy każ­dym wy­stą­pie­niu błę­du kon­wer­sji obiekt sytu­acji wy­jąt­ko­wej Class­Cast­Exception jest two­rzo­ny od no­wa i za­wie­ra peł­nię in­for­mac­ji o przy­czy­nie wy­stą­pie­nia. Gdy jed­nak ma­szy­na wir­tu­al­na stwier­dzi, że da­ny frag­ment pro­gra­mu jest „go­rą­cym punk­tem” (ang. hot spot) i prze­pro­wa­dzi je­go kom­pi­la­cję JIT, ko­lej­ne zgło­sze­nia tej sytu­acji wy­jąt­ko­wej bę­dą wy­ko­rzys­ty­wa­ły je­den wspól­ny, pre­de­fi­nio­wa­ny obiekt. Dzięki te­mu zmniej­sza się koszt cza­so­wy i pa­mię­cio­wy zgło­sze­nia stan­dar­do­wej sytu­acji wy­jąt­ko­wej, ak­cep­to­wal­ny w cza­sie, gdy kod apli­kac­ji jest in­ter­pre­to­wa­ny, lecz nie­pro­por­cjo­nal­nie dłu­gi, gdy na­stą­pi je­go kom­pi­la­cja do po­sta­ci ma­szy­no­wej.

Ta sa­ma opty­ma­li­zac­ja do­ty­czy kil­ku in­nych stan­dar­do­wych sytu­acji wy­jąt­ko­wych, w tym mię­dzy in­ny­mi sław­ne­go (lub nie­sław­ne­go…) Null­Pointer­Exception. Można ją wy­łą­czyć do­da­jąc do op­cji ma­szy­ny wir­tu­al­nej pa­ra­metr -XX:-Omit­Stack­Trace­In­Fast­Throw.

Jeżeli jed­nak ma­my moż­li­wość po­now­ne­go uru­cho­mie­nia apli­kac­ji, łat­wo jest do­pro­wa­dzić do sytu­acji, w któ­rej miej­sce wy­stą­pie­nia błę­du nie bę­dzie jesz­cze sta­no­wi­ło „go­rą­ce­go punk­tu” i sytu­acja wy­jąt­ko­wa Class­Cast­Exception zo­sta­nie zgło­szo­na w „zwyk­ły” spo­sób. Komunikat, któ­ry po­ja­wi się wte­dy ja­ko jej opis, w po­łą­cze­niu ze śla­dem reali­zac­ji pro­gra­mu po­wi­nien roz­wiać na­sze wąt­pli­wo­ści co do przy­czy­ny błę­du. Chyba, że na ekra­nie zo­ba­czy­my coś ta­kie­go:

java.lang.ClassCastException: [B cannot be cast to oracle.sql.STRUCT

Część ko­mu­ni­ka­tu po pra­wej stro­nie jest jas­na. Czym jed­nak jest [B? Otóż dla ty­pów pros­tych Java nie sto­su­je peł­nych nazw, lecz jed­no­li­te­ro­we skró­ty. Dodatkowo, w przy­pad­ku tab­lic, skrót jest po­prze­dza­ny na­wia­sa­mi kwad­ra­to­wy­mi, któ­rych licz­ba od­po­wia­da rzę­do­wi tab­li­cy. Poniższa ta­be­la pre­zen­tu­je skró­ty od­po­wia­da­ją­ce po­szcze­gól­nym ty­pom pros­tym.

SkrótNazwa ty­pu
Zboolean
Bbyte
Sshort
Cchar
Iint
Jlong
Ffloat
Ddouble

Kod [B to za­tem po pros­tu tab­li­ca baj­tów byte[].

Informacje, któ­re przed­sta­wi­łem w tym ar­ty­ku­le na pew­no nie są przy­dat­ne w co­dzien­nej pra­cy pro­gra­mis­ty ję­zy­ka Java. Warto jed­nak mieć po­ję­cie, dla­cze­go ko­mu­ni­kat po­wią­za­ny z obiek­tem sytu­acji wy­jąt­ko­wej mo­że być pu­sty, choć za­zwy­czaj wi­dzie­liś­my w nim przy­dat­ne in­for­mac­je, lub skąd bio­rą się dziw­ne, po­zor­nie nie­po­praw­ne naz­wy ty­pów da­nych.

Zaś tym, któ­rzy za­sta­na­wia­ją się, co ozna­czał od­szu­ka­ny po ty­lu tru­dach błąd kon­wer­sji byte[] na oracle.sql.STRUCT mo­gę pod­po­wie­dzieć, że win­na by­ła bi­blio­te­ka third-party auto­ma­tycz­nie ge­ne­ru­ją­ca treść za­py­tań SQL, któ­ra przy błęd­nej kon­fi­gu­rac­ji upraw­nień ba­zy da­nych po­wie­la­ła naz­wy ko­lumn i ocze­ki­wa­ła da­nych geo­met­rycz­nych w miej­scu, w któ­rym ba­za przy­sy­ła­ła iden­ty­fi­ka­tor wier­sza.