컬렉션 / 배열 / 목록에서 쉼표로 구분 된 문자열을 만드는 가장 정교한 방법은 무엇입니까?


98

데이터베이스 작업을하는 동안 쿼리 문자열을 작성하고이 문자열에서 목록 / 배열 / 컬렉션의 where-clause에 몇 가지 제한을 두어야합니다. 다음과 같이 표시되어야합니다.

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

문자열 모음이 있고이 문자열의 쉼표로 구분 된 목록을 하나의 문자열로 만들고자한다는 질문으로 이것을 줄임으로써 이것을 단순화 할 수 있습니다.

지금까지 사용한 내 접근 방식은 다음과 같습니다.

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

목록 항목 (문자열) 자체에 쉼표 또는 큰 따옴표가 포함되어 있고 따옴표로 이스케이프해야하는 까다로운 부분이 있습니다. 내가 아무것도 놓치지 않았다면 위의 예는 그것을 고려하지 않고 모든 텍스트를 반복하고 쉼표를 검색하는 아이디어가 싫습니다.이 문제를 해결하는 더 좋은 방법이 있습니까?
Samurai Girl



이것은 그것을해야합니다. stackoverflow.com/a/15815631/3157062
Parag Jadhav

답변:


85

참고 :이 답변은 11 년 전에 작성되었을 때 좋았지 만 이제는 Java 내장 클래스 만 사용하거나 유틸리티 라이브러리를 사용하여 한 줄로 더 깔끔하게 수행 할 수있는 훨씬 더 나은 옵션이 있습니다. 아래의 다른 답변을 참조하십시오.


문자열은 변경할 수 없으므로 코드에서 문자열을 변경하려는 경우 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
컬렉션에 요소가 하나 이상 있어야합니다.
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

1
구아바가 움직였습니다. 참조 github.com/google/guava/wiki/StringsExplained
기멜

76

오늘이 작업을 수행 한 코드를 살펴 보았습니다. 이것은 AviewAnew의 답변에 대한 변형입니다.

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

StringUtils에 (<- commons.lang 2.x에서, 또는 commons.lang 3.X 링크 ) 우리가 사용은에서이다 아파치 코 몬즈 .


... 그리고 StringUtils는 어디에서 왔습니까?
vwegert

1
아, 좋은 지적입니다. 그 코드를 본지 오래되었지만 우리가 org.apache.commons.lang.StringUtils를 사용하고 있다고 생각합니다.
Ogre Psalm33


2
좋아, 고마워. StringUtils # join은 Iterable에서도 작동하므로 컬렉션을 먼저 배열로 변환 할 필요가 없습니다.
Roy

47

루프를 작성하는 방법은 다음과 같습니다.

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

sep의 성능에 대해 걱정하지 마십시오. 과제는 매우 빠릅니다. 핫스팟은 어쨌든 루프의 첫 번째 반복을 벗겨내는 경향이 있습니다 (널 (null) 및 모노 / 바이 모픽 인라인 검사와 같은 이상한 항목을 처리해야하는 경우가 많으므로).

많이 사용하는 경우 (두 번 이상) 공유 방법에 넣으십시오.

SQL 문에 ID 목록을 삽입하는 방법을 다루는 stackoverflow에 대한 또 다른 질문이 있습니다.


42

Java 8부터 다음을 사용할 수 있습니다.


3
이것은 좋다! toString ()에서 다루지 않는 특수 문자열 변환이 필요한 객체를 조작하는 경우 Object :: toString을 클래스를 String에 매핑하는 java.util.function.Function <YourType, String>으로 바꿉니다.
Torben

2
또한 cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));컬렉션의 단일 변수에 대해 다음과 같이 사용할 수 있습니다 .
numsu

그 성능이 궁금합니다 stream. int [] 또는 long [] 또는 값을 간단히 캐스트 할 수있는 다른 배열의 경우 String비 스트리밍 솔루션을 찾습니다. 사실 나는 찾고 있습니다.
Adam

11

더 많은 요소에 대한 테스트가 있기 때문에 반복기 관용구가 우아하다는 것을 알았습니다 (간결성을 위해 null / empty 테스트 생략).

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 ()'호출이 얼마나 복잡한 지에 따라 허용 된 솔루션보다 효율성이 떨어집니다. 게다가 String 연결보다는 StringBuilder를 사용해야합니다.
Stephen C

좋습니다. 효율성에 대해 까다롭게하려면 StringWriter를 사용하십시오.)
Miguel Ping

8

이에 대한 많은 수동 솔루션이 있지만 위의 Julie의 답변을 반복하고 업데이트하고 싶습니다. Google 컬렉션 Joiner 클래스를 사용하십시오 .

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

var args, iterables 및 array를 처리하고 하나 이상의 문자 구분 기호를 적절하게 처리합니다 (gimmel의 답변과 달리). 또한 필요한 경우 목록의 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에서 사용할 수 있습니다.

대안 (Google 구아바 종속성을 추가 할 필요 없음) :

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 요소 목록에 적합합니다. 그러나 널 목록을 확인해야합니다. 나는 이것을 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

이 문제를 처리하는 또 다른 방법입니다. 가장 짧지는 않지만 효율적이고 작업을 완료합니다.

/**
 * 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 라이브러리가 있지만 이와 같은 간단한 용도로만 라이브러리를 사용하고 싶지는 않을 것입니다. 나는 당신의 버전보다 조금 더 낫다고 생각하는 이와 같은 도우미 메서드를 만들 것입니다. 그것은 많은 문자열을 조인해야 할 때 더 효율적이며 모든 유형의 컬렉션에서 작동합니다.

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

코드를 추악하게 만드는 것은 첫 번째 경우에 대한 특수 처리입니다. 이 작은 스 니펫의 대부분의 줄은 코드의 일상적인 작업을 수행하는 것이 아니라 특수한 경우를 처리하는 데 전념합니다. 그리고 그것은 특별한 핸들링을 루프 외부로 이동하여 gimel의 해결과 같은 대안입니다. 하나의 특수한 경우가 있습니다 (글쎄요, 시작과 끝을 모두 특수한 경우로 볼 수 있지만 그중 하나만 특별히 처리해야 함) 루프 내에서 처리하는 것은 불필요하게 복잡합니다.


1

방금 내 도서관 달러 테스트에 체크인했습니다 .

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

그것은 사용 목록 / 배열 / 문자열 / 등 주위에 능통 래퍼 만드는 하나의 고정 수입 : $.

주의 :

범위를 사용하여 이전 목록을 다음과 같이 다시 작성할 수 있습니다. $(1, 5).join(",")


1

IN 표현식의 좋은 점은 반복되는 값이 있으면 결과가 변경되지 않는다는 것입니다. 따라서 첫 번째 항목을 복제하고 전체 목록을 처리하십시오. 이것은 목록에 적어도 하나의 항목이 있다고 가정합니다. 항목이 없으면 먼저 확인한 다음 SQL을 전혀 실행하지 않는 것이 좋습니다.

이것은 트릭을 수행하고 수행하는 작업에서 분명하며 외부 라이브러리에 의존하지 않습니다.

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

1

가장 좋은 방법은 Guava의 Joiner를 사용하는 것이라고 생각하지만, 직접 코드를 작성한다면이 접근 방식이 '첫 번째'플래그 나 마지막 쉼표를 잘라내는 것보다 더 우아하다는 것을 알 수 있습니다.

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

Join 'methods'는 Arrays 및 확장 AbstractCollections되지만 toString()메서드를 재정의하지 않는 클래스에서 사용할 수 있습니다 (거의 모든 컬렉션처럼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

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

문자열 표현은 반복기에 의해 반환되는 순서대로 대괄호 ( "[]")로 묶인 컬렉션 요소 목록으로 구성됩니다. 인접한 요소는 ","(쉼표 및 공백) 문자로 구분됩니다.

AbstractCollection javadoc


0

목록 토큰 = 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.