Problem when copying array of different types using Arrays.copyOf
I am trying to create a method that pretty much takes anything as a parameter, and returns a concatenated string representation of the value with some delimiter.
public static String getConcatenated(char delim, Object ...names) {
String[] stringArray = Arrays.copyOf(names, names.length, String[].class); //Exception here
return getConcatenated(delim, stringArray);
}
And the actual method
public static String getConcatenated(char delim, String ... names) {
if(names == null || names.length == 0)
return "";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < names.length; i++) {
String n = 开发者_StackOverflow中文版names[i];
if(n != null) {
sb.append(n.trim());
sb.append(delim);
}
}
//Remove the last delim
return sb.substring(0, sb.length()-1).toString();
}
And I have the following JUnit test:
final String two = RedpillLinproUtils.getConcatenated(' ', "Shervin", "Asgari");
Assert.assertEquals("Result", "Shervin Asgari", two); //OK
final String three = RedpillLinproUtils.getConcatenated(';', "Shervin", "Asgari");
Assert.assertEquals("Result", "Shervin;Asgari", three); //OK
final String four = RedpillLinproUtils.getConcatenated(';', "Shervin", null, "Asgari", null);
Assert.assertEquals("Result", "Shervin;Asgari", four); //OK
final String five = RedpillLinproUtils.getConcatenated('/', 1, 2, null, 3, 4);
Assert.assertEquals("Result", "1/2/3/4", five); //FAIL
However, the test fails on the last part with the exception:
java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at java.util.Arrays.copyOf(Arrays.java:2763)
Can someone spot the error?
Since you can't store, say Integers
, in a String[]
array, there is no way you can just copy an array of objects into an array of Strings. You have to somehow go through .toString()
on each object.
This solution would for instance work:
public static String concat(char delim, Object... objs) {
if (objs == null || objs.length == 0) return "";
StringBuilder sb = new StringBuilder();
for (Object o : objs)
sb.append(delim).append(o);
return sb.substring(1);
}
As a side note on varargs; I doubt you need to check if objs == null
. The compiler will turn a call of the from concat(",", "a", "b", "c")
into concat(",", new Object[] {"a", "b", "c")
, thus I can't see how objs
could ever equal null
. -->
The cause
You can't put an Integer
in a String[]
. That's why you get ArrayStoreException
. Arrays.copyOf
does no conversion.
From the API:
T[] copyOf(U[] original, int newLength, Class< ? extends T[]> newType)
Throws:
ArrayStoreException
- if an element copied fromoriginal
is not of a runtime type that can be stored in an array of classnewType
The ArrayStoreException
API has an example:
Thrown to indicate that an attempt has been made to store the wrong type of object into an array of objects. For example, the following code generates an
ArrayStoreException
:Object x[] = new String[3]; x[0] = new Integer(0);
See also
- JLS 10.10 Array Store Exception
The fix
That said, you actually don't need a String...
and Arrays.copyOf
to a String[]
. Your getConcatenated
can just take Object...
and it'd work just fine.
public static String getConcatenated(char delim, Object... objs) {
if(objs == null || objs.length == 0)
return "";
StringBuilder sb = new StringBuilder();
for (Object o : objs) {
if(o != null) {
if (sb.length() > 0) sb.append(delim);
sb.append(o.toString().trim());
}
}
return sb.toString();
}
Now you can do the following:
System.out.println(getConcatenated(';', "Shervin", null, "Asgari", null));
// prints "Shervin;Asgari"
System.out.println(getConcatenated('/', 1, 2, null, 3, 4));
// prints "1/2/3/4"
System.out.println(getConcatenated(':', " ", null, "boo", "boo"));
// prints "boo:boo"
Note that this follows the intended specification in the original code, i.e. trim()
and skips null
.
You can only copy String
s into a String[]
.
System#arraycopy
and Arrays#copyOf
do not do any type conversions, you have to turn the objects into Strings one by one yourself, for example by calling String#valueOf
(the null-safe version of Object#toString
).
Why not change the method to accept Object...
instead of String...
and call String.valueOf(obj)
on each of them.
精彩评论