Java: convierte List <String> en una cadena

596

JavaScript tiene Array.join()

js>["Bill","Bob","Steve"].join(" and ")
Bill and Bob and Steve

¿Java tiene algo como esto? Sé que puedo improvisar algo con StringBuilder:

static public String join(List<String> list, String conjunction)
{
   StringBuilder sb = new StringBuilder();
   boolean first = true;
   for (String item : list)
   {
      if (first)
         first = false;
      else
         sb.append(conjunction);
      sb.append(item);
   }
   return sb.toString();
}

... pero no tiene sentido hacerlo si algo así ya forma parte del JDK.

Jason S
fuente
1
Ver también la cuestión de las listas
Casebash
18
No está estrictamente relacionado, pero Android tiene una función de unión integrada como parte de su clase TextUtils: developer.android.com/reference/android/text/… , java.lang.Iterable)
Xavi
16
Java 8 tiene un String.join()método. Eche un vistazo a esta respuesta si está utilizando Java 8 (o más reciente) stackoverflow.com/a/22577565/1115554
micha

Respuestas:

764

Con Java 8 puede hacer esto sin ninguna biblioteca de terceros.

Si desea unirse a una Colección de cadenas, puede usar el nuevo método String.join () :

List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"

Si tiene una colección con otro tipo que no sea String, puede usar la API Stream con el recopilador de unión :

List<Person> list = Arrays.asList(
  new Person("John", "Smith"),
  new Person("Anna", "Martinez"),
  new Person("Paul", "Watson ")
);

String joinedFirstNames = list.stream()
  .map(Person::getFirstName)
  .collect(Collectors.joining(", ")); // "John, Anna, Paul"

La StringJoinerclase también puede ser útil.

micha
fuente
77
Desafortunadamente, String.join solo acepta CharSequences y no como cabría esperar Objetos. Ni siquiera estoy seguro si es nullsafe
Marc
@MarcassertThat(String.join(", ", Lists.newArrayList("1", null)), is("1, null"));
Sanghyun Lee
StringJoiner es especialmente útil si uno quiere unirse a algo como [a, b, c]incluir las llaves.
koppor
@Marc String.join también acepta Iterable<CharSequence>; Relación de interfaz:Iterable -> Collection -> List
aff
306

Todas las referencias a Apache Commons están bien (y eso es lo que usa la mayoría de la gente), pero creo que el equivalente de Guava , Joiner , tiene una API mucho mejor.

Puede hacer el caso de unión simple con

Joiner.on(" and ").join(names)

pero también lidiar fácilmente con nulos:

Joiner.on(" and ").skipNulls().join(names);

o

Joiner.on(" and ").useForNull("[unknown]").join(names);

y (lo suficientemente útil en lo que a mí respecta para usarlo en lugar de commons-lang), la capacidad de manejar Mapas:

Map<String, Integer> ages = .....;
String foo = Joiner.on(", ").withKeyValueSeparator(" is ").join(ages);
// Outputs:
// Bill is 25, Joe is 30, Betty is 35

que es extremadamente útil para depurar, etc.

Cowan
fuente
10
gracias por el enchufe! Nuestro Joiner también le da la opción de agregar directamente a un Appendable (como StringBuilder o cualquier Escritor) sin crear cadenas intermedias, que la biblioteca de Apache parece carecer.
Kevin Bourrillion
2
Esto es genial, pero ¿puedes agregar un .useForLastSeparator()método similar? De esa manera, podría obtener algo como "y" entre solo los dos últimos elementos (con "," para el resto).
Jeff Evans
2
Sí, es seguro para subprocesos. El único estado guardado en una unión es el separator(que es final). Todo lo que he visto en Guava, donde solía usar un equivalente de Apache Commons, ha sido mucho mejor (léase: más limpio, más rápido, más seguro y más robusto en general) en Guava con menos fallas en los bordes y problemas de seguridad de roscas y menor huella de memoria. Su director rector de "la mejor manera posible" parece ser cierto hasta ahora.
Shadow Man
Si necesita ir en la dirección opuesta (es decir, dividir una Cadena en pedazos), consulte el Divisor de clase de guayaba, también muy bien diseñado / implementado.
Matt Passell
En Java 8, hay un String.join()método y una StringJoinerclase.
theTechnoKid
138

No está listo para usar, pero muchas bibliotecas tienen similares:

Lang Comunes:

org.apache.commons.lang.StringUtils.join(list, conjunction);

Primavera:

org.springframework.util.StringUtils.collectionToDelimitedString(list, conjunction);
Arne Burmeister
fuente
125

En Android puedes usar la clase TextUtils .

TextUtils.join(" and ", names);
Rafal Enden
fuente
11
Yo estaba buscando esto. String.joinrequiere un nivel mínimo de API 26 en Android.
okarakose
49

No, no existe tal método de conveniencia en la API estándar de Java.

No es sorprendente que Apache Commons proporcione tal cosa en su clase StringUtils en caso de que no quiera escribirlo usted mismo.

Bart Kiers
fuente
11
Siempre me ha molestado que String tenga una división pero no una unión. En cierto modo, tiene sentido que lo haga, pero es molesto que no haya al menos un método estático para ello en String.
Powerlord
3
Estoy contigo Bemrose. Al menos nos dieron un isEmpty()método en lugar de un método (estático) join()en String...: rollseyes: :)
Bart Kiers
41

Tres posibilidades en Java 8:

List<String> list = Arrays.asList("Alice", "Bob", "Charlie")

String result = String.join(" and ", list);

result = list.stream().collect(Collectors.joining(" and "));

result = list.stream().reduce((t, u) -> t + " and " + u).orElse("");
amra
fuente
27

Con un colector java 8, esto se puede hacer con el siguiente código:

Arrays.asList("Bill", "Bob", "Steve").stream()
.collect(Collectors.joining(" and "));

Además, la solución más simple en Java 8:

String.join(" and ", "Bill", "Bob", "Steve");

o

String.join(" and ", Arrays.asList("Bill", "Bob", "Steve"));
aifa
fuente
21

Escribí este (lo uso para beans y exploit toString, así que no escribas Collection<String>):

public static String join(Collection<?> col, String delim) {
    StringBuilder sb = new StringBuilder();
    Iterator<?> iter = col.iterator();
    if (iter.hasNext())
        sb.append(iter.next().toString());
    while (iter.hasNext()) {
        sb.append(delim);
        sb.append(iter.next().toString());
    }
    return sb.toString();
}

pero Collectionno es compatible con JSP, así que para TLD escribí:

public static String join(List<?> list, String delim) {
    int len = list.size();
    if (len == 0)
        return "";
    StringBuilder sb = new StringBuilder(list.get(0).toString());
    for (int i = 1; i < len; i++) {
        sb.append(delim);
        sb.append(list.get(i).toString());
    }
    return sb.toString();
}

y poner a .tldarchivo:

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
    <function>
        <name>join</name>
        <function-class>com.core.util.ReportUtil</function-class>
        <function-signature>java.lang.String join(java.util.List, java.lang.String)</function-signature>
    </function>
</taglib>

y usarlo en archivos JSP como:

<%@taglib prefix="funnyFmt" uri="tag:com.core.util,2013:funnyFmt"%>
${funnyFmt:join(books, ", ")}
gavenkoa
fuente
1
sugerir cambiar Collection<>a Iterable<>?
Jason S
@JasonS +1. Buen punto, pero lea stackoverflow.com/questions/1159797/…
gavenkoa
19

El código que tiene es la forma correcta de hacerlo si desea hacerlo utilizando JDK sin ninguna biblioteca externa. No hay un simple "one-liner" que pueda usar en JDK.

Si puede usar libs externas, le recomiendo que busque en la clase org.apache.commons.lang.StringUtils en la biblioteca Apache Commons.

Un ejemplo de uso:

List<String> list = Arrays.asList("Bill", "Bob", "Steve");
String joinedResult = StringUtils.join(list, " and ");
Juha Syrjälä
fuente
17

Una forma ortodoxa de lograrlo es definiendo una nueva función:

public static String join(String joinStr, String... strings) {
    if (strings == null || strings.length == 0) {
        return "";
    } else if (strings.length == 1) {
        return strings[0];
    } else {
        StringBuilder sb = new StringBuilder(strings.length * 1 + strings[0].length());
        sb.append(strings[0]);
        for (int i = 1; i < strings.length; i++) {
            sb.append(joinStr).append(strings[i]);
        }
        return sb.toString();
    }
}

Muestra:

String[] array = new String[] { "7, 7, 7", "Bill", "Bob", "Steve",
        "[Bill]", "1,2,3", "Apple ][","~,~" };

String joined;
joined = join(" and ","7, 7, 7", "Bill", "Bob", "Steve", "[Bill]", "1,2,3", "Apple ][","~,~");
joined = join(" and ", array); // same result

System.out.println(joined);

Salida:

7, 7, 7 y Bill y Bob y Steve y [Bill] y 1,2,3 y Apple] [y ~, ~

Daniel De León
fuente
55
dar a un parámetro de método el mismo nombre que el método en sí mismo probablemente no sea la mejor idea. separatorSería mucho más sugerente.
ccpizza
10

Solución Java 8 con java.util.StringJoiner

Java 8 tiene una StringJoinerclase. Pero aún necesita escribir un poco de repetitivo, porque es Java.

StringJoiner sj = new StringJoiner(" and ", "" , "");
String[] names = {"Bill", "Bob", "Steve"};
for (String name : names) {
   sj.add(name);
}
System.out.println(sj);
klingt.net
fuente
No necesita escribir un poco de repetitivo si usa el método más conveniente String.join () .
Andy Thomas
9

Puede usar la biblioteca apache commons que tiene una clase StringUtils y un método de combinación.

Consulte este enlace: https://commons.apache.org/proper/commons-lang/javadocs/api.2.0/org/apache/commons/lang/StringUtils.html

Tenga en cuenta que el enlace anterior puede volverse obsoleto con el tiempo, en cuyo caso puede buscar "apache commons StringUtils" en la web, lo que debería permitirle encontrar la última referencia.

(referenciado desde este hilo) Equivalentes Java de C # String.Format () y String.Join ()

dcp
fuente
7

Puedes hacerlo:

String aToString = java.util.Arrays.toString(anArray);
// Do not need to do this if you are OK with '[' and ']'
aToString = aToString.substring(1, aToString.length() - 1);

O una línea (solo cuando no desea '[' y ']')

String aToString = java.util.Arrays.toString(anArray).substring(1).replaceAll("\\]$", "");

Espero que esto ayude.

NawaMan
fuente
1
Esto no agregará la conjunción de elección.
hexium
¡Vaya! Lo siento, no vi eso.
NawaMan el
6

Una forma divertida de hacerlo con JDK puro, en una sola línea de servicio:

String[] array = new String[] { "Bill", "Bob", "Steve","[Bill]","1,2,3","Apple ][" };
String join = " and ";

String joined = Arrays.toString(array).replaceAll(", ", join)
        .replaceAll("(^\\[)|(\\]$)", "");

System.out.println(joined);

Salida:

Bill y Bob y Steve y [Bill] y 1,2,3 y Apple] [


¡Una forma no demasiado perfecta ni demasiado divertida!

String[] array = new String[] { "7, 7, 7","Bill", "Bob", "Steve", "[Bill]",
        "1,2,3", "Apple ][" };
String join = " and ";

for (int i = 0; i < array.length; i++) array[i] = array[i].replaceAll(", ", "~,~");
String joined = Arrays.toString(array).replaceAll(", ", join)
        .replaceAll("(^\\[)|(\\]$)", "").replaceAll("~,~", ", ");

System.out.println(joined);

Salida:

7, 7, 7 y Bill y Bob y Steve y [Bill] y 1,2,3 y Apple] [

Daniel De León
fuente
Pruébelo con "[Bill]", "1,2,3" y "Apple] [". Creativo, pero tiene casos cuando es incorrecto.
Jason S
1
Ahora inténtalo con "~, ~". No se puede hacer un programa con este tipo de arquitectura completamente a prueba de balas.
Jason S
Sí, lo sé ... he eliminado el voto negativo. Pero debe pensar un poco más cuidadosamente sobre publicar respuestas aquí, especialmente para preguntas como esta que tienen varios años. Las respuestas no solo son útiles inmediatamente para el póster original, sino que también aparecen en las búsquedas de Google. (De hecho, para las preguntas que tienen varios años, las respuestas nuevas pueden no tener valor para el póster original). Las soluciones que tienen inconvenientes o errores pueden ser perjudiciales para las personas que lo encuentran fuera de contexto.
Jason S
1
Aprendo mucho aquí de otras publicaciones como la mía, con una solución no perfecta, pero realmente rica en conocimiento y pensamiento paralelo. Relájese para los demás, cada uno debe entender lo que hace cada línea de su código. Recuerde que programar es como un arte, e incluso cada línea de código significa algo sobre la personalidad del programador. ¡Y sí, no usaré este código en producción!
Daniel De León
4

Si está utilizando Eclipse Collections (anteriormente GS Collections ), puede utilizar el makeString()método.

List<String> list = Arrays.asList("Bill", "Bob", "Steve");

String string = ListAdapter.adapt(list).makeString(" and ");

Assert.assertEquals("Bill and Bob and Steve", string);

Si puede convertirlo Lista un tipo de Colecciones Eclipse, puede deshacerse del adaptador.

MutableList<String> list = Lists.mutable.with("Bill", "Bob", "Steve");
String string = list.makeString(" and ");

Si solo desea una cadena separada por comas, puede usar la versión de makeString()que no toma parámetros.

Assert.assertEquals(
    "Bill, Bob, Steve", 
    Lists.mutable.with("Bill", "Bob", "Steve").makeString());

Nota: Soy un committer para Eclipse Collections.

Craig P. Motlin
fuente
3

Con java 1.8 se puede usar stream,

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
List<String> list = Arrays.asList("Bill","Bob","Steve").
String str = list.stream().collect(Collectors.joining(" and "));
Saurabh
fuente
3

EDITAR

También noto el toString()problema de implementación subyacente y sobre el elemento que contiene el separador, pero pensé que estaba siendo paranoico.

Como tengo dos comentarios al respecto, estoy cambiando mi respuesta a:

static String join( List<String> list , String replacement  ) {
    StringBuilder b = new StringBuilder();
    for( String item: list ) { 
        b.append( replacement ).append( item );
    }
    return b.toString().substring( replacement.length() );
}

Que se parece bastante a la pregunta original.

Entonces, si no tiene ganas de agregar todo el jar a su proyecto, puede usar esto.

Creo que no hay nada malo con tu código original. En realidad, la alternativa que todo el mundo sugiere se ve casi igual (aunque hace varias validaciones adicionales)

Aquí está, junto con la licencia Apache 2.0.

public static String join(Iterator iterator, String separator) {
    // handle null, zero and one elements before building a buffer
    if (iterator == null) {
        return null;
    }
    if (!iterator.hasNext()) {
        return EMPTY;
    }
    Object first = iterator.next();
    if (!iterator.hasNext()) {
        return ObjectUtils.toString(first);
    }

    // two or more elements
    StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
    if (first != null) {
        buf.append(first);
    }

    while (iterator.hasNext()) {
        if (separator != null) {
            buf.append(separator);
        }
        Object obj = iterator.next();
        if (obj != null) {
            buf.append(obj);
        }
    }
    return buf.toString();
}

Ahora lo sabemos, gracias código abierto

OscarRyz
fuente
Esto funcionará ahora, pero no puede estar seguro de cómo se comportará List.toString () en el futuro. Nunca confiaría en una operación toString () que no sea imprimir alguna cadena informativa sobre el objeto en cuestión. En su defensa, no es el único que propone esta solución.
Hans Doggen
¿Qué sucede si el contenido de un elemento en la lista contiene la subcadena ", "?
Bart Kiers
@Hans y @Bart: Tienes razón. Pensé que nadie más se daría cuenta: P Estoy cambiando mi respuesta.
OscarRyz
Depende del caso de uso. Para fines de depuración toString () está bien IMO ... siempre que no confíe en un formato particular. Y aun así, si escribe pruebas para su código, se detectarán los cambios futuros.
akostadinov
2

La API de guayaba de Google también tiene .join (), aunque (como debería ser obvio con las otras respuestas), Apache Commons es más o menos el estándar aquí.

Dean J
fuente
1

Java 8 trae el

Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

método, que es nulo seguro mediante el uso prefix + suffixde valores nulos.

Se puede usar de la siguiente manera:

String s = stringList.stream().collect(Collectors.joining(" and ", "prefix_", "_suffix"))

El Collectors.joining(CharSequence delimiter)método solo llama joining(delimiter, "", "")internamente.

thg
fuente
0

Puede usar esto desde StringUtils de Spring Framework. Sé que ya se ha mencionado, pero en realidad puedes tomar este código y funciona de inmediato, sin necesidad de Spring.

// from https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/StringUtils.java

/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class StringUtils {
    public static String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix) {
        if(coll == null || coll.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        Iterator<?> it = coll.iterator();
        while (it.hasNext()) {
            sb.append(prefix).append(it.next()).append(suffix);
            if (it.hasNext()) {
                sb.append(delim);
            }
        }
        return sb.toString();
    }
}
EpicPandaForce
fuente
-3

Prueba esto:

java.util.Arrays.toString(anArray).replaceAll(", ", ",")
                .replaceFirst("^\\[","").replaceFirst("\\]$","");
jorge
fuente
1
¿Probablemente porque la pregunta requiere el uso de una Lista versus una Matriz? encogimiento de hombros
Nick Coelius
44
Porque si tienes un "," en tus cadenas no funcionará.
Cyrille Pontvieux