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开发者_Go百科; 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)
Use Java generics. That way, you can just write one generic method and specify a type parameter each time you use it.
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); }
}
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);
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();
}
精彩评论