User1291
User1291

Reputation: 8179

Forcing call with array to resolve as varargs invocation

Alright, so I have a function like

public static UnorderedList newUnorderedList(Object... items) {
    return new UnorderedList(
        stream(items)
            .peek(e -> checkNotNull(e, "Cannot create null list item"))
            .map(e -> {
                if (e instanceof Component) return newListItem((Component) e);
                if (e instanceof String) return newListItem((String) e);
                throw new IllegalArgumentException("List item must be String or Component but is neither: " + e.getClass().getName());
            }).toArray(ListItem[]::new)
    );
}

(EDIT: Note: the UnorderedList here, is Vaadin's implementation of an html <ul> tag, I'm not trying to get a java list.)

This will trigger a warning when you call it with an array saying that it's unclear whether you want to treat the array itself as a single element or as a container for the elements.

Don't immediately see an elegant way out of this. I like neither of these:

Is there an annotation or something that would let the compiler know to always resolve arrays to vararg calls on annotated methods? (On the method declaration, not the call sites.)

Upvotes: 1

Views: 253

Answers (3)

Holger
Holger

Reputation: 298233

You can overload the method:

public static UnorderedList newUnorderedList(Object first, Object... other) {
    return newUnorderedListImpl(Stream.concat(Stream.of(first), Arrays.stream(other)));
}
public static UnorderedList newUnorderedList(Object[] items) {
    return newUnorderedListImpl(Arrays.stream(items));
}
private static UnorderedList newUnorderedListImpl(Stream<?> items) {
    return new UnorderedList(
        items
            .peek(e -> checkNotNull(e, "Cannot create null list item"))
            .map(e -> {
                if (e instanceof Component) return newListItem((Component) e);
                if (e instanceof String) return newListItem((String) e);
                throw new IllegalArgumentException("List item must be String or Component but is neither: " + e.getClass().getName());
            }).toArray(ListItem[]::new)
    );
}

Then, a call with an existing array will end up at newUnorderedList(Object[] items) whereas an actual varargs call would end up at newUnorderedList(Object first, Object... other), even when only one argument has been specified, as long as that argument is not an array. Since the single argument is supposed to be either, a String or Component, this is not a problem.

The only possibility which has been lost with these two methods, is the ability to call the method without an argument. If this is a problem, you need to add another overload:

public static UnorderedList newUnorderedList() {
    return newUnorderedListImpl(Stream.empty());
}

Upvotes: 5

tquadrat
tquadrat

Reputation: 4044

From your question, I guess that you want to do the following:

Object [] items = new Object [x];
items [0] = new Object [] {"Object1", "Object2", "Object3"};
var result = newUnorderedList( items );

This will treat the array with the String as a single item, causing the exception, while calling

var result = newUnorderedList( items [0] );

would return 3 elements for result.

There is no annotation that forces the handling of a single array as a single item instead of a list of items. Have fun with a signature like this:

Object function( Object [] ... );

Upvotes: 0

lscoughlin
lscoughlin

Reputation: 2416

You're making an UnorderedList the wrong way.

Assuming it's a collection:

    Object[] objects = new Object[]{1, "hello", "there", "george"};
    LinkedList<AtomicReference<?>> list = Arrays.stream(objects)
            .filter(Objects::nonNull)
            .map(e -> {
                if (e instanceof Integer) return new AtomicReference<>(e);
                if (e instanceof StringBuilder) return new AtomicReference<>(e);
                throw new IllegalArgumentException("PAIN");
            })
            .collect(Collectors.toCollection(LinkedList::new));

Upvotes: 0

Related Questions