0

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?

JohnK
  • 6,865
  • 8
  • 49
  • 75
  • [Related - covariance](https://stackoverflow.com/questions/3763192/java-collections-covariance-problem) – StuartLC Mar 21 '22 at 17:34
  • 1
    QBrute and StuartLC - it seems to me that OP understands very well that a `List` and a `List` are not the same thing. He's asking for code to convert one to the other, not an explanation of why they are different. – Dawood ibn Kareem Mar 21 '22 at 17:52
  • Why do you declare `fileItems` as `List`? – Sotirios Delimanolis Mar 21 '22 at 19:03
  • @SotiriosDelimanolis, in my mock, I need some additional methods on `MockDiskFileItem` that `FileItem` and `DiskFileItem` don't have. – JohnK Mar 21 '22 at 19:41
  • Yes, they often close questions and mark them as duplicate when they're not. When there's more to the question than the suggested duplicate answer. I clearly dislike the superficiality that has taken hold with a lot of the "moderators". Most have established a routine of "maintaining" this site, and this is not good for the community. – JayC667 Mar 23 '22 at 03:42

1 Answers1

1

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.

JohnK
  • 6,865
  • 8
  • 49
  • 75
JayC667
  • 2,418
  • 2
  • 17
  • 31
  • 1
    I don't think this will help, when OP is trying to override a base class where `parseRequest` returns `List`. He really needs to convert his `List` to a `List`, not just use a weaker data type. – Dawood ibn Kareem Mar 21 '22 at 17:51
  • @JayC667, I'm overriding a method in an established class, so changing the return type is not possible. – JohnK Mar 21 '22 at 19:46
  • 2
    `Collections.unmodifiableList()` should also work. If a client using this method expects to be able to alter the returned list, s/he will be screwed just as with the copied list; the only difference is that this throws an exception whereas a copied list just might not do what the client wants. – Izruo Mar 22 '22 at 14:36
  • @Izruo While your solution is also good, actually I cannot see the problem that you address in my solution. I did an "upcast" of the contained elements, so they're actual `FileItem`s. I know there'd be problems if we had used downcasts, then we'd run into `ClassCastException`s and the like. Can you give me an example where my solution would cause an exception? (Unless he down-casts the list again somewhere...) – JayC667 Mar 23 '22 at 04:05
  • 2
    With *this throws an exception* I was referring to `Collections.unmofiableList()`. The scenario I'm thinking about is that a client expects to alter the behavior of the class by manipulating the returned list. In the code given by OP, this would affect consecutive calls of the `#parseRequest` method, in the code given in this answer, this would have no affect. – Izruo Mar 23 '22 at 05:49