5

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)

terryhau
  • 549
  • 2
  • 9
  • 18

4 Answers4

3

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

Rafe Kettler
  • 75,757
  • 21
  • 156
  • 151
  • you mean with something like private void read(Class clazz)? I can't access the 2 constants through the clazz parameter. – terryhau Mar 26 '11 at 19:10
  • 2
    Be aware that you can't do `new T[x]` with generics. You *can* get around it, but the answer isn't that simple. – Brian Roach Mar 26 '11 at 19:13
  • @Brian Roach: a hint on what to use then? A generic container? – xtofl Mar 26 '11 at 19:15
  • 1
    The Generics example by @Bala shows how you can get around the `T[]` problem. Personally ... I think I'd just use an approach that incorporated `ArrayList` rather than mucking around directly with arrays. – Brian Roach Mar 26 '11 at 19:28
2

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); }
}
A Lee
  • 7,828
  • 4
  • 35
  • 49
  • You've misread the code. It's an abstract base class that you extend, e.g., `class Plane extends LumpySize.Base`. – A Lee Mar 26 '11 at 19:53
  • +1. Yep. That's the way to get implementation dependent constants into generic code. Very usefull to know. – extraneon Mar 26 '11 at 20:35
  • Yeah ... I just don't know that I care for it. You could just as easily define the same abstract class outside the interface, and it's really not needed since `header` can encapsulate all the functionality. And the math is still being done in each class (Plane, Node, etc). The way I approached it moves all of that inside the class that `header` is an instance off. – Brian Roach Mar 26 '11 at 20:45
1

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);
Brian Roach
  • 76,169
  • 12
  • 136
  • 161
  • Thanks, i think that is probably the best way to do it (except the getArray function doesn't need to be inside header). However, when i try it, c.newInstance() throws an InstantiationException, and I have no idea why, so I'm stumped. – terryhau Mar 27 '11 at 04:20
  • You never posted what your constructors were for your objects. Do you have a no argument constructor? – Brian Roach Mar 27 '11 at 04:22
  • Yes they have no arguments. I've figured out why its throwing the exception. Thanks – terryhau Mar 27 '11 at 05:18
0

Use generics, but you'll need to pass in some sort of factory object to construct instances to put in your collection, eg:

public class MyClass {

public <E> E[] getArray(IObjectFactory builder, int index, int size){
    ArrayList<E> arrayList = new ArrayList<E>();
    int count = header.lumps[index].filelen / size;//wasn'tsure where header was coming from...
    for(int i = 0; i< count; i++){
        E newInstance = builder.getNewInstance();
        arrayList.add(newInstance);
    }
    return (E[]) arrayList.toArray();
  }   
}    

interface IObjectFactory {
<E> E getNewInstance();
}
katsharp
  • 2,551
  • 24
  • 27