Javassa näkee aika useasti luokissa tämän tyylisen luokkamuuttujan:
private static final long serialVersionUID = 12345L;
Mikä tämä on ja mihin tätä käytetään?
lainaus:
The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an InvalidClassException.
Lyhyesti: Sitä käytetään deserialisoinnissa tunnistamaan, että puhutaan oikeasta luokasta. Liittyy siis osaksi Serializable-rajapintaa.
Esitin kysymyksen oikeastaan sen takia, että en ihan täysin ymmärrä, missä tätä muuttujaa pääsisi hyödyntämään. Käsitän Tritonin lainaaman tekstin jotenkin niin, että mikäli minulla on luokka tyyliin:
package fi.jotain.joo; import java.io.Serializable; public class JokinLuokka implements Serializable { private static final long serialVersionUID = 12345L; public jokinMetodi() {...
Sitten teen muutoksen luokkaan ja vaihdan serialVersionUID:ksi 123456L ja nyt luokan ladannut instanssi tajuaa, että luokka on vaihtunut, vaikka nimi onkin sama. Olenko oikeassa?
Entä missä käytännössä tällaista voisi käyttää. Jos teen jotain softaa, niin softanhan voi aina kääntää uudelleen, jolloin kaikki luokat käännetään ja ladataan uudelleen?
Asia ehkä selviää paremmin, jos mietit järkevästi, mitä serialisoinnilla edes tehdään.
Yhden ohjelman yhdellä ajokerralla ei ole järkeä serialisoida ja purkaa ohjelman sisäisiä olioita – vai keksitkö jotain järkeä? Ja tietenkin luokan versio on samassa ohjelmassa samalla ajokerralla jatkuvasti sama, eli ei siihen tarvita ylimääräistä muuttujaa.
Serialisoinnin tarkoitus on, että olioita voi vaikkapa tallentaa tiedostoon tai lähettää verkossa toiselle koneelle. Silloin serialVersionUID auttaa varmistamaan, että tiedoston lukija ja kirjoittaja tai verkossa lähettäjä ja vastaanottaja käyttävät luokasta (tai koko ohjelmasta) samaa versiota.
Osuit vahingossa naulan kantaan: kyllä, softan ”voi aina kääntää uudelleen, jolloin kaikki luokat käännetään ja ladataan uudestaan”, ja jos sitten yritetään ladata levyltä tai vastaanottaa verkosta vanhalla versiolla serialisoitua oliota, homma ei toimi.
Sanoisin, että järkevintä on jättää tuon serialVersionUID:n generointi JVM:een vastuulle - eikä itse määrittää sitä. Tuon pointti on siinä, että jos teet esim. client-server-softan, jossa esimerkiksi siirrät Message-olioita client- ja server-ohjelmien välillä, niin Java tietää, että molemmissa ohjelmissa olevat Message-luokat ovat yksi ja sama asia vertailemalla noita serialVersionUID:ta keskenään.
Edit. Näköjään suositellaan tuon serialVersionUID:n itse määrittämistä, jotta vältytään epäselvyyksiltä. Itse en ole tosin koskaan törmännyt ongelmiin, vaikka en sitä ole eksplisiittisesti määrittänyt.
serialVersionUID:n omalle luokalleen määrittämällä ikään kuin sitoutuu siihen, että suostuu deserialisoimaan vanhaakin serialisaatiodataa, jolla olisi muuten automaattigeneraation myötä eri UID, jos luokkaan on vaikkapa lisännyt uusia kenttiä. Siispä en suosittelisi määrittämään sitä itse, jos ei tällaista halua tukea.
Olen pyrkinyt sisäistämään tätä asiaa ja miettimään, missä tätä käytännössä tarvitaan. Ymmärrän sen nyt esimerkin valossa jotenkin näin:
Minulla on client-server tyyppinen sovellus, jonka server pää varastoi dataa olioon DataOlio.java ja lähettää sen clientille. Vastaavasti client-ottaa data olion vastaan, tekee sille temppunsa ja lähettää sen takaisin serverille.
Oletetaan, että client ja server pyörivät eri ympäristöissä. Nyt voi käydä esimerkiksi client-päässä niin, että mikäli serialVersionUID ei ole luokassa DataOlio.java määriteltynä, voi Javassa tapahtuva automaattigenerointi tehdä tepposet ja generoida DataOlio.javalle eri serialVersionUID:n kuin vastaavalle oliolle on server-päässä generoituna. Mikäli näin käy, saadaan aikaiseksi unexpected InvalidClassExceptions.
Määrittelemällä luokalle, tässä tapauksessa DataOlio.javalle, kiinteä serialVersionUID, voidaan varmistua siitä, että yllä mainittua ei pääse tapahtumaan ja client sekä server pystyvät käsittelemään samaa oliota, ilman unexpected InvalidClassExceptions ongelmia.
Olenko oikeilla jäljillä?
Kyllä. Javan dokumentaatiossa suositellaan arvon määrittämistä itse juurikin siksi, että eri ympäristöissä (JVM:n eri versioilla?) automaattinen generointi voi tuottaa eri tuloksia.
Toisaalta käsin määrittelyssä otat itse vastuun asiasta: jos muutat luokkaa ja unohdat muuttaa serialVersionUID:tä ja toiselle osapuolelle jää pyörimään vanha versio ohjelmasta, voi käydä hassusti.
Täällä on selitetty asiaa mielestäni aika havainnollisesti:
https://www.mkyong.com/java-best-practices/understand-the-serialversionuid/
Itse keksimässäni client-server esimerkissä saattaa tyypillisesti olla niin, että client-kone on Windows-kone, kun taas serveripään vehje on Linukka. Tällöinkin Javan generoima serialVersionUID saattaa olla erilainen.
Eräs asia jäi askarruttamaan mieltäni. Miksi itse generoitu serialVersionUID pitäisi olla hirveän monimutkainen. Eikö olisi järkevää käyttää esimerkiksi juoksevaa numerointia tyyliin 1,2,3... ja aina kasvataa sitä yhdellä, kun tehdään muutoksia?
koodiman kirjoitti:
Miksi itse generoitu serialVersionUID pitäisi olla hirveän monimutkainen. Eikö olisi järkevää käyttää esimerkiksi juoksevaa numerointia tyyliin 1,2,3... ja aina kasvataa sitä yhdellä, kun tehdään muutoksia?
Aivan. Esimerkit netissä ovat aika outoja, kun niissä on turhaan pitkiä lukuja. Jotkut kuvittelevat, että pitkä luku on jotenkin hienompi tai ”turvallisempi” tai jotain, mutta kyllä ykkösestä on hyvä aloittaa.
Metabolix kirjoitti:
Aivan. Esimerkit netissä ovat aika outoja, kun niissä on turhaan pitkiä lukuja. Jotkut kuvittelevat, että pitkä luku on jotenkin hienompi tai ”turvallisempi” tai jotain, mutta kyllä ykkösestä on hyvä aloittaa.
Todellako. Luulin pitkällä luvulla olevan jokin "syvempi" tarkoitus. Ehkä joskus kannattaisi luottaa omaan järkeensäkin. Javassahan on oma työkalunsakin: serialver, jolla voi generoida "hienon pitkän" luvun. Ilmeisesti työkalun tarkoitus on vain generoida aina varmasti eri luku ja siksi luvusta tulee tietenkin varsin pitkä.
koodiman kirjoitti:
Javassahan on oma työkalunsakin: serialver, jolla voi generoida "hienon pitkän" luvun. Ilmeisesti työkalun tarkoitus on vain generoida aina varmasti eri luku ja siksi luvusta tulee tietenkin varsin pitkä.
serialver palauttaa luokan nykyisen uid:n, joka on useimpien luokkien kohdalla se automaattisesti generoitu. Jos luokalla on ohjelmoijan määrittelemä kenttä serialVersionUID, komento palauttaa sen arvon. serialver:ia ei siis ole tarkoitettu miksikään pohjaksi itse määritellylle arvolle.
fergusq kirjoitti:
serialver palauttaa luokan nykyisen uid:n, joka on useimpien luokkien kohdalla se automaattisesti generoitu. Jos luokalla on ohjelmoijan määrittelemä kenttä serialVersionUID, komento palauttaa sen arvon. serialver:ia ei siis ole tarkoitettu miksikään pohjaksi itse määritellylle arvolle.
Tarkoitin serialverin käytöllä sitä, että olen nähnyt netissä ohjeita, jossa serialveriä käytetään serialVersionUID-arvon määrittämiseen. Toisin sanoen niin, että serialver ajetaan komentoriviltä jollekin luokalle ja sen jälkeen ohjelmoija kopioi serialverin generoiman arvon luokkaan. Tämän keskustelun valossa nuo ohjeet eivät vaikuta järkevältä, jos saman asian ajaa ohjelmoijan itse määrittelemä serialVersionUID-arvo.
Aihe on jo aika vanha, joten et voi enää vastata siihen.