After adding a good answer, here's an awful answer, just for the heck of it. What bothers me about the Apache Commons ArrayUtils class is that there are 8 versions of the same method, just for different input types. I found a generic way to convert any primitive array into its wrapper equivalent (hence reducing the 8 different versions to one). This is the code:
public final class ArraysUtils {
private ArraysUtils() { }
@SuppressWarnings("unchecked")
public static Object[] toWrapperArray(final Object primitiveArray) {
Objects.requireNonNull(primitiveArray, "Null values are not supported");
final Class<?> cls = primitiveArray.getClass();
if (!cls.isArray() || !cls.getComponentType().isPrimitive()) {
throw new IllegalArgumentException(
"Only primitive arrays are supported");
}
final int length = Array.getLength(primitiveArray);
if (length == 0) {
throw new IllegalArgumentException(
"Only non-empty primitive arrays are supported");
}
final Object first = Array.get(primitiveArray, 0);
Object[] arr = (Object[]) Array.newInstance(first.getClass(), length);
arr[0] = first;
for (int i = 1; i < length; i++) {
arr[i] = Array.get(primitiveArray, i);
}
return arr;
}
}
As you can see, there's quite a lot wrong with that method:
- There's no compile-time safety, the method parameter can be anything and only the method itself will validate runtime parameters, rigorously rejecting null values, empty arrays, non-arrays and non-primitive arrays
- Reflection was needed
- There is no way to support empty arrays without keeping some sort of lookup table between primitive and wrapper classes.
Anyway, here is a test suite for all the necessary scenarios, using JUnit's Parameterized
runner:
@RunWith(Parameterized.class)
public class ArraysUtilsTest {
@Parameterized.Parameters(name = "{0}")
public static List<Object> parameters() {
return Arrays.asList(
success(new int[]{1, 2, 3}, new Integer[]{1, 2, 3}),
success(new long[]{1L, 2L, 3L}, new Long[]{1L, 2L, 3L}),
success(new byte[]{1, 2, 3}, new Byte[]{1, 2, 3}),
success(new short[]{1, 2, 3}, new Short[]{1, 2, 3}),
success(new char[]{'a', 'b', 'c'}, new Character[]{'a', 'b', 'c'}),
success(new double[]{1.0, 2.0, 3.0}, new Double[]{1.0, 2.0, 3.0}),
success(new float[]{1.0f, 2.0f, 3.0f}, new Float[]{1.0f, 2.0f, 3.0f}),
success(new boolean[]{true, false, true}, new Boolean[]{true, false, true}),
failure(null, NullPointerException.class, "Null"),
failure("foo", IllegalArgumentException.class, "Non-array"),
failure(new String[]{"foo", "bar"}, IllegalArgumentException.class, "Non-primitive array"),
failure(new int[0], IllegalArgumentException.class, "Empty array")
);
}
private static Object[] success(Object primitiveArray, Object[] wrapperArray) {
return new Object[]{
primitiveArray.getClass().getCanonicalName(),
primitiveArray, null, wrapperArray};
}
private static Object[] failure(Object input,
Class<? extends RuntimeException> exceptionClass,
String description) {
return new Object[]{description, input, exceptionClass, null};
}
@Parameterized.Parameter(0)
// only used to generate the test name
public String scenarioName;
@Parameterized.Parameter(1)
public Object inputArray;
@Parameterized.Parameter(2)
public Class<? extends RuntimeException> expectedException;
@Parameterized.Parameter(3)
public Object[] expectedOutput;
@Test
public void runScenario() {
try {
Object[] wrapped = ArraysUtils.toWrapperArray(inputArray);
if (expectedException != null) {
fail(String.format("Expected %s to be thrown",
expectedException.getSimpleName()));
}
assertThat(wrapped, is(equalTo(expectedOutput)));
} catch (RuntimeException e) {
if (expectedException == null) {
fail(String.format("Expected no exception but got %swith message '%s'",
e.getClass().getSimpleName(),
e.getMessage()));
}
if(!expectedException.isInstance(e)){
fail(String.format("Expected %s but got %s with message '%s'",
expectedException.getSimpleName(),
e.getClass().getSimpleName(),
e.getMessage()));
}
}
}
}