Slightly over-engineered, but helpful to have if this issue occurs more often (especially if you add overloaded methods that delegate to this one using default mappers and separators):
/**
* @param separator used to join the values. NOTE: the separator is interpreted as a regular expression.
* @return a list of the values' string representation according to <code>mapper</code>, separated by the specified
* string. Null if list is null or empty.
*/
public static <R> String toListString(Collection<R> list, String separator,
Function<? super R, ? extends String> mapper)
{
if (list == null || list.isEmpty())
{
return null;
}
return list.stream()
.map(mapper)
.collect(Collectors.joining(separator));
}
and the appropriate inverse funtion:
/**
* @param list a list of values, separated by the specified separator
* @param separator used to join the values. NOTE: the separator is interpreted as a regular expression.
* @param mapper the function to map a single string to a value.
* @return a list of the values. Empty if string is null or empty.
*/
public static <R> List<R> fromListString(String list, String separator,
Function<? super String, ? extends R> mapper)
{
if (list == null || list.isEmpty())
{
return new ArrayList<>();
}
return Arrays.stream(list.trim().split(separator))
.map(mapper)
.collect(Collectors.toCollection(ArrayList::new));
}
If performance is an issue I would opt for the classic loop approach:
StringBuilder s = new StringBuilder();
for(R r : list){
if (s.length() != 0)
s.append(separator);
s.append(mapper.apply(r));
}
return s.toString();
and:
List<R> result = new ArrayList<>();
for (String s : list.trim().split(separator)){
result.add(mapper.apply(s));
}
return result;