コレクション/配列/リストからカンマ区切りの文字列を作成する最も洗練された方法は?


98

データベースでの作業中に、クエリ文字列を記述していることに気付きました。この文字列には、リスト/配列/コレクションのwhere句にいくつかの制限を課す必要があります。次のようになります。

select * from customer 
where customer.id in (34, 26, ..., 2);

これを、文字列のコレクションがあり、この文字列のコンマ区切りのリストを1つの文字列だけで作成したいという質問に減らすことで、これを簡略化できます。

これまでに使用した私のアプローチは次のようなものです。

String result = "";
boolean first = true;
for(String string : collectionOfStrings) {
    if(first) {
        result+=string;
        first=false;
    } else {
        result+=","+string;
    }
}

しかし、これは非常に醜いものです。特に、(すべてのSQLクエリのように)構築された文字列が複雑になっている場合、最初の外観で何が起こるかはわかりません。

あなたの(より)エレガントな方法は何ですか?


おそらく、上記のSQLは実際には次のようになります。select* from customer where customer.id in(34、26、2);
–Dónal

リストアイテム(文字列)自体にコンマまたは二重引用符が含まれていて、それらを引用符でエスケープする必要がある場合、注意が必要な部分があります。何も見逃していない場合、上記の例はそれを考慮せず、すべてのテキストをループしてコンマを検索するという考えは嫌いです。これを解決するより良い方法があると思いませんか?
サムライガール

この回答を確認してください... stackoverflow.com/a/15815631/728610
Arvind Sridharan 2013

これまでにstackoverflow.com/questions/10850753/…を確認したことがありますか?
Hiren Patel、2015

これはそれを行うべきです。stackoverflow.com/a/15815631/3157062
Parag Jadhav

回答:


85

注:この答えは11年前に書かれたときは良かったですが、Java組み込みクラスのみを使用するか、ユーティリティライブラリを使用するかのどちらかで、1行でこれをよりきれいに行うためのはるかに優れたオプションがあります。以下の他の回答を参照してください。


文字列は不変なので、コード内の文字列を変更する場合は、StringBuilderクラスを使用できます。

StringBuilderクラスは、内容が変更されたときにより多くのメモリを割り当てる可変のStringオブジェクトと見なすことができます。

質問の元の提案は、冗長な末尾のカンマを処理することにより、さらに明確かつ効率的に記述できます

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";

7
これには、コレクションに少なくとも1つの要素が必要であることに注意してください。
Guus


空のリストについては、修正案をご覧ください。
gimel

1
グアバの答えは良いです。車輪を再発明する必要はありません。
davidjnelson

1
@ xtreme-bikerかなり最近のコンパイラでは、StringBuilderが自動的に使用される場合があります。+ =を使用する前に、環境を確認してください。stackoverflow.com/questions/1532461/…を
ギメル

89

Google Guava APIjoinメソッドを使用します

Joiner.on(",").join(collectionOfStrings);

4
現在、クラスは呼び出されていJoinerます。google-collections.googlecode.com/svn/trunk/javadoc/com/google/...
Jonik

2
そして今日、コレクションは非推奨になりました。代わりにGoogle Guavaを使用してください。
darioo

12
その間、org.apache.commons.lang.StringUtilsは変更されません。:-)
Ogre Psalm33 2012年


76

今日これを行ったコードを見ただけです。これはAviewAnewの回答のバリエーションです。

collectionOfStrings = /* source string collection */;
String csList = StringUtils.join(collectionOfStrings.toArray(), ",");

使用したStringUtils(<-commons.lang 2.x、またはcommons.lang 3.xリンク)は、Apache Commonsのものです。


...そしてStringUtilsはどこから来たのですか?
vwegert 2010

1
ああ、良い点。そのコードを見てからしばらく経ちましたが、私たちはorg.apache.commons.lang.StringUtilsを使用していたと思います。
Ogre Psalm33

これは、StringUtilscommons.apache.org /proper/commons
Ryan S

2
よかった、ありがとう。StringUtils#joinはIterableでも機能するため、おそらく最初にコレクションを配列に変換する必要はありません。
ロイ

47

そのループの記述方法は次のとおりです。

StringBuilder buff = new StringBuilder();
String sep = "";
for (String str : strs) {
    buff.append(sep);
    buff.append(str);
    sep = ",";
}
return buff.toString();

9月のパフォーマンスについて心配する必要はありません。割り当ては非常に高速です。とにかく、ホットスポットはループの最初の反復をはがす傾向があります(ヌルやモノ/バイモルフィックインラインチェックなどの奇妙な要素に対処する必要があるため)。

何度も使用する場合は、共有メソッドに入れてください。

IDのリストをSQLステートメントに挿入する方法を扱う、stackoverflowに関する別の質問があります。


42

Java 8以降、以下を使用できます。


3
これはいい!toString()でカバーされない特別な文字列変換が必要なオブジェクトを操作する場合は、Object :: toStringを、クラスを文字列にマップするjava.util.function.Function <YourType、String>に置き換えます。
Torben

2
また、次のように使用することもできcats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));ます。コレクションの単一の変数に対して。
numsu

あれのパフォーマンスについてstream。値を単純ににキャストできるint []またはlong []またはその他の配列の場合、String非ストリーミングソリューションを探します。実際私は探しています。
アダム

11

イテレータのイディオムは、より多くの要素のテストがあるため簡潔であることがわかりました(簡潔にするためにnull /空のテストは省略されています)。

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}

...そして、おそらく「hasNext()」の呼び出しがどれほど複雑かによっては、受け入れられているソリューションよりも効率が悪い。さらに、おそらく文字列連結ではなくStringBuilderを使用する必要があります。
スティーブンC

OK、効率を気にしたい場合は、StringWriterを使用してください;)
Miguel Ping

8

これには多くの手動による解決策がありますが、私は上記のジュリーの答えを繰り返し、更新したいと思いました。グーグルコレクションのジョイナークラスを使用してください。

Joiner.on(", ").join(34, 26, ..., 2)

これは変数引数、反復可能オブジェクト、および配列を処理し、複数の文字のセパレータを適切に処理します(ギンメルの答えとは異なります)。必要に応じて、リスト内のnull値も処理します。


7

ここに私が以前の提案の組み合わせから構築した信じられないほど一般的なバージョンがあります:

public static <T> String buildCommaSeparatedString(Collection<T> values) {
    if (values==null || values.isEmpty()) return "";
    StringBuilder result = new StringBuilder();
    for (T val : values) {
        result.append(val);
        result.append(",");
    }
    return result.substring(0, result.length() - 1);
}

7
String.join(", ", collectionOfStrings)

Java8 APIで利用できます。

の代わりに(グーグルグアバ依存関係を追加する必要なしに):

Joiner.on(",").join(collectionOfStrings);

5

あなたは試すことができます

List collections = Arrays.asList(34, 26, "...", 2);
String asString = collection.toString();
// justValues = "34, 26, ..., 2"
String justValues = asString.substring(1, asString.length()-1);

4

これは、GuavaまたはApache Commonsを使用することを除いて、これまでのところ最短のソリューションになります

String res = "";
for (String i : values) {
    res += res.isEmpty() ? i : ","+i;
}

0、1、nの要素リストで良好です。ただし、nullリストを確認する必要があります。これをGWTで使用しているので、StringBuilderがなくても問題ありません。そして、要素の数が少ない短いリストの場合、他の場所でも問題ありません;)


4

最近誰かがこれに遭遇した場合に備えて、Java 8を使用した簡単なバリエーションを追加しましたreduce()。また、他の人がすでに言及したソリューションのいくつかも含まれています。

import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.StringUtils;    

import com.google.common.base.Joiner;

public class Dummy {
  public static void main(String[] args) {

    List<String> strings = Arrays.asList("abc", "de", "fg");
    String commaSeparated = strings
        .stream()
        .reduce((s1, s2) -> {return s1 + "," + s2; })
        .get();

    System.out.println(commaSeparated);

    System.out.println(Joiner.on(',').join(strings));

    System.out.println(StringUtils.join(strings, ","));

  }
}

4

Androidでは、これを使用する必要があります。

TextUtils.join(",",collectionOfStrings.toArray());

4

私はあなたがしているようにwhere句の値を連結するsqlを構築するのは良い考えではないと思います:

SELECT.... FROM.... WHERE ID IN( value1, value2,....valueN)

valueX文字列のリストはどこから来ますか。

まず、文字列を比較する場合は、引用符で囲む必要があります。これは、文字列に引用符を含めることができる場合は簡単ではありません。

次に、値がユーザーまたは他のシステムからのものである場合、SQLインジェクション攻撃が可能です。

これはもっと冗長ですが、次のような文字列を作成する必要があります。

SELECT.... FROM.... WHERE ID IN( ?, ?,....?)

変数をでバインドしますStatement.setString(nParameter,parameterValue)


3

この問題に対処するためのもう1つの方法。最も短いわけではありませんが、それは効率的で、仕事を成し遂げます。

/**
 * Creates a comma-separated list of values from given collection.
 * 
 * @param <T> Value type.
 * @param values Value collection.
 * @return Comma-separated String of values.
 */
public <T> String toParameterList(Collection<T> values) {
   if (values == null || values.isEmpty()) {
      return ""; // Depending on how you want to deal with this case...
   }
   StringBuilder result = new StringBuilder();
   Iterator<T> i = values.iterator();
   result.append(i.next().toString());
   while (i.hasNext()) {
      result.append(",").append(i.next().toString());
   }
   return result.toString();
}

2

文字列結合メソッドを提供するサードパーティのJavaライブラリがいくつかありますが、そのような単純な目的のためだけにライブラリの使用を開始したくない場合があります。私はこのようなヘルパーメソッドを作成しますが、これはあなたのバージョンより少し優れていると思います。StringBufferを使用します。これは、多くの文字列を結合する必要がある場合により効率的で、あらゆるタイプのコレクションで機能します。

public static <T> String join(Collection<T> values)
{
    StringBuffer ret = new StringBuffer();
    for (T value : values)
    {
        if (ret.length() > 0) ret.append(",");
        ret.append(value);
    }
    return ret.toString();
}

Collection.toString()を使用する別の提案はより短いですが、Collection.toString()が非常に特定の形式で文字列を返すことに依存しています。


2

Springを使用すると、次のことができます。

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(パッケージorg.springframework.util)


1

これがどれほど「高度」であるかはわかりませんが、確かに少し短いです。Set <Integer>、List <String>など、さまざまな種類のコレクションで機能します。

public static final String toSqlList(Collection<?> values) {

    String collectionString = values.toString();

    // Convert the square brackets produced by Collection.toString() to round brackets used by SQL
    return "(" + collectionString.substring(1, collectionString.length() - 1) + ")";
}

読者のための演習:このメソッドを変更して、null /空のコレクションを正しく処理するようにしてください:)


1

コードを醜くするのは、最初のケースの特別な処理です。この小さなスニペットのほとんどの行は、コードのルーチンジョブを実行するためではなく、その特殊なケースを処理するために費やされています。そして、それは特別な処理をループの外に移動することによって、ギメルのような代替手段が解決するものです。特別なケースが1つあります(まあ、開始と終了の両方を特別なケースとして見ることができますが、特別に処理する必要があるのは1つだけです)。そのため、ループ内での処理は不必要に複雑です。


1

ライブラリのドルのテストをチェックインしたところです。

@Test
public void join() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    String string = $(list).join(",");
}

それは使用など/リスト/配列/文字列の周りに流暢なラッパーを作成する唯一の静的インポートを$

注意

範囲を使用すると、前のリストは次のように書き直すことができます $(1, 5).join(",")


1

IN式の良い点は、値を繰り返し使用しても結果が変化しないことです。したがって、最初のアイテムを複製してリスト全体を処理するだけです。これは、リストに少なくとも1つの項目があることを前提としています。アイテムがない場合は、まずチェックしてからSQLをまったく実行しないことをお勧めします。

これはトリックを実行し、それが何をしているのかは明らかであり、外部ライブラリに依存しません。

StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
  inString.append(",").append(currentID);
}

1

私はあなたの最善の策はグアバのジョイナーを使用することだと思いますが、私が手作業でコーディングした場合、このアプローチは「最初の」フラグまたは最後のカンマを切り取るよりもエレガントだと思います。

private String commas(Iterable<String> strings) {
    StringBuilder buffer = new StringBuilder();
    Iterator<String> it = strings.iterator();
    if (it.hasNext()) {
        buffer.append(it.next());
        while (it.hasNext()) {
            buffer.append(',');
            buffer.append(it.next());
        }
    }

    return buffer.toString();
}

1

配列がある場合は、次のことができます。

Arrays.asList(parameters).toString()

1

私がここで見るものに基づいた別のオプション(わずかな変更を加えて)。

public static String toString(int[] numbers) {
    StringBuilder res = new StringBuilder();
    for (int number : numbers) {
        if (res.length() != 0) {
            res.append(',');
        }
        res.append(number);
    }
    return res.toString();
}

1

結合 'メソッド'は、配列と、拡張するAbstractCollectionstoString()メソッドをオーバーライドしないクラスで使用できます(実質的にすべてのコレクションのようにjava.util)。

例えば:

String s= java.util.Arrays.toString(collectionOfStrings.toArray());
s = s.substing(1, s.length()-1);// [] are guaranteed to be there

SQLに関してはデータが同じような数値に対してのみ機能するため、これはかなり奇妙な方法です。


1
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");

springframeowrk:spring-coreのStringUtils



0
java.util.List<String> lista = new java.util.ArrayList<String>();
lista.add("Hola");
lista.add("Julio");
System.out.println(lista.toString().replace('[','(').replace(']',')'));

$~(Hola, Julio)

1
これは悪い習慣です。toStringの実装が変更されていると想定することはできません。
drindt 2015

0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

文字列表現は、コレクションの要素のリストで構成され、イテレータによって返された順序で、角かっこ( "[]")で囲まれています。隣接する要素は、文字「、」(カンマとスペース)で区切られます。

AbstractCollection javadoc


0

List token = new ArrayList(result); 最終的なStringBuilderビルダー= new StringBuilder();

    for (int i =0; i < tokens.size(); i++){
        builder.append(tokens.get(i));
        if(i != tokens.size()-1){
            builder.append(TOKEN_DELIMITER);
        }
    }

builder.toString();

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.