Java: converta Lista <> em uma String

595

JavaScript tem Array.join()

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

Java tem algo assim? Eu sei que posso criar algo com o 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();
}

... mas não faz sentido fazer isso se algo assim já faz parte do JDK.

Jason S
fonte
1
Veja também esta pergunta para obter listas
Casebash
18
Não estritamente relacionados, mas android tem um construído em função de juntar-se como parte de sua classe textutils: developer.android.com/reference/android/text/... , java.lang.Iterable)
Xavi
16
O Java 8 tem um String.join()método Ter um olhar para esta resposta se você estiver usando Java 8 (ou mais recente) stackoverflow.com/a/22577565/1115554
micha

Respostas:

763

Com o Java 8, você pode fazer isso sem nenhuma biblioteca de terceiros.

Se você deseja ingressar em uma coleção de seqüências de caracteres, pode usar o novo método String.join () :

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

Se você tem uma coleção com outro tipo que não String, pode usar a API do Stream com o coletor associado :

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"

A StringJoinerclasse também pode ser útil.

micha
fonte
7
Infelizmente, o String.join aceita apenas CharSequences e não como seria de esperar objetos. Não tenho certeza se é nulo seguro
Marc
@MarcassertThat(String.join(", ", Lists.newArrayList("1", null)), is("1, null"));
Sanghyun Lee
StringJoiner é especialmente útil se alguém quiser se juntar a algo como [a, b, c]incluir chaves.
koppor
@Marc String.join também aceita Iterable<CharSequence>; Relação da interface:Iterable -> Collection -> List
aff
306

Todas as referências ao Apache Commons são boas (e é isso que a maioria das pessoas usa), mas acho que o equivalente do Guava , Joiner , tem uma API muito melhor.

Você pode fazer o caso de junção simples com

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

mas também lida facilmente com nulos:

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

ou

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

e (útil o suficiente para usá-lo em vez de commons-lang), a capacidade de lidar com o Google Maps:

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

o que é extremamente útil para depuração etc.

Cowan
fonte
10
obrigado pela ficha! Nosso Joiner também oferece a opção de anexar diretamente a um Appendable (como um StringBuilder ou qualquer gravador) sem criar Strings intermediárias, que a biblioteca do Apache parece não ter.
Kevin Bourrillion
2
Isso é ótimo, mas você pode adicionar um .useForLastSeparator()método ou outro similar? Dessa forma, você pode obter algo como "e" entre apenas os dois últimos itens (com "," nos demais).
Jeff Evans
2
Sim, é seguro para threads. O único estado salvo em um marceneiro é o separator(que é final). Tudo o que vi no Guava, onde costumava usar um equivalente do Apache Commons, foi muito melhor (leia-se: mais limpo, mais rápido, mais seguro e apenas mais robusto em geral) no Guava, com menos falhas nas bordas e problemas de segurança de threads e menor espaço de memória. Seu principal guia "da melhor maneira possível" parece valer até agora.
Shadow Man
Se você precisar seguir na direção oposta (ou seja, dividir uma String em pedaços), consulte a classe Guava Splitter - também muito bem projetada / implementada.
Matt Passell
No Java 8, há um String.join()método e uma StringJoinerclasse.
theTechnoKid
138

Não fora da caixa, mas muitas bibliotecas têm similar:

Commons Lang:

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

Primavera:

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

No Android, você pode usar a classe TextUtils .

TextUtils.join(" and ", names);
Rafal Enden
fonte
11
Eu estava procurando por isso. String.joinrequer min api nível 26 no android.
precisa saber é
49

Não, não existe esse método de conveniência na API Java padrão.

Não é de surpreender que o Apache Commons forneça isso em sua classe StringUtils , caso você não queira escrevê-lo.

Bart Kiers
fonte
11
Sempre me incomodou que String tivesse uma divisão, mas não uma junção. De certa forma, faz sentido, mas é irritante que não haja pelo menos um método estático para ele em String.
Powerlord
3
Estou com você Bemrose. Pelo menos eles nos deram um isEmpty()método em vez de um (estática) join()método em String...: rollseyes: :)
Bart Kiers
41

Três possibilidades no 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
fonte
27

Com um coletor java 8, isso pode ser feito com o seguinte código:

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

Além disso, a solução mais simples em java 8:

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

ou

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

Eu escrevi este (eu uso para beans e exploit toString, então não escreva 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();
}

mas Collectionnão é suportado pelo JSP, então, para o TLD, escrevi:

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();
}

e colocar em .tldarquivo:

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

e use-o em arquivos JSP como:

<%@taglib prefix="funnyFmt" uri="tag:com.core.util,2013:funnyFmt"%>
${funnyFmt:join(books, ", ")}
gavenkoa
fonte
1
sugerir mudar Collection<>para Iterable<>?
Jason S
@JasonS +1. Bom ponto, mas leia stackoverflow.com/questions/1159797/…
gavenkoa
19

O código que você possui é a maneira correta de fazer isso, se você quiser usar o JDK sem nenhuma biblioteca externa. Não existe um "one-liner" simples que você possa usar no JDK.

Se você pode usar bibliotecas externas, recomendo que você analise a classe org.apache.commons.lang.StringUtils na biblioteca do Apache Commons.

Um exemplo de uso:

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

Uma maneira ortodoxa de alcançá-lo é definindo uma nova função:

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();
    }
}

Amostra:

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);

Resultado:

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

Daniel De León
fonte
5
atribuir a um parâmetro de método o mesmo nome que o próprio método provavelmente não é a melhor ideia. separatorseria muito mais sugestivo.
Ccpizza
10

Solução Java 8 com java.util.StringJoiner

O Java 8 tem uma StringJoinerclasse. Mas você ainda precisa escrever um pouco de clichê, porque é Java.

StringJoiner sj = new StringJoiner(" and ", "" , "");
String[] names = {"Bill", "Bob", "Steve"};
for (String name : names) {
   sj.add(name);
}
System.out.println(sj);
klingt.net
fonte
Você não precisa escrever um pouco de clichê se usar o método mais conveniente String.join () .
Andy Thomas
9

Você pode usar a biblioteca apache commons, que possui uma classe StringUtils e um método de junção.

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

Observe que o link acima pode se tornar obsoleto ao longo do tempo; nesse caso, você pode simplesmente pesquisar na web por "apache commons StringUtils", o que deve permitir que você encontre a referência mais recente.

(referenciado neste encadeamento) Java equivalentes de C # String.Format () e String.Join ()

dcp
fonte
7

Você consegue fazer isso:

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);

Ou uma linha (somente quando você não deseja '[' e ']')

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

Espero que isto ajude.

NawaMan
fonte
1
Isso não anexará a conjunção de escolha.
Hexium
OOPS !! Me desculpe, eu não vi isso.
NawaMan 17/11/2009
6

Uma maneira divertida de fazer isso com o JDK puro, em uma linha de serviço:

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);

Resultado:

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


Uma maneira não muito perfeita e não muito 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);

Resultado:

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

Daniel De León
fonte
Experimente com "[Bill]", "1,2,3" e "Apple] [". Criativo, mas possui casos em que está incorreto.
Jason S
1
Agora tente com "~, ~". Você não pode criar um programa com esse tipo de arquitetura completamente à prova de balas.
Jason S
Sim, eu sei ... removi o voto negativo. Mas você deve pensar um pouco mais cuidadosamente sobre a publicação de respostas aqui, especialmente para perguntas como essa que têm vários anos de idade. As respostas não apenas são imediatamente úteis para o pôster original, mas também aparecem nas pesquisas do Google. (De fato, para perguntas com vários anos de idade, novas respostas podem não ter valor para o pôster original.) As soluções que apresentam desvantagens ou erros podem ser prejudiciais para as pessoas que a encontram mais tarde fora de contexto.
Jason S
1
Eu aprendo muito aqui com outras pessoas que postam como a minha, com uma solução não perfeita, mas muito rica em conhecimento e com idéias paralelas. Relaxe para os outros, cada um deve entender o que cada linha de seu código faz. Lembre-se de que programar é como uma arte, e mesmo cada linha de código significa algo sobre a personalidade do programador. E sim, não usarei esse código na produção!
Daniel De León
4

Se você estiver usando o Eclipse Collections (anteriormente GS Collections ), poderá usar o 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);

Se você pode converter o seu Listem um tipo de Coleções Eclipse, poderá se livrar do adaptador.

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

Se você quiser apenas uma sequência separada por vírgula, poderá usar a versão makeString()que não requer parâmetros.

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

Nota: Sou um colaborador das Coleções Eclipse.

Craig P. Motlin
fonte
3

Com o java 1.8 stream pode ser usado,

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
fonte
3

EDITAR

Percebo também o toString()problema de implementação subjacente e sobre o elemento que contém o separador, mas pensei que estava sendo paranóico.

Como tenho dois comentários a esse respeito, vou alterar minha resposta para:

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() );
}

O que parece bastante com a pergunta original.

Portanto, se você não quiser adicionar o frasco inteiro ao seu projeto, use-o.

Eu acho que não há nada errado com o seu código original. Na verdade, a alternativa sugerida por todos parece quase a mesma (embora faça várias validações adicionais)

Aqui está, junto com a licença 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();
}

Agora sabemos, obrigado código aberto

OscarRyz
fonte
Isso funcionará agora, mas você não pode ter certeza de como List.toString () se comportará no futuro. Eu nunca confiaria em uma operação toString () que não imprimisse uma seqüência de informações sobre o objeto em questão. Em sua defesa, você não é o único que propõe esta solução.
21119 Hans Doggen
E se o conteúdo de um elemento da lista contiver a substring ", "?
Bart Kiers
@ Hans & @ Bart: Você está certo. Eu pensei que ninguém mais notaria: P Estou mudando minha resposta.
OscarRyz 17/11/2009
Depende do caso de uso. Para propósitos de depuração, toString () é excelente para IMO ... desde que você não dependa de formatação específica. E mesmo assim, se você escrever um teste para seu código, quaisquer alterações futuras serão detectadas.
Akostadinov 28/10/12
2

A API Guava do Google também possui .join (), embora (como deve ser óbvio com as outras respostas), o Apache Commons seja praticamente o padrão aqui.

Dean J
fonte
1

O Java 8 traz o

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

método, que é nulo e seguro usando prefix + suffixvalores nulos.

Pode ser usado da seguinte maneira:

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

O Collectors.joining(CharSequence delimiter)método apenas chama joining(delimiter, "", "")internamente.

thg
fonte
0

Você pode usar isso no StringUtils do Spring Framework. Eu sei que ele já foi mencionado, mas você pode simplesmente pegar esse código e ele funciona imediatamente, sem a necessidade do 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
fonte
-3

Tente o seguinte:

java.util.Arrays.toString(anArray).replaceAll(", ", ",")
                .replaceFirst("^\\[","").replaceFirst("\\]$","");
jorge
fonte
1
Provavelmente porque a pergunta exige o uso de uma lista versus uma matriz? encolher de ombros
Nick Coelius
4
Porque se você tiver um "," em suas strings, não funcionará.
Cyrille Pontvieux 03/08/2015