Since I need a Java method to receive any number of objects of three specific classes I decided to use a vararg ...
to implement it. However, I have the restriction that these three classes are:
In my imaginary syntax my method could look like:
public void myMethod(<String|Class|MyWidget>... elements)) {
// do something
}
I was thinking I could use a superclass or interface, but the first two classes belong to the JVM and I cannot touch them.
How can I do this in Java?
Variadic varargs aren't supported. What you want with the hypothetical syntax isn't in Java, at all. There is no way to write a method that accepts an arbitrary number of arguments such that the compiler marks that call off as invalid if any of the args aren't typed Class
, String
, or MyWidget
.
Make 3 methods each with args MyWidget...
, Class<?>...
, etc. Now you can call this method with all mywidgets, or all classes, etc, but you still can't call this method with a hybridized mix of types.
Just make one method with Object...
and use instanceof
to check theem all before continuing. This has the downside that I can call your method passing, say, a bunch of InputStream
objects, and the compiler will let it happen; you won't know that's invalid until you actually run the code.
Something like this:
static class MyMethodBuilder {
List<Class<?>> classes = new ArrayList<>();
List<MyWidget> widgets = new ArrayList<>();
List<String> strings = new ArrayList<>();
MyMethodBuilder add(Class<?> f) {
classes.add(f);
return this;
}
MyMethodBuilder add(MyWidget f) {
widgets.add(f);
return this;
}
MyMethodBuilder add(String f) {
strings.add(f);
return this;
}
void go() {
// body of method here
}
}
Called as:
myMethod()
.add(SomeWidget.class)
.add(someInstanceOfWidget)
.add("someNameOfWidget")
.go();
Note that this solution doesn't preserve order (you can't tell the difference between first adding a string and then adding a widget, vs the other way around); if you need order preserved you'll need to do more work, such as storing them all in a single List<Object>
and using something like:
for (Object v : elems) {
if (v instanceof MyWidget mw) process(mw);
else if (v instanceof Class<?> v) process(v.getConstructor().newInstance());
else if (v instanceof String) processViaString(...);
else throw new IllegalStateException("BUG: arrived here with a " + v.getClass());
}
Make a wrapper type that accepts any of the 3, then make a method that accepts varargs amount of that wrapper:
sealed interface MyWrapper {
static StringWrapper of(String in) {
return new StringWrapper(in);
}
static ClassWrapper of(Class<?> in) {
return new ClassWrapper(in);
}
static WidgetWrapper of(MyWidget in) {
return new WidgetWrapper(in);
}
}
final class StringWrapper implements MyWrapper {
final String value;
StringWrapper(String value) {
this.value = value;
}
}
final class WidgetWrapper implements MyWrapper {
final MyWidget value;
ClassWrapper(String value) {
this.value = value;
}
}
// same for ClassWrapper
// and then:
void myMethod(MyWrapper... wrappers)
which can be called as:
myMethod(Wrapper.of("Hi"), Wrapper.of(myWidget), Wrapper.of(Foo.class));
A method that accepts either instances of MyWidget
, or a class object, or a string sounds like bad design. Accepting Class<?>
is almost always wrong; presumably you are going to invoke .newInstance()
on that, which is bad, because now you no longer get compile checks checking that the type provided has a public no-args constructor in the first place.
What you probably want is a varargs set of Supplier<MyWidget>
. Then you can call:
MyWidget mw = makeAWidget();
myMethod(
() -> mw,
SomeWidgetType::new,
loadDynamic("foo.bar.fullyqualifiednameof.SomeCustomWidget"));
Where the idea of creating these with a string is kinda icky, that's, obviously, not compile time checkable. loadDynamic
is a method that finds that class, checks that it has a no-args constructor that is public and callable. It fails immediately if those things aren't set up properly; if all is well, it returns a supplier that calls .newInstance()
each time you get()
on it.
And now your varargs method is simple. It takes any number of Supplier<MyWidget>
. This is compile-time checked; SomeWidgetClass::new
is a valid expression of type Supplier<MyWidget>
if it has a public no-args constructor, and isn't if it isn't.