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ä:

Type safety in Nice

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

(ei näy sivuilla)

(kirjoita kuvassa näkyvät merkit, pakollinen)

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


Kalenteri

Heinäkuu 2006
M T K T P L S
« Kes   Elo »
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Uusimmat kirjoitukset

Sivusto

If you would die right now, how would you feel about your life?
- Unknown