tags:

views:

60

answers:

3

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 = 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?

+6  A: 

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);
}
aioobe
+1, but will go out of bounds on the substring if the Object list is empty, add a `if (objs == null || objs.length == 0) return "";` on top.
Thilo
thank you for that point. updated answer accordingly.
aioobe
But if delim is a String, then this call would fail: `concat("/", "1","2");` Doesn't the delim have to be char if you want to pass it like that?
Shervin
I missed that, I took the more general option of having it as a string. Changed to char.
aioobe
@Shervin, why would it fail in case the signature is `(String s, String... strs)`?
aioobe
@aioobe: Its strange, when I first tested it, the compiler complained, but when I test it again, it doesn't complain...
Shervin
+1  A: 

You can only copy Strings 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.

Thilo
+1  A: 

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 from original is not of a runtime type that can be stored in an array of class newType

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


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.

polygenelubricants
Lovely, just lovely :-)
Shervin