it-swarm-pt.tech

Qual é a melhor maneira de construir uma string de itens delimitados em Java?

Enquanto trabalhava em um aplicativo Java, recentemente precisei montar uma lista de valores delimitados por vírgulas para passar para outro serviço da Web sem saber quantos elementos haveria com antecedência. O melhor que eu pude pensar em cima da minha cabeça foi algo assim:

public String appendWithDelimiter( String original, String addition, String delimiter ) {
    if ( original.equals( "" ) ) {
        return addition;
    } else {
        return original + delimiter + addition;
    }
}

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Eu percebo que isso não é particularmente eficiente, uma vez que existem strings sendo criadas em todo o lugar, mas eu estava indo para clareza mais do que otimização.

Em Ruby, posso fazer algo assim, que é muito mais elegante:

parameterArray = [];
parameterArray << "elementName" if condition;
parameterArray << "anotherElementName" if anotherCondition;
parameterString = parameterArray.join(",");

Mas como Java não tem um comando de junção, não consegui descobrir nada equivalente.

Então, qual é a melhor maneira de fazer isso em Java?

280
Sean McMains

Pre Java 8:

O commons do Apache lang é seu amigo aqui - ele fornece um método de junção muito semelhante ao que você se refere no Ruby:

StringUtils.join(Java.lang.Iterable,char)


Java 8:

Java 8 fornece junção fora da caixa via StringJoiner e String.join(). Os trechos abaixo mostram como você pode usá-los:

StringJoiner

StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); // "01,02,03"

String.join(CharSequence delimiter, CharSequence... elements))

String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join(" ", strings);
//message returned is: "Java is cool"
490
Martin Gladdish

Você pode escrever um pequeno método utilitário de junção que funcione em Java.util.Lists

public static String join(List<String> list, String delim) {

    StringBuilder sb = new StringBuilder();

    String loopDelim = "";

    for(String s : list) {

        sb.append(loopDelim);
        sb.append(s);            

        loopDelim = delim;
    }

    return sb.toString();
}

Então use assim:

    List<String> list = new ArrayList<String>();

    if( condition )        list.add("elementName");
    if( anotherCondition ) list.add("anotherElementName");

    join(list, ",");
52
Rob Dickerson

No caso do Android, a classe StringUtils do commons não está disponível, então para isso eu usei

Android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)

http://developer.Android.com/reference/Android/text/TextUtils.html

46
Kevin

O biblioteca de goiaba do Google tem com.google.common.base.Joiner classe que ajuda a resolver tais tarefas.

Amostras:

"My pets are: " + Joiner.on(", ").join(Arrays.asList("rabbit", "parrot", "dog")); 
// returns "My pets are: rabbit, parrot, dog"

Joiner.on(" AND ").join(Arrays.asList("field1=1" , "field2=2", "field3=3"));
// returns "field1=1 AND field2=2 AND field3=3"

Joiner.on(",").skipNulls().join(Arrays.asList("London", "Moscow", null, "New York", null, "Paris"));
// returns "London,Moscow,New York,Paris"

Joiner.on(", ").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona", "FC Bayern", null, null, "Chelsea FC", "AC Milan"));
// returns "FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan"

Aqui está um artigo sobre os utilitários de string do Guava .

31
Alex K

Em Java 8 você pode usar String.join() :

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

Também dê uma olhada em esta resposta para um exemplo da Stream API.

25
micha

Você pode generalizá-lo, mas não há junção em Java, como você bem diz.

Isto pode funcionar melhor.

public static String join(Iterable<? extends CharSequence> s, String delimiter) {
    Iterator<? extends CharSequence> iter = s.iterator();
    if (!iter.hasNext()) return "";
    StringBuilder buffer = new StringBuilder(iter.next());
    while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
    return buffer.toString();
}
20
Vinko Vrsalovic

Use uma abordagem baseada em Java.lang.StringBuilder ! ("Uma sequência mutável de caracteres")

Como você mencionou, todas essas concatenações de strings estão criando Strings por toda parte. StringBuilder não fará isso.

Por que StringBuilder em vez de StringBuffer ? Do StringBuilder javadoc:

Sempre que possível, recomenda-se que essa classe seja usada em preferência a StringBuffer, pois ela será mais rápida na maioria das implementações.

15
Stu Thompson

em Java 8 você pode fazer isso como:

list.stream().map(Object::toString)
                        .collect(Collectors.joining(delimiter));

se a lista tiver nulos, você poderá usar:

list.stream().map(String::valueOf)
                .collect(Collectors.joining(delimiter))
12
gladiator

Eu usaria as Coleções do Google. Existe uma facilidade de Nice Join.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Mas se eu quisesse escrever por conta própria,

package util;

import Java.util.ArrayList;
import Java.util.Iterable;
import Java.util.Collections;
import Java.util.Iterator;

public class Utils {
    // accept a collection of objects, since all objects have toString()
    public static String join(String delimiter, Iterable<? extends Object> objs) {
        if (objs.isEmpty()) {
            return "";
        }
        Iterator<? extends Object> iter = objs.iterator();
        StringBuilder buffer = new StringBuilder();
        buffer.append(iter.next());
        while (iter.hasNext()) {
            buffer.append(delimiter).append(iter.next());
        }
        return buffer.toString();
    }

    // for convenience
    public static String join(String delimiter, Object... objs) {
        ArrayList<Object> list = new ArrayList<Object>();
        Collections.addAll(list, objs);
        return join(delimiter, list);
    }
}

Eu acho que funciona melhor com uma coleção de objetos, já que agora você não precisa converter seus objetos em strings antes de se juntar a eles.

10
Eric Normand

Apache commons A classe StringUtils tem um método de junção.

8
Alejandro

Java 8

stringCollection.stream().collect(Collectors.joining(", "));
4
gstackoverflow

E um mínimo (se você não quiser incluir o Apache Commons vs Guava nas dependências do projeto apenas por se juntar a strings)

/**
 *
 * @param delim : String that should be kept in between the parts
 * @param parts : parts that needs to be joined
 * @return  a String that's formed by joining the parts
 */
private static final String join(String delim, String... parts) {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < parts.length - 1; i++) {
        builder.append(parts[i]).append(delim);
    }
    if(parts.length > 0){
        builder.append(parts[parts.length - 1]);
    }
    return builder.toString();
}
3
Thamme Gowda

Use StringBuilder e a classe Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator(", ");
for (String each : list) {
    buf.append(sep).append(each);
}

O separador envolve um delimitador. O delimitador é retornado pelo método toString do Separator, a menos que na primeira chamada que retorna a string vazia!

Código-fonte para a classe Separator

public class Separator {

    private boolean skipFirst;
    private final String value;

    public Separator() {
        this(", ");
    }

    public Separator(String value) {
        this.value = value;
        this.skipFirst = true;
    }

    public void reset() {
        skipFirst = true;
    }

    public String toString() {
        String sep = skipFirst ? "" : value;
        skipFirst = false;
        return sep;
    }

}
3
akuhn

Você pode usar o tipo StringBuilder do Java para isso. Há também StringBuffer, mas contém uma lógica extra de segurança de thread que é frequentemente desnecessária.

3
Kent Boogaart

Se você estiver usando coleções do Eclipse , você pode usar makeString() ou appendString().

makeString() retorna uma representação String, similar a toString().

Tem três formas

  • makeString(start, separator, end)
  • makeString(separator) defaults start e end para strings vazias
  • makeString() padroniza o separador para ", " (vírgula e espaço)

Exemplo de código:

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));

appendString() é semelhante a makeString(), mas é anexado a um Appendable (como StringBuilder) e é void. Tem as mesmas três formas, com um primeiro argumento adicional, o Anexável.

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());

Se você não conseguir converter sua coleção em um tipo Eclipse Collections, basta adaptá-la ao adaptador relevante.

List<Object> list = ...;
ListAdapter.adapt(list).makeString(",");

Nota: Eu sou um committer para coleções do Eclipse.

2
Craig P. Motlin

Se você estiver usando o Spring MVC, tente seguir as etapas a seguir.

import org.springframework.util.StringUtils;

List<String> groupIds = new List<String>;   
groupIds.add("a");    
groupIds.add("b");    
groupIds.add("c");

String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray());

Isso resultará em a,b,c

2
Ankit Lalan

Por que não escrever seu próprio método join ()? Isso levaria como coleção de parâmetros de Strings e um String delimitador. Dentro do método, itere a coleção e construa o resultado em um StringBuffer.

2
Dave Costa

Com Java 5 variáveis ​​args, então você não precisa colocar todas as suas strings em uma coleção ou array explicitamente:

import junit.framework.Assert;
import org.junit.Test;

public class StringUtil
{
    public static String join(String delim, String... strings)
    {
        StringBuilder builder = new StringBuilder();

        if (strings != null)
        {
            for (String str : strings)
            {
                if (builder.length() > 0)
                {
                    builder.append(delim).append(" ");
                }
                builder.append(str);
            }
        }           
        return builder.toString();
    }
    @Test
    public void joinTest()
    {
        Assert.assertEquals("", StringUtil.join(",", null));
        Assert.assertEquals("", StringUtil.join(",", ""));
        Assert.assertEquals("", StringUtil.join(",", new String[0]));
        Assert.assertEquals("test", StringUtil.join(",", "test"));
        Assert.assertEquals("foo, bar", StringUtil.join(",", "foo", "bar"));
        Assert.assertEquals("foo, bar, x", StringUtil.join(",", "foo", "bar", "x"));
    }
}
1
Mark B

Tipo Nativo do Java 8

List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));

Objeto Personalizado do Java 8:

List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));
1
Luis Aguilar

Para aqueles que estão em um contexto Spring, a classe StringUtils também é útil:

Existem muitos atalhos úteis como:

  • collectionToCommaDelimitedString (Coleta de coleção)
  • collectionToDelimitedString (Coleta de coleta, Delimitação de cadeia)
  • arrayToDelimitedString (Object [] arr, String delim)

e muitos outros.

Isso pode ser útil se você ainda não estiver usando Java 8 e já estiver em um contexto Spring.

Eu prefiro isso contra o Apache Commons (embora muito bom também) para o suporte da coleção, que é mais fácil assim:

// Encoding Set<String> to String delimited 
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes, ";");

// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString);
1
рüффп

Você provavelmente deve usar um StringBuilder com o método append para construir o seu resultado, mas de outra forma isso é tão bom quanto uma solução que o Java tem a oferecer.

1
newdayrising

Por que você não faz em Java a mesma coisa que você está fazendo em Ruby, que está criando a string separada por delimitadores somente depois de adicionar todas as partes à matriz?

ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep = ""; StringBuffer b = new StringBuffer();
for (String p: parms) {
    b.append(sep);
    b.append(p);
    sep = "yourDelimiter";
}

Você pode querer mover esse loop for em um método auxiliar separado e também usar StringBuilder em vez de StringBuffer ...

Editar: corrigiu a ordem dos anexos.

1
agnul

Então, basicamente algo assim:

public static String appendWithDelimiter(String original, String addition, String delimiter) {

if (original.equals("")) {
    return addition;
} else {
    StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
        sb.append(original);
        sb.append(delimiter);
        sb.append(addition);
        return sb.toString();
    }
}
0
killdash10

Não sei se isso é realmente melhor, mas pelo menos está usando o StringBuilder, que pode ser um pouco mais eficiente.

Abaixo está uma abordagem mais genérica se você puder construir a lista de parâmetros ANTES de fazer qualquer delimitação de parâmetro.

// Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
    StringBuilder sb = new StringBuilder(original);
    if(sb.length()!=0) {
        sb.append(delimiter).append(addition);
    } else {
        sb.append(addition);
    }
    return sb.toString();
}


// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
    StringBuilder sb = new StringBuilder();
    for (String string : strings) {
        if(sb.length()!=0) {
            sb.append(delimiter).append(string);
        } else {
            sb.append(string);
        }
    }

    return sb.toString();
}

public void testAppendWithDelimiters() {
    String string = appendWithDelimitersGeneric(",", "string1", "string2", "string3");
}
0
Mikezx6r

Sua abordagem não é tão ruim, mas você deve usar um StringBuffer em vez de usar o sinal +. O + tem a grande desvantagem de que uma nova instância String está sendo criada para cada operação. Quanto mais tempo sua corda fica, maior a sobrecarga. Então, usar um StringBuffer deve ser o caminho mais rápido:

public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
        if ( original == null ) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(addition);
                return buffer;
        } else {
                buffer.append(delimiter);
                buffer.append(addition);
                return original;
        }
}

Depois de terminar de criar sua string, simplesmente chame toString () no StringBuffer retornado.

0
Yaba
//Note: if you have access to Java5+, 
//use StringBuilder in preference to StringBuffer.  
//All that has to be replaced is the class name.  
//StringBuffer will work in Java 1.4, though.

appendWithDelimiter( StringBuffer buffer, String addition, 
    String delimiter ) {
    if ( buffer.length() == 0) {
        buffer.append(addition);
    } else {
        buffer.append(delimiter);
        buffer.append(addition);
    }
}


StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) { 
    appendWithDelimiter(parameterBuffer, "elementName", "," );
}
if ( anotherCondition ) {
    appendWithDelimiter(parameterBuffer, "anotherElementName", "," );
}

//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString(); 
0
MetroidFan2002

usar dólar é simples como digitar:

String joined = $(aCollection).join(",");

NB: funciona também para Array e outros tipos de dados

Implementação

Internamente, ele usa um truque muito bom:

@Override
public String join(String separator) {
    Separator sep = new Separator(separator);
    StringBuilder sb = new StringBuilder();

    for (T item : iterable) {
        sb.append(sep).append(item);
    }

    return sb.toString();
}

a classe Separator retorna a String vazia somente na primeira vez que é invocada, então retorna o separador:

class Separator {

    private final String separator;
    private boolean wasCalled;

    public Separator(String separator) {
        this.separator = separator;
        this.wasCalled = false;
    }

    @Override
    public String toString() {
        if (!wasCalled) {
            wasCalled = true;
            return "";
        } else {
            return separator;
        }
    }
}
0
dfa

Corrigir resposta Rob Dickerson.

É mais fácil de usar:

public static String join(String delimiter, String... values)
{
    StringBuilder stringBuilder = new StringBuilder();

    for (String value : values)
    {
        stringBuilder.append(value);
        stringBuilder.append(delimiter);
    }

    String result = stringBuilder.toString();

    return result.isEmpty() ? result : result.substring(0, result.length() - 1);
}
0
maXp

Em vez de usar a concatenação de strings, você deve usar o StringBuilder se o seu código não for encadeado, e o StringBuffer se ele for.

0
Aaron

Então, algumas coisas que você pode fazer para sentir que parece que está procurando:

1) Estender classe de lista - e adicione o método de junção a ela. O método de junção simplesmente faria o trabalho de concatenar e adicionar o delimitador (que poderia ser um parâmetro para o método de junção)

2) Parece que Java 7 irá adicionar métodos de extensão a Java - o que permite que você apenas anexe um método específico a uma classe: assim você pode escrever esse método de junção e adicione-o como um método de extensão para Listar ou até para Coleção.

A solução 1 é provavelmente a única realista, agora, apesar de Java 7 ainda não ter saído :) Mas deve funcionar muito bem.

Para usar os dois, basta adicionar todos os itens à Lista ou Coleção como de costume e, em seguida, chamar o novo método personalizado para "ingressar" neles.

0
user13276

Você está fazendo isso um pouco mais complicado do que tem que ser. Vamos começar com o final do seu exemplo:

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Com a pequena mudança de usar um StringBuilder em vez de uma String, isso se torna:

StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
...

Quando estiver pronto (eu suponho que você tem que verificar algumas outras condições também), apenas certifique-se de remover a vírgula de rejeição com um comando como este:

if (parameterString.length() > 0) 
    parameterString.deleteCharAt(parameterString.length() - 1);

E finalmente, pegue a corda que você quer com

parameterString.toString();

Você também pode substituir o "," na segunda chamada para anexar com uma cadeia delimitadora genérica que pode ser definida como qualquer coisa. Se você tem uma lista de coisas que você sabe que precisa anexar (não condicionalmente), você poderia colocar esse código dentro de um método que pega uma lista de strings.

0
cjw2600

Melhoria ligeira [velocidade] da versão do izb:

public static String join(String[] strings, char del)
{
    StringBuilder sb = new StringBuilder();
    int len = strings.length;

    if(len > 1) 
    {
       len -= 1;
    }else
    {
       return strings[0];
    }

    for (int i = 0; i < len; i++)
    {
       sb.append(strings[i]).append(del);
    }

    sb.append(strings[i]);

    return sb.toString();
}
0
java al

Eu pessoalmente, muitas vezes, uso a seguinte solução simples para fins de registro:

List lst = Arrays.asList("ab", "bc", "cd");
String str = lst.toString().replaceAll("[\\[\\]]", "");
0
Philipp Grigoryev

Você pode tentar algo assim:

StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString();
0
martinatime