2

I've encountered the following problem:

If I map an object in a stream to a specific class in Java, the java stream API does not recognize a specific object after the mapping and still assumes it is an object. What am I doing wrong, and is there a way to solve this without potential class cast exceptions?

Here is the code example:

public class MyMapper {

        MyMapper() {
            Object someObject = new Person();
            final var listOfObjects = List.of(someObject);
            final var listOfPerson = toListOfPerson(listOfObjects);
        }

        List<Optional<Person>> toListOfPerson(Object object) {
            return ((List) object).stream()
                           .map(this::toPerson)
                           .collect(Collectors.toList());
        }

        Optional<Person> toPerson(Object object) {

            if (object instanceof Person) {
                return Optional.of((Person) object);
            }
            return Optional.empty();
        }

        public class Person {}
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
TheBohne
  • 79
  • 1
  • 10

2 Answers2

2

cast it to a typed List and then your .map(this::toPerson) will "accept" the element of this List

List<Optional<Person>> toListOfPerson(Object object) {
            return ((List<Object>) object).stream()
                    .map(this::toPerson)
                    .collect(Collectors.toList());
    }
1

It doesn't make sense to keep the collection of optional objects which could be potentially empty, it almost the same as storing null values.

If you think for some reason that List<Optional<Person>> is good idea, I recommend you to have a look at this question. A quote from the answer by Stuart Marks (JDK developer):

I'm sure somebody could come up with some contrived cases where they really want to store an Optional in a field or a collection, but in general, it is best to avoid doing this.

Optional was introduced in the JDK as a limited mechanism to represent a nullable return value. That is its only purpose, other cases of usage of optional objects like optional method arguments, fields, collections of optionals are considered to be antipattern.

You have to unpack your Optionals in the stream:

public List<Person> toListOfPerson(Object object) {
    if (!(object instanceof List<?>)) {
        return Collections.emptyList();
    }
    
    return ((List<?>) object).stream()
        .map(this::toPerson)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • thanks for the detailed answer. In my use case there is an extra treatment of "persons" that do not exist. But I agree to your comment and just throw a class cast exception instead of using optionales. – TheBohne Apr 25 '22 at 12:06
  • @TheBohne *extra treatment of "persons" that do not exist* - Then you might consider implementing the *Null-object pattern* to handle this case. Also note that *instanceof* checks is a *code-smell*. If you are working with a legacy system that returns you objects, that understandable. But it better to avoid it and design the logic in such a way that you don't need type-checks and down-casting. – Alexander Ivanchenko Apr 25 '22 at 13:42