Java 5: Generics
Heinäkuu 20th, 2006
Näin Java 6:n beta-buildien kanssa pelatessa on hyvä muistella wanhoja.
Mielestäni Java 5:n tärkein uusi ominaisuus olivat ns. Genericsit [JSR-14]. Ne ovat etäistä sukua C++:n Template-mallille ja käytännössä niiden pitäisi siirtää castit, instanceofit ja niiden myötä myös ClassCastExceptionit historiaan. Ikävä kyllä niitä ei oikein tunnuta käytettävän missään, eikä vanhaa "raw types"-mallia estetty kääntäjän puolelta[1]. Siitäkin huolimatta kyseessä on erittäin merkittävä parannus kieleen.
Lisää Java-kielen perusheikkouksista (null pointerit, castit ja array-käsittely) voi lueskella vaikkapa täältä:
Niinpä ajattelin kirjoitella lyhyen esittelyn aiheesta. Ne, joille Genericsien käyttö on tuttua, voivat skipata loput.
Aloitetaan wanhalla casti-härdellillä. Sanotaan, että asiakas haluaa kehyksen, joka muuntaa ja käsittelee kaikkea mahdollista ja valitsee toteutuksen tilanteen mukaan.
Esimerkiksi jotain tällaista (Java 1.4):
public final class FunctionalList extends ArrayList
{
public FunctionalList() {}
public FunctionalList(final Collection params)
{
super(params);
}
public FunctionalList run(final Filter filter)
{
final FunctionalList results = new FunctionalList();
for (final Iterator i = this.iterator(); i.hasNext(); )
{
final Object obj = i.next();
if (filter.filter(obj)) results.add(obj);
}
return results;
}
public FunctionalList run(final Transformer transformer)
{
final FunctionalList results = new FunctionalList();
for (final Iterator i = this.iterator(); i.hasNext(); )
{
final Object obj = i.next();
results.add(transformer.transform(obj));
}
return results;
}
public FunctionalList run(final Processor processor)
{
for (final Iterator i = this.iterator(); i.hasNext(); )
{
processor.process(i.next());
}
return this;
}
}
Julmasti visuaalista roskaa, joka ei ole millään tavalla olennaista itse käsittelyn kannalta.
Yksikkötesti sisältäisi sitten jotain tällaista:
public void testFunctionalList()
{
String original = "dummy asset";
String transformed = "transformed asset";
filter.filter(original);
control.setReturnValue(true);
control.replay();
transformer.transform(original);
tcontrol.setReturnValue(transformed);
tcontrol.replay();
processor.process(transformed);
pcontrol.setReturnValue(true);
pcontrol.replay();
List processed = new FunctionalList(assets)
.run(filter)
.run(transformer)
.run(processor);
control.verify();
tcontrol.verify();
pcontrol.verify();
assertTrue(1 == processed.size());
assertEquals(processed.get(0), transformed);
}
Suurin ongelma tässä toteutuksessa on se, että rajapinnat (Filter, Transformer ja Processor) toteuttavien luokkien tulee tietää minkä luokan instansseja ollaan käsittelemässä. Tämä taas aiheuttaa melkoisella todennäköisyydellä paljon turhia bugeja ja debuggaus-istuntoja.
Generics-toteutus olisi paljon selkeämpi (Java 5):
import static java.util.Collections.addAll;
import java.util.ArrayList;
import java.util.Collection;
public final class FunctionalList<E> extends ArrayList<E>
{
public FunctionalList() {}
public FunctionalList(final Collection<E> params)
{
super(params);
}
public FunctionalList(final E... params)
{
addAll(this, params);
}
public FunctionalList<E> filter(final Filter<E> filter)
{
final FunctionalList<E> results = new FunctionalList<E>();
for (E obj : this)
{
if (filter.filter(obj))
{
results.add(obj);
}
}
return results;
}
public <T> FunctionalList<T> transform(final Transformer<E, T> transformer)
{
final FunctionalList<E> results = new FunctionalList<E>();
for (E obj : this)
{
results.add(transformer.transform(obj));
}
return results;
}
public FunctionalList<E> apply(final Processor<E> processor)
{
for (E obj : this)
{
processor.process(obj);
}
return this;
}
public <T> T inject(final Handler<E, T> handler, T lastResult)
{
for (E obj : this)
{
lastResult = handler.process(obj, lastResult);
}
return lastResult;
}
}
Sitten yksikkötesti Stringistä XML:ään:
@SuppressWarnings("boxing")
public void testFunctionalListWithDom4j()
{
final String
matti = "Matti Meikäläinen",
maija = "Maija Meikäläinen";
final FunctionalList<String> persons = new FunctionalList<String>
(
matti, maija, null
);
final Document transformed = DocumentFactory.getInstance()
.createDocument()
.addElement("person")
.addElement("name")
.addText(matti)
.getDocument();
final Transformer<Document, String>
xmlToStringTransformer = new Transformer<Document, String>()
{
public String transform(final Document person)
{
return person.selectSingleNode("/person/name").getText();
}
};
ctrl.reset();
expect(stringFilter.filter(matti)).andReturn(true);
expect(stringFilter.filter(maija)).andReturn(false);
expect(stringFilter.filter(null)).andReturn(false);
expect(stringToXmlTransformer.transform(matti)).andReturn(transformed);
expect(xmlProcessor.process(transformed)).andReturn(true);
ctrl.replay();
final List<Document> processed = persons
.filter(stringFilter)
.transform(stringToXmlTransformer)
.apply(xmlProcessor)
.transform(xmlToStringTransformer);
for (String name : processed)
{
System.out.println("Terve " + name + "!");
}
ctrl.verify();
assertTrue(processed.size() == 1);
assertEquals(processed.get(0), matti);
}
Aika paljon parempi. Nyt FunctionalList-luokan voi alustaa millä tahansa prosessoitavalla luokalla ja rajapintojen toteutusten ei tarvitse arvailla, että minkä luokan instansseja ollaan prosessoimassa:
public class StringLogger implements Processor<String>
{
private static final Logger logger = Logger.getLogger(StringLogger.class);
public boolean process(final String param)
{
if (logger.isDebugEnabled())
{
logger.debug(param);
}
return true;
}
}
Kirjoittelin noita esimerkkejä osittain ilman kääntämistä, joten typoja saattaa olla jonkun verran. Toivottavasti tämä kuitenkin avasi Genericsien perusteita jollekulle.
1) Java pyrkii olemaan loputtomiin alaspäin yhteensopiva
Artikkeli on luettu 612 kertaa. Kuuluu luokkiin: Java, Nice, Ohjelmointi
Jätä kommentti
Sallitut HTML-elementit:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
Trackback this post | Subscribe to the comments via RSS Feed