1

I've tried to do the following, but it doesn't work

Optional<List<Integer>> listOne = someAPI.call()
// convert
Optional<ArrayList<Integer>> listTwo = new ArrayList<>(listOne); // doesn't work
Optional<ArrayList<Integer>> listTwo = Optional.of(new ArrayList(listOne)); // also not working

Note that the first API returns Optional<List>. And I need to send Optional<ArrayList> to another API.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • first get the list out of the optional using on e of those method here https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html not recommend to use get as there prpatily that the optional empty use one of those or method then the list the arraylist constructor as you did –  Jul 24 '22 at 04:02
  • 1
    Why not just use an empty list to represent “no elements” and avoid the extra complication of the `Optional`? – Ole V.V. Jul 24 '22 at 07:41
  • 1
    Agreed with you but API is written by someone else so I have no control over return type. – DATATruejji Jul 24 '22 at 14:37

3 Answers3

7

You can convert it like this:

Optional<ArrayList<Integer>> listTwo = listOne.map(ArrayList::new);

Hamza Hathoute
  • 438
  • 3
  • 12
3

By making use of Optional<ArrayList<Integer>> you piggyback one contrived problem on top of the other.

Don't wrap Collections with Optional

Any Collection by itself could represent the absence of data by being empty. And you can safely interact with an empty collection. Wrapping it with an Optional is redundant.

Here is a quote from the answer by Brian Goetz, Java language Architect, regarding how the Optional is intended to be used, and how it shouldn't be used from the perspective of its creators:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.

For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list.

Conclusion: design of the first API returning Optional<List<Integer>> is incorrect.

Don't use Optional as an Argument

This considered a misuse of the Optional because it goes against the main design goal of Optional (see the quote above, also have a look at the answer by Stuart Marks, Java and OpenJDK developer).

Conclusion: design of the second API expecting Optional<ArrayList<Integer>> is also wrong.

Write your code against Interfaces

Leverage abstractions, write your code against interfaces. There's no justification for making the code inflexible and capable only of consuming instances of ArrayList because it would not be able to deal with a List produced by Collections.emptyList(), List.of(), Arrays.asList(), Stream.toList(), etc.

See What does it mean to "program to an interface"?

Fixing the problem

Fix both endpoints if you can. Make them work without Optional.

In case if you're not in control of this first API returning the optional, fine, then don't make the problem bigger - fix the second API, make it expect a list of integer. And unpack the optional result right on the spot before call the second API.

You can use orElse() for that purpose:

List<Integer> list = someAPI.call().orElse(Collections.emptyList());

If you can't change both APIs, then you're out of luck.

Also, not that listOne.map(ArrayList::new) creates a copy of the initial data, that means that your system instead of making the second call immediately is forced to perform an additional step because of poor API design.

cb4
  • 6,689
  • 7
  • 45
  • 57
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • Agreed with you but API is written by someone else so I have no control over return type. – DATATruejji Jul 24 '22 at 14:37
  • 1
    @DATATruejji That's unfortunate if you're not in control of both APIs. At least, I've managed to deliver information regarding the best practices of using Optional that can useful to you in other situations, as well as to readers of this question. – Alexander Ivanchenko Jul 24 '22 at 14:59
1

If you start with this:

Optional<List<Integer>> optionalList = getFromSomewhere();

You can convert to Optional<ArrayList<Integer>> like this:

Optional<ArrayList<Integer>> optionalArrayList
        = Optional.of(new ArrayList<>(optionalList.get()));

This is close to what you tried - Optional.of(new ArrayList(listOne)) – but your attempt didn't work because listOne is type Optional<List<Integer>>, whereas the constructor to new ArrayList() takes a Collection. If you call .get() then you'll get the list out of the Optional, and that works fine for the ArrayList constructor.

UPDATE: This is a safer way to do it, guarding empty optional. Agree that the other answer – map(ArrayList::new) – is a simpler, cleaner way to do it. In this answer, I wanted to address the approach initially taken by OP, how it was close and could be made to work.

if (list.isPresent()) {
    Optional<ArrayList<Integer>> optionalArrayList
        = Optional.of(new ArrayList<>(optionalList.get()));
}
Kaan
  • 5,434
  • 3
  • 19
  • 41
  • 2
    also, `new ArrayList(listOne)` is raw. – rzwitserloot Jul 24 '22 at 04:58
  • 3
    This will throw a `NoSuchElementException` if the `Optional` is empty. Probably better to use `map`, as the [other answer](https://stackoverflow.com/a/73095669/6395627) shows. – Slaw Jul 24 '22 at 05:07
  • @Slaw Good point. I added an edit to check the optional before calling get(). – Kaan Jul 24 '22 at 18:21
  • It's not the recommended way, it's overly complicated and it still does not take the empty `Optional` into account (it just does nothing in that case). I definitely recommend that no one tries to use this answer. – Ole V.V. Jul 25 '22 at 17:11