views:

112

answers:

3

I have been research this all day and I cannot find a solution. I understand the problem of using subclasses and how the compiler wont know what the class will actually be because of the possibility of setting setting it to something else before calling add. I have look everywhere and I still don't know how to fix this problem. I have an abstract class that is a file wrapper. the file wrapper has an arraylist of sheetwrappers. I want the concrete class of excelfile to be able to add excelsheets to its arraylist but I keep getting this error:

The method add(capture#2-of ? extends SheetWrapper) in the type ArrayList is not applicable for the arguments (ExcelSheet)

Here is the necessary code. Only the parts that are involved in the error are pasted here.ExcelSheet is also an abstract class. Any help would be very very appreciated.Thanks!

public abstract class FileWrapper {

    protected ArrayList<? extends SheetWrapper> sheets;


public class ExcelFile extends FileWrapper {

    this.sheets = new ArrayList<ExcelSheet>();
    for(int i = 0; i < wb.getNumberOfSheets(); i++)
    {
     if(ParserTools.isLegitSheet(wb.getSheetAt(i)))
     {
      this.sheets.add(new ExcelSheet(null)); //ERROR HERE
     }
    }
+1  A: 

Well, you declared a variable as an "ArrayList of some elements that extend SheetWrapper". This means that you cannot add an instance of ExcelSheet to it, since it could actually be an ArrayList of some other type that extends SheetWrapper.

More precisely speaking, add() is not covariant.

If all your code is actually in the same place, then just fill your list before assigning it:

ArrayList<ExcelSheet> sheets = new ArrayList<ExcelSheet>();
for(int i = 0; i < wb.getNumberOfSheets(); i++)
{
    if(ParserTools.isLegitSheet(wb.getSheetAt(i)))
    {
            sheets.add(new ExcelSheet(null)); //ERROR HERE
    }
}
this.sheets = sheets;

If you need to do it from different parts of code, just lift that local to be another private or protected field (e.g. excelSheets) alongside the inherited sheets, and use that.

Pavel Minaev
Awesome! Thanks so much for the quick response. One little question. Maybe I am a little confused on how the compiler works, but if I initiated the sheets to an ArrayList of excelsheets why doesn't the compiler know that this will be an arraylist of excesheets, and therefore allow me to add excelsheets to it? That sounds weird, but I mean why doest the compiler autimtically know that once I initiate the arraylist that that it will only hold excelsheets? Thanks again
Daniel Hertz
Well, what if you have an `if..else` statement, in one branch of it you initialize the variable with `new ArrayList<ExcelSheet>`, and in another it's something else? In general, the compiler only considers the type of _variable_, as it is declared (hence why we say that Java is statically typed). It does not try to divine any additional type information from the way you use that variable.
Pavel Minaev
Achaaa! Makes sense now. Thanks so much!
Daniel Hertz
A: 

See the GenericsFAQ. You want simply:

protected List<SheetWrapper> sheets;

Note that using the interface type "List" is preferable to the concrete implementation class "ArrayList" for a variable declaration (unless specific methods are needed from that class).

bkail
I think he wants to let the derived classes restrict the specific type of elements they can contain (by initializing with some specific instantiation of `List`). Otherwise I don't see why he'd want a wildcard there in the first place.
Pavel Minaev
Initializing with a specific instantiation does restrict the values the list can contain; generics are compile-time only. If what you're saying is true, then what he wants is Collections.checkedList.
bkail
I'm guessing he wants `public abstract class FileWrapper<T extends SheetWrapper> { protected final List<T> sheets;`. `protected` is evil anyway.
Tom Hawtin - tackline
A: 

A second possibility is to make FileWrapper itself generic:

public abstract class FileWrapper<T extends SheetWrapper> {

    protected ArrayList<T> sheets;


    public class ExcelFile extends FileWrapper<ExcelSheet> {

        this.sheets = new ArrayList<ExcelSheet>();
        for(int i = 0; i < wb.getNumberOfSheets(); i++)
        {
            if(ParserTools.isLegitSheet(wb.getSheetAt(i)))
            {
                    this.sheets.add(new ExcelSheet(null));
            }
        }
    }
}

That way, you can use sheets as a list of ExcelSheet anywhere in ExcelFile.

ILMTitan