Here's an example of this which uses a simple object for each case.
package mcve.util;
import java.util.*;
import java.util.function.*;
/**
* Allows switch-like statements with classes and consumers.
*/
public final class ClassSwitch implements Consumer<Object> {
/**
* For each of the specified cases, in order of their
* appearance in the array, if cases[i].test(obj) returns
* true, then invoke cases[i].accept(obj) and return.
*
* @param obj the object to switch upon
* @param cases the cases for the switch
* @throws NullPointerException
* if any of the cases are null
*/
public static void cswitch(Object obj, Case<?>... cases) {
if (cases != null) {
for (Case<?> c : cases) {
if (c.test(obj)) {
c.accept(obj);
break;
}
}
}
}
/**
* @param type the type of the case
* @param action the action to perform
* @param <T> the type of the case
* @throws NullPointerException
* if the type or action is null
* @return a new Case
*/
public static <T> Case<T> ccase(Class<T> type, Consumer<? super T> action) {
return new Case<>(type, action);
}
/**
* @param <T> the type of the case
*/
public static final class Case<T> implements Predicate<Object>,
Consumer<Object> {
private final Class<T> type;
private final Consumer<? super T> action;
/**
* @param type the type of the case
* @param action the action to perform
* @throws NullPointerException
* if the type or action is null
*/
public Case(Class<T> type, Consumer<? super T> action) {
this.type = Objects.requireNonNull(type, "type");
this.action = Objects.requireNonNull(action, "action");
}
/**
* @param obj the object to test
* @return true if the object is an instance of T, else false
*/
@Override
public boolean test(Object obj) {
return type.isInstance(obj);
}
/**
* @param obj the object to perform the action on
* @throws ClassCastException
* if the object is not an instance of T
*/
@Override
public void accept(Object obj) {
action.accept(type.cast(obj));
}
}
/**
* An unmodifiable list of the cases in this switch.
*/
private final List<Case<?>> cases;
/**
* @param cases the cases for this switch
* @throws NullPointerException
* if any of the cases are null
*/
public ClassSwitch(Case<?>... cases) {
if (cases == null) {
this.cases = Collections.emptyList();
} else {
List<Case<?>> list = new ArrayList<>(cases.length);
for (Case<?> c : cases) {
list.add(Objects.requireNonNull(c, "case"));
}
this.cases = Collections.unmodifiableList(list);
}
}
/**
* @return an unmodifiable view of the cases in this switch
*/
public List<Case<?>> getCases() { return cases; }
/**
* For each of the cases in this switch, in order of their
* appearance in the list, if cases.get(i).test(obj) returns
* true, then invoke cases.get(i).accept(obj) and return.
*
* @param obj the object to switch upon
*/
@Override
public void accept(Object obj) {
for (Case<?> c : cases) {
if (c.test(obj)) {
c.accept(obj);
break;
}
}
}
}
A usage example would be something like this, assuming imports of e.g. import static mcve.util.ClassSwitch.*;
:
cswitch(anObject,
ccase(Byte.class, b -> System.out.println("Byte")),
ccase(Short.class, s -> System.out.println("Short")),
ccase(Integer.class, i -> System.out.println("Integer")),
ccase(Long.class, l -> System.out.println("Long")),
ccase(Float.class, f -> System.out.println("Float")),
ccase(Double.class, d -> System.out.println("Double"))
);
You could also create a reusable object:
ClassSwitch ts =
new ClassSwitch(ccase(String.class, System.out::println),
ccase(Double.class, System.out::println));
ts.accept(anObject);
Notes:
If you want a default
case, you can use Object.class
as the last case.
There's no way to make a case which handles null
, but it could be modified a little bit for that. You could e.g. make a class NullCase
whose test
method returns obj == null
.
What you could also do is actually generate overloads instead of using varargs. This lets you associate classes with consumers using just generic method declarations. The following is a fairly simple example of this:
package mcve.util;
import java.util.*;
import java.util.function.*;
/**
* Allows switch-like statements with classes and consumers.
*/
public final class GeneratedClassSwitch {
private GeneratedClassSwitch() {}
/**
* Generates overloads for a class switch to System.out.
*
* For example, if max=4, then 5 methods are generated:
* with 0, 1, 2, 3, and 4 cases.
*
* @param max
* the number of cases in the largest overload generated
* @param indents
* the number of indents to indent each generated method
* @throws IllegalArgumentException
* if max is negative or greater than 26, or if indents
* is negative
*/
public static void generateFixedOverloads(int max, int indents) {
if (max < 0 || max > 26) {
throw new IllegalArgumentException("max=" + max);
}
String indent = String.join("", Collections.nCopies(indents, " "));
for (int i = 0; i <= max; ++i) {
System.out.print(indent);
System.out.print("public static ");
if (i > 0) {
System.out.print("<");
for (char ch = 'A'; ch < 'A' + i; ++ch) {
if (ch != 'A') {
System.out.print(", ");
}
System.out.print(ch);
}
System.out.print("> ");
}
System.out.print("void cswitch");
if (i > 0) {
System.out.println();
System.out.print(indent + " (Object o, ");
for (char ch = 'A'; ch < 'A' + i; ++ch) {
if (ch != 'A') {
System.out.println(",");
System.out.print(indent + " ");
}
System.out.print("Class<" + ch + "> class" + ch);
System.out.print(", Consumer<? super " + ch + "> action" + ch);
}
} else {
System.out.print("(Object o");
}
System.out.println(") {");
for (char ch = 'A'; ch < 'A' + i; ++ch) {
if (ch == 'A') {
System.out.print(indent + " ");
} else {
System.out.print(" else ");
}
System.out.println("if (class" + ch + ".isInstance(o)) {");
System.out.print(indent + " ");
System.out.println("action" + ch + ".accept(class" + ch + ".cast(o));");
System.out.print(indent + " ");
System.out.print("}");
if (ch == ('A' + i - 1)) {
System.out.println();
}
}
System.out.print(indent);
System.out.println("}");
}
}
// Generated code pasted below.
public static void cswitch(Object o) {
}
public static <A> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
}
}
public static <A, B> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
}
}
public static <A, B, C> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
}
}
public static <A, B, C, D> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC,
Class<D> classD, Consumer<? super D> actionD) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
} else if (classD.isInstance(o)) {
actionD.accept(classD.cast(o));
}
}
public static <A, B, C, D, E> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC,
Class<D> classD, Consumer<? super D> actionD,
Class<E> classE, Consumer<? super E> actionE) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
} else if (classD.isInstance(o)) {
actionD.accept(classD.cast(o));
} else if (classE.isInstance(o)) {
actionE.accept(classE.cast(o));
}
}
public static <A, B, C, D, E, F> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC,
Class<D> classD, Consumer<? super D> actionD,
Class<E> classE, Consumer<? super E> actionE,
Class<F> classF, Consumer<? super F> actionF) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
} else if (classD.isInstance(o)) {
actionD.accept(classD.cast(o));
} else if (classE.isInstance(o)) {
actionE.accept(classE.cast(o));
} else if (classF.isInstance(o)) {
actionF.accept(classF.cast(o));
}
}
public static <A, B, C, D, E, F, G> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC,
Class<D> classD, Consumer<? super D> actionD,
Class<E> classE, Consumer<? super E> actionE,
Class<F> classF, Consumer<? super F> actionF,
Class<G> classG, Consumer<? super G> actionG) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
} else if (classD.isInstance(o)) {
actionD.accept(classD.cast(o));
} else if (classE.isInstance(o)) {
actionE.accept(classE.cast(o));
} else if (classF.isInstance(o)) {
actionF.accept(classF.cast(o));
} else if (classG.isInstance(o)) {
actionG.accept(classG.cast(o));
}
}
public static <A, B, C, D, E, F, G, H> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC,
Class<D> classD, Consumer<? super D> actionD,
Class<E> classE, Consumer<? super E> actionE,
Class<F> classF, Consumer<? super F> actionF,
Class<G> classG, Consumer<? super G> actionG,
Class<H> classH, Consumer<? super H> actionH) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
} else if (classD.isInstance(o)) {
actionD.accept(classD.cast(o));
} else if (classE.isInstance(o)) {
actionE.accept(classE.cast(o));
} else if (classF.isInstance(o)) {
actionF.accept(classF.cast(o));
} else if (classG.isInstance(o)) {
actionG.accept(classG.cast(o));
} else if (classH.isInstance(o)) {
actionH.accept(classH.cast(o));
}
}
}
If you want to generate overloads, for example to have more than 8 cases, you can say something like the following:
GeneratedClassSwitch.generateFixedOverloads(16, 1);
That will generate methods to System.out
that follow the general form of:
public static <A, B, C> void cswitch
(Object o, Class<A> classA, Consumer<? super A> actionA,
Class<B> classB, Consumer<? super B> actionB,
Class<C> classC, Consumer<? super C> actionC) {
if (classA.isInstance(o)) {
actionA.accept(classA.cast(o));
} else if (classB.isInstance(o)) {
actionB.accept(classB.cast(o));
} else if (classC.isInstance(o)) {
actionC.accept(classC.cast(o));
}
}
Notice that we're able to map each class type to its associated consumer type, i.e.
Class<A>
with Consumer<? super A>
, Class<B>
with Consumer<? super B>
, and so on. This is actually impossible with varargs (as of the current version of Java, anyway, which is 10).
Our usage example is now like this, again assuming imports of e.g. import static mcve.util.GeneratedClassSwitch.*;
:
cswitch(anObject,
Byte.class, b -> System.out.println("Byte"),
Short.class, s -> System.out.println("Short"),
Integer.class, i -> System.out.println("Integer"),
Long.class, l -> System.out.println("Long"),
Float.class, f -> System.out.println("Float"),
Double.class, d -> System.out.println("Double")
);
(Notes about default
cases and null
are the same as the first example.)