I februar skrev jeg et innlegg om hvordan du kunne sette opp et register i Java, som inneholdt for eksempel datastrukturen din, og som sørget for at dataen ble lagret ved programslutt. I dag tenkte jeg å utvide denne klassen litt, med generiske metoder.
import java.util.Map;
import java.util.HashMap;
public class Registry implements Serializable
{
private static final long serialVersionUID = 7422025143560909873L;
private static Map registry = new HashMap<>();
public static void addObject (String k, Object v)
{
this.registry.put (k, v);
}
public static T getObject (String key)
{
return (T) this.registry.get(key);
}
}
Her har jeg gjort følgende endringer:
Gjort klassen statisk, slik at du ikke behøver å opprette en instans av klassen før du benytter den.
Innført generisk metode for å hente frem et objekt, slik at du slipper å kaste om objektet når du henter det frem.
I praksis, så vil klassen fungere slik:
public class Main
{
public static void main (String[] args)
{
Registry.addObject ( "node", new Node("someName"));
// begge disse er like:
Node n = Registry.getObject ("node");
Node n2 = Registry.getObject ("node");
}
}
Noe ganske essensielt med et program er egenskapen å huske innstillinger til neste oppstart. Men hvordan skal vi få til dette? Skal vi bruke en database eller XML? Eller finnes det andre metoder?
I Java har vi en interface som heter «Serializable». Klasse som implementerer den, har muligheten til å bli lagret på disk til senere bruk ved bruk av ObjectOutputStream og ObjectInputStream.
Objektet vårt er nå lagret i filen myObject.ser, og vi kan hente det frem igjen slik:
try
{
FileInputStream fis = new FileInputStream("myObject.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
// myObj er nå av samme instans som i eksempelet ovenfor
MyObject myObj = (MyObject) ois.readObject();
ois.close();
}
catch (Exception e)
{
e.printStackTrace();
}
Som du kanskje ser, så er det ikke så veldig komplisert. Men hva dersom applikasjonen inneholder mange flere objekter? Å lagre alle objektene manuelt er både tidkrevende og unødvendig.
Vi kan nemlig anvende oss av programmeringsparadigmet Registry. Essensielt er det en klasse som inneholder alle andre objekter som brukes i en applikasjon. Registeret anvender også gjerne Singleton-mønsteret slik at objektene kan nås overalt.
Når vi har et register trenger vi bare lagre register-objektet. Alle objekter som måtte være i registeret vil da også bli lagret.
Et eksempel på et slik register:
class Registry implements Serializable
{
private static final long serialVersionUID = 7422025143560909873L;
private static final Registry instance = new Registry();
private HashMap registry = new HashMap();
private Registry ()
{ }
public static Registry getInstance ()
{
return instance;
}
public void addObject (String k, Object v)
{
this.registry.put (k, v);
}
public Object getObject (String k)
{
return this.registry.get (k);
}
}
Du har kanskje lagt merke til datafeltet serialVersionUID, og lurer på hva det er?
serialVersionUID er en universell versjonsindikator for en klasse som støtter serial-handling. Deserialiseringa bruker dette ID-nummeret for å sikre at klassen er i samsvar med det serialiserte objektet.
I praksis betyr dette at Java bruker serienummeret til å sjekke om det objektet du henter frem fra filen er i samsvar med det objektet du la inn. Det høres kanskje litt diffus ut fortsatt, men ta følgende scenario:
- Du oppretter et objekt av typen MyObject (som i kode #1), og lagrer dette til en fil
- I mellomtiden endrer du på klassestrukturen til MyObject, og legger til flere datafelt
- Når du henter frem objektet igjen, etter klasseendringene, hva tror du skjer?
Java kjenner nemlig ikke igjen klassene, og tror de er forskjellige. Du vil derfor få en feilmelding om at det er en ugyldig klasse. Dersom du hadde implementert serialVersionUID, hadde Java forstått at objektene likevel var like.
Verdien er unik per klasse, og blir generert av Java selv. For å finne ut hvilket nummer du skal legge inn i hver klasse, må du ta ibruk serialver-programmet (følger med Java-installasjonen).
Du åpner opp kommandolinjen og endrer mappe til hvor klassefilene dine ligger. Deretter utfører du kommandoen:
serialver Registry
Bytt ut Registry med hva klassenavnet er i ditt tilfelle.
Et eksempel på bruk
class Program
{
public static void main (String[] args)
{
Registry reg = Registry.getInstance();
reg.addObject ("myObj", new MyObject ("Something"));
reg.addObject ("foo", new MyObject ("bar"));
// lagre objektene ved avslutning
try
{
FileOutputStream fos = new FileOutputStream ("registry.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(reg);
oos.flush();
oos.close();
}
catch (IOException e)
{
e.printStackTrace();
}
// ... så kan vi hente dem frem igjen
try
{
FileInputStream fis = new FileInputStream("registry.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
reg = (Registry) ois.readObject();
ois.close();
}
catch (Exception e)
{
e.printStackTrace();
}
// do something..
MyObject myObj = (MyObject) reg.getObjet ("myObj");
}
}
Oppsummering
Alle klasser som skal lagres må implementere Serializable, og bør inneholdet serialVersionUID
Et felles register med alle objekter vil spare deg for tid og ressurser, for eksempel ved lagring av objekter
Når en jobber med store Java-prosjekter som inneholder mange klasse- og kildefiler, kan det være et mareritt å holde styr på alle disse. For ikke å snakke om å sørge for å kompilere alle, én etter én. Apache Ant ble laget for akkurat dette formålet, og har mange likhetstrekk med Make (for de som er kjent med Unix). Ant er et automatisert kompileringsverktøy som kan brukes for å kompilere hele Java-prosjekter med én kommando.
Programmet tar utgangspunkt i en konfigurasjonsfil som er formatert i XML, og styrer hvilke filer som kan kompileres og hvor klassefilene skal ligge. Du kan også velge om du skal opprette JAR-filer for hele prosjektet.
Sørg deretter for at du har en miljøvariabel kalt JAVA_HOME med verdien tilsvarende mappen som inneholder JDK. For eksempel slik: c:\Progra~1\Java\jdk1.7.0\
Du må også huske å inkludere C:\ant\bin\ i miljøvariabelen PATH.
Forberedelse
Opprett en helt enkelt Java-klasse med en main-metode, som du lagrer i en mappe kalt src.
Deretter oppretter du en XML-fil som du kaller build.xml med følgende innhold:
I starten trenger du ikke tenke på mer enn å endre «TestApp» til prosjektets navn, samt endre «main-class» til den klassen som inneholder main-metoden.
De ulike target-blokkene utgjør kommandoene du kan gjøre med Ant. For eksempel vil vi med denne filen ha følgende kommandoer tilgjengelige:
ant compile
ant jar
ant run
ant clean
Dersom du legger merke til linje nummer to, så står det blant annet default=»compile». Dette gjør at dersom vi kjører kommandoen:
ant
Så vil den kjøre compile-oppgaven.
Konfigurasjonsfilen skal lagres i prosjektmappen, slik at du ender opp med følgende mappestruktur;
TestApp
+ src/
| Test.java
+ build.xml
Gjennomføring
Nå er det lille prosjektet vårt klar til å kompileres.
Start opp kommandolinjen og endre mappe til prosjektet.
For å kompilere prosjektet, skriver du inn kommandoen:
ant
Denne kommandoen gjør oppgaven som er spesifisert under <target name=»compile»>.
Den oppretter mappen build\classes og lagrer klassefilene der.
For å lage en JAR-fil for videre distribusjon, utfører vi kommandoen:
ant jar
Denne er avhengig av at prosjektet først er kompilert, men dette sørger Apache Ant for å gjøre dersom du ikke har gjort det.
Det vil si at vi faktisk ikke trenger å utføre kommandoen ant compile på forhånd.
For å teste at JAR-filen fungerer, kan vi bruke kommandoen ant run.