Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deduplicate this java code duplication

I have about 10+ classes, and each one has a LUMP_INDEX and SIZE static constant. I want an array of each of these classes, where the size of the array is calculated using those two constants. At the moment i have a function for each class to create the array, something along the lines of:

private Plane[] readPlanes()
{
    int count = header.lumps[Plane.LUMP_INDEX].filelen / Plane.SIZE;
    Plane[] planes = new Plane[count];
    for(int i = 0; i < count; i++)
        planes[i] = new Plane();

    return planes;
}

private Node[] readNodes()
{
    int count = header.lumps[Node.LUMP_INDEX].filelen / Node.SIZE;
    Node[] nodes = new Node[count];
    for(int i = 0; i < count; i++)
        nodes[i] = new Node();

    return nodes;
}

private Leaf[] readLeaves()
{
    int count = header.lumps[Leaf.LUMP_INDEX].filelen / Leaf.SIZE;
    Leaf[] leaves = new Leaf[count];
    for(int i = 0; i < count; i++)
        leaves[i] = new Leaf();

    return leaves;
}

etc. There are 10 of these functions, and the only differences is the class type, so as you can see, there's a ton of duplication.

Does any one have any ideas on how to avoid this duplication? Thanks. (I asked a similar question before, but i guess the way i asked it was a bit off)

like image 679
terryhau Avatar asked Mar 26 '11 19:03

terryhau


3 Answers

Use Java generics. That way, you can just write one generic method and specify a type parameter each time you use it.

like image 111
Rafe Kettler Avatar answered Oct 22 '22 17:10

Rafe Kettler


Bala's solution is close. You can't access constants from the generic type though, so I'd create a getCount() (or whatever you want to name it) and have each subtype implement it with the appropriate constants.

interface LumpySize<L extends LumpySize> {
    int getCount(); // subtypes return the appropriate header.lumps[Plane.LUMP_INDEX].filelen / Plane.SIZE; 

    T[] initializeArray();

    abstract <T extends LumpySize> static class Base implements LumpySize<T> {
        protected T[] initializeArray(Class<T> cls) {
            int count = getCount();
            T[] lumps = (T[]) Array.newInstance(cls, count);
            for(int i = 0; i < count; i++) {
                try {
                    lumps[i] = cls.newInstance();
                } catch (Exception e) {  // obviously this isn't good practice.
                    throw new RuntimeException(e);
                }
            }
            return lumps;
        }    
    }            
}

class Plane extends LumpySize.Base<Plane> {
    public int getCount() {
        return header.lumps[Plane.LUMP_INDEX].filelen / Plane.SIZE; // assuming header is available somewhere
    }
    public Plane[] initializeArray() { return initializeArray(Plane.class); }
}
like image 44
A Lee Avatar answered Oct 22 '22 19:10

A Lee


Okey doke ... I've tested this to make sure, and I believe it does what you're looking for.

You need an interface:

public interface MyInterface
{
    public int getSize();
    public int getLumpIndex();
}

Your classes implement that interface:

public class Plane implements MyInterface
{

    ...
    public int getSize()
    {
        return SIZE;
    }

    public int getLumpIndex()
    {
        return LUMP_INDEX;
    }

}

In the class that header is an instance of, you have ...

public <E extends MyInterface> E[] 
    getArray(Class<E> c, MyInterface foo)
{
    int count = lumps[foo.getLumpIndex()].filelen / foo.getSize();
    E[] myArray = (E[]) Array.newInstance(c, count);
    for(int i = 0; i < count; i++)
         myArray[i] = c.newInstance();
    return myArray;
}

You could call it from say, your Plane class as:

Plane[] p = header.getArray(Plane.class, this);

I think? :) Can someone look at this and see if I'm off?

(EDIT: Becasue I've tested it now - That works)

On an additional note, you could eliminate the getters in each class by making getArray() take the size and index as arguments:

public <E extends MyInterface> E[] 
    getArray(Class<E> c, int size, int index)
{
    int count = lumps[index].filelen / size;
    E[] myArray = (E[]) Array.newInstance(c, count);
    for(int i = 0; i < count; i++)
         myArray[i] = c.newInstance();
    return myArray;
}

And call it as:

Plane p[] = header.getArray(Plane.class, SIZE, LUMP_INDEX);

from inside your classes. The interface just becomes empty to provide the generic type and you don't have to define the getter methods.

OR (last edit I promise, but this does give you choices and explains a bit about generics)

Ditch the interface. What this removes is some sanity checking because the method doesn't care what type of object you give it:

public <E> E[] 
    getArray(Class<E> c, int size, int index)
{
    ...

Now you don't have to define the interface or implement it, you just call:

Plane p[] = header.getArray(Plane.class, SIZE, LUMP_INDEX);
like image 1
Brian Roach Avatar answered Oct 22 '22 19:10

Brian Roach