Java: Instantiating a generic class with no default constructor

放肆的年华 提交于 2019-12-23 12:27:50

问题


I am trying to do this:

public class BaseTable<T extends TableEntry>

{

    protected int mRows;
    protected int mCols;
    protected ArrayList<T> mEntries;

    public BaseTable(int rows, int cols)
    {
        mRows = rows;
        mCols = cols;
        mEntries = new ArrayList<T>();
        for (int i = 0; i < rows; i++)
        {
            mEntries.add(new T(cols)); //this obv. doesn't work
        }
    }
}

Instantiating generics is hard enough as it is, but what makes this even harder is that T here does not have a default constructor, it takes a single int parameter in its constructor.

How can this be done?


I have asked a follow up question here too. I'd be grateful if you could answer that as well.

This question is related, but only is relevant where the classes are assumed to have a default constructor.


回答1:


It was already said, that you can't create an instance of T with new, so I would use the Factory Pattern or a Prototype Pattern

So your constructor would look like public BaseTable(int rows, int cols, LineFactory factory) with an appropriate instance of a factory.

In your case, I would prefer the Prototype Pattern, because your TableEntry objects are probably very light-weight. Your code would look like:

public BaseTable(int rows, int cols, T prototype)
{       
  mRows = rows;
  mCols = cols;
  prototype.setColumns(cols);
  mEntries = new ArrayList<T>();
  for (int i = 0; i < rows; i++)
  {
    @SuppressWarnings("unchecked")
    T newClone = (T)prototype.clone();
    mEntries.add(newClone); //this obv. does work :)
  }
}

public static void main(String[] args)
{
  new BaseTable<SimpleTableEntry>(10, 2, new SimpleTableEntry());
}



回答2:


The good reason why it shouldn't be done like this is, that you can't force a sub class to implement a certain constructor. It's very obvious for implementations of interface, for they don't include a contract for constructors.

And if your real or abstract class BaseTable had a constructor BaseTable(int columns), the fictious sub class VectorTable with a single column wouldn't need to implement it and could do something bad like

public VectorTable(int intialValue) {
  super(1);
  this.initialValue = initialValue;
}

So first, you don't know if T implements the constructor at all and second, you don't know if the constructor has the same purpose (which it really should have in proper code!!)

So I think, a better solution is to move the parametrized part from the constructor into a separate (final?) method, create the instance with the default constructor and call that initialization method right afterwards.

You may think of implementing a BaseTableFactory if you want to make sure, that all BaseTable sub classes are always initialized correctly.

Edit

And new T() (instantiating the default constructor) isn't possible as well, because no class can be forced to implement an accessible default constructor. I still think, the factory pattern is your best friend here.




回答3:


You need to use Factory interface:

public interface TableEntryFactory<T extends TableEntry>{
 public T create (int cols);
}

also you need to make a factory for each class "with type T":

// for every class of type T
public class SpecialTableEntry extends TableEntry{
   SpecialTableEntry(int cols){
      ...
   }
}

// make a factory creating instance of this class
public class SpecialTableEntryFactory implements TableEntryFactory<SpecialTableEntry> {
   @Override
   public SpecialTableEntry create (int cols){
       return new SpecialTableEntry(cols);
   }
}

Your code will look like:

public class BaseTable<T extends TableEntry>

{

protected int mRows;
protected int mCols;
protected ArrayList<T> mEntries;

public BaseTable(int rows, int cols, TableEntryFactory<T> tableFactory)
{
    mRows = rows;
    mCols = cols;
    mEntries = new ArrayList<T>();
    for (int i = 0; i < rows; i++)
      {
        mEntries.add(tableFactory.create(cols)); //this should work now
      }
  }
}


You can call it like:

TableEntryFactory<SpecificTableEntry> myFactory = new SpecificTableEntryFactory();
BaseTable<?> table = new BaseTable<SpecificTableEntry>(rows,cols,myFactory);


P.S. It's not my original solution. I have found it somewhere long time ago and used it in my code. Unfortunately I can't find a link to an original idea...




回答4:


Simple!

Use a static factory method instead of a constructor:

public static <T extends TableEntry> newInstance(int rows, int cols) {
    return new BaseTable<T>(rows, cols);
}

To instantiate type

BaseTable<Number> = BaseTable.newInstance(10, 20);


来源:https://stackoverflow.com/questions/2106874/java-instantiating-a-generic-class-with-no-default-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!