Search code examples
javapolymorphism

Overriding method needs to convert from List<ChildClass> to List<ParentClass>


I'm mocking some code for testing. In one mock, I'm trying to mock ServletFileUpload::parseRequest, which returns a List of FileItems.

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

    private class MockServletFileUpload extends ServletFileUpload {

        List<MockDiskFileItem> fileItems;

        @Override
        public List<FileItem> parseRequest(HttpServletRequest request) {
            return fileItems;
        }
    }

but Eclipse signals an error on the method's return: Type mismatch: cannot convert from List<MockDiskFileItem> to List<FileItem>. But MockDiskFileItem implements FileItem! Why doesn't this work?

On the other hand, this version of the method, which casts the individual elements of the List to the parent class, doesn't give errors:

    @Override
    public List<FileItem> parseRequest(HttpServletRequest request) {
        return fileItems.stream()
                .map( dfi -> (FileItem) dfi )
                .collect(Collectors.toList());
    }

Why do the elements have to be individually cast? And is there any shorter way to write this?

UPDATE

To clarify, my question is primarily a practical one: how to convert/cast from List<MockDiskFileItem> to List<FileItem> most efficiently. I'd also like to know why Java can't automatically do the conversion/cast of the whole list, when as should be clear to anyone who takes the time to read the second version of the method, Java will automatically convert/cast any MockDiskFileItem to FileItem.

UPDATE 2

I actually have an answer to my own question that shows why this question is different and not a duplicate. And you can see from the one answer below how this is a different question. Why was this closed? You moderators are too heavy handed. How many reputation points did that gain you? And what did it cost this site's integrity?


Solution

  • Write it like this, you'll get no errors:

    public List<? extends FileItem> parseRequest(final HttpServletRequest request) {
        return fileItems;
    }
    

    As above comments said, you have a problem with contravariance, and that generics are not covariant.

    Update

    Oh yes, I completely forgot he's overriding a method. Sorry for that. This is IMO the easiest and most efficient conversion with the least lines:

    class MockServletFileUpload extends ServletFileUpload {
    
        List<MockDiskFileItem> fileItems;
    
        @Override public List<FileItem> parseRequest(final HttpServletRequest request) {
            return new ArrayList<>(fileItems);
        }
    
    
    
        public static void main(final String[] args) {
            final MockServletFileUpload msfu = new MockServletFileUpload();
            msfu.fileItems = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
                msfu.fileItems.add(new MockDiskFileItemImpl());
            }
    
    
            final List<FileItem> result = msfu.parseRequest(null);
            System.out.println("Results:");
            System.out.println(result.getClass());
            for (final FileItem fileItem : result) {
                System.out.println("\t" + fileItem.getClass());
            }
            System.out.println("Done.");
        }
    }
    

    Up until quite a high amount if items (I guess 20k+) this will also be way faster than streams or parallel streams.

    I added some simple test code to prove the items are still MockDiskFileItems (or my implementation of it).

    Explanation

    This SO question is helpful to see the problem, but here's a little summary. Casting a generic like List<ChildClass> foo to List<ParentClass> bar

    List<ChildClass> foo = new ArrayList<>();
    List<ParentClass> bar = (List<ParentClass>) foo; // Doesn't work
    

    would just create an alias to the same object in memory (same pointer), but treat it as a list consisting of elements of a different type. The danger is that objects of a sibling/other child type could then be added to this list whose elements are really the first child type. The classic example given:

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // doesn't work because ...
    animals.add(new Cat()); // ... BIG problem
    

    Hopefully, you can see the problem in that last line!

    To avoid the danger of adding a Cat to a Dog List, what you really need is a new list, converted, so to speak, to the parent element type. So you'll see the solution above makes a copy of the original list with the cast (implicit) to the parent type.