Cách phức tạp nhất để tạo Chuỗi được phân tách bằng dấu phẩy từ Bộ sưu tập / Mảng / Danh sách?


98

Trong quá trình làm việc với cơ sở dữ liệu, tôi nhận thấy rằng tôi viết chuỗi truy vấn và trong chuỗi này, tôi phải đặt một số hạn chế trong mệnh đề where-từ danh sách / mảng / bộ sưu tập. Sẽ như thế này:

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

Bạn có thể đơn giản hóa điều này bằng cách giảm điều này thành câu hỏi rằng bạn có tập hợp các chuỗi và muốn tạo một danh sách được phân tách bằng dấu phẩy của các chuỗi này chỉ trong một chuỗi.

Cách tiếp cận của tôi mà tôi đã sử dụng cho đến nay là một cái gì đó như thế:

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

Nhưng điều này là như bạn thấy rất xấu xí. Bạn không thể thấy điều gì xảy ra ở đó ngay từ cái nhìn đầu tiên, đặc biệt khi các chuỗi được xây dựng (giống như mọi truy vấn SQL) đang trở nên phức tạp.

Cách thanh lịch (nhiều hơn) của bạn là gì?


Có lẽ SQL được hiển thị ở trên thực sự sẽ giống như sau: select * from customer where customer.id in (34, 26, 2);
Dónal

Có một phần khó khăn, khi bản thân các mục danh sách (chuỗi) chứa dấu phẩy hoặc dấu ngoặc kép và chúng cần được đặt sau dấu ngoặc kép. Nếu tôi không bỏ sót bất cứ điều gì, các ví dụ trên không xem xét nó và tôi ghét ý tưởng lặp lại tất cả các văn bản và tìm kiếm dấu phẩy .. Bạn có biết có cách nào tốt hơn để giải quyết vấn đề này không?
Samurai Girl

kiểm tra câu trả lời này ra ... stackoverflow.com/a/15815631/728610
Arvind Sridharan

Bạn đã bao giờ kiểm tra stackoverflow.com/questions/10850753/… chưa?
Hiren Patel

Điều này nên làm điều đó. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Câu trả lời:


85

Lưu ý: Câu trả lời này rất hay khi nó được viết cách đây 11 năm, nhưng bây giờ có nhiều lựa chọn tốt hơn để thực hiện điều này rõ ràng hơn trong một dòng duy nhất, cả chỉ sử dụng các lớp tích hợp sẵn của Java hoặc sử dụng thư viện tiện ích. Xem các câu trả lời khác bên dưới.


Vì các chuỗi là bất biến, bạn có thể muốn sử dụng lớp StringBuilder nếu bạn định thay đổi Chuỗi trong mã.

Lớp StringBuilder có thể được xem như là một đối tượng String có thể thay đổi, phân bổ nhiều bộ nhớ hơn khi nội dung của nó bị thay đổi.

Gợi ý ban đầu trong câu hỏi thậm chí có thể được viết rõ ràng và hiệu quả hơn, bằng cách lưu ý đến dấu phẩy thừa ở cuối :

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

7
Lưu ý rằng điều này yêu cầu bộ sưu tập của bạn phải có ít nhất một phần tử.
Guus

3
Xem câu trả lời được bình chọn nhiều nhất - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel

Xem bản sửa lỗi được đề xuất cho một danh sách trống.
gimel

1
câu trả lời ổi là tốt hơn. Không cần phải phát minh lại bánh xe.
davidjnelson

1
@ xtreme-biker Với trình biên dịch hiện đại hợp lý, StringBuilder có thể được sử dụng tự động. Kiểm tra môi trường của bạn trước khi sử dụng + =. Xem stackoverflow.com/questions/1532461/…
gimel

89

Sử dụng API Google Guavajoin phương pháp của :

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

4
Ngày nay lớp được gọi là Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik

2
Và ngày nay, Bộ sưu tập không được dùng nữa. Sử dụng Google Guava thay thế.
darioo

12
Trong khi đó, org.apache.commons.lang.StringUtils vẫn không thay đổi. :-)
Ogre Psalm 33

1
ổi đã chuyển đi. xem github.com/google/guava/wiki/StringsExplained
gimel

76

Tôi vừa xem mã đã làm điều này hôm nay. Đây là một biến thể trong câu trả lời của AviewAnew.

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

Các StringUtils (<- 2.x commons.lang, hoặc liên kết 3.x commons.lang ), chúng tôi sử dụng là từ Apache Commons .


... và StringUtils đến từ đâu?
vwegert

1
Ah, điểm tốt. Đã một thời gian kể từ khi tôi xem mã đó, nhưng tôi tin rằng chúng tôi đang sử dụng org.apache.commons.lang.StringUtils.
Ogre Psalm33

Dưới đây là một liên kết trực tiếp đến join phương pháp StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/...
Ryan S

2
Tốt, cảm ơn. StringUtils # join cũng hoạt động trên Iterable, vì vậy có thể không cần chuyển đổi bộ sưu tập của bạn thành một mảng trước.
Roy

47

Cách tôi viết vòng lặp đó là:

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

Đừng lo lắng về hiệu suất của sep. Một nhiệm vụ rất nhanh chóng. Hotspot có xu hướng loại bỏ lần lặp đầu tiên của một vòng lặp (vì nó thường phải đối phó với các điều kỳ lạ như kiểm tra nội tuyến null và mono / bimorphic).

Nếu bạn sử dụng nó nhiều (nhiều hơn một lần), hãy đặt nó vào một phương pháp chia sẻ.

Có một câu hỏi khác về stackoverflow giải quyết cách chèn danh sách id vào một câu lệnh SQL.


42

Kể từ Java 8, bạn có thể sử dụng:


3
Điều này là tốt! Nếu bạn đang thao tác các đối tượng cần chuyển đổi chuỗi đặc biệt không có trong toString (), hãy thay thế Object :: toString bằng một java.util. Chức năng.Function <YourType, String> ánh xạ lớp của bạn thành một Chuỗi.
Torben

2
Ngoài ra, bạn có thể sử dụng nó như thế này: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));cho một biến duy nhất từ ​​bộ sưu tập của bạn.
numsu

Tôi tự hỏi về hiệu suất của điều đó stream. Đối với int [] hoặc long [] hoặc các mảng khác mà giá trị có thể được truyền đơn giản đến String, tôi muốn tìm giải pháp không phát trực tuyến. Trong thực tế, tôi đang tìm kiếm.
Adam

11

Tôi thấy thành ngữ trình lặp thanh lịch, bởi vì nó có một bài kiểm tra cho nhiều phần tử hơn (bài kiểm tra rỗng / rỗng không giới hạn cho sự ngắn gọn):

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

... và có lẽ kém hiệu quả hơn giải pháp được chấp nhận, tùy thuộc vào mức độ phức tạp của lệnh gọi 'hasNext ()'. Bên cạnh đó, có lẽ bạn nên sử dụng một StringBuilder hơn là nối chuỗi.
Stephen C

OK, nếu bạn muốn trở thành cầu kỳ về tính hiệu quả, sử dụng một StringWriter;)
Miguel Ping

8

Có rất nhiều giải pháp thủ công cho vấn đề này, nhưng tôi muốn nhắc lại và cập nhật câu trả lời của Julie ở trên. Sử dụng lớp Joiner bộ sưu tập của google .

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

Nó xử lý var args, iterables và mảng và xử lý đúng cách các dấu phân cách của nhiều hơn một ký tự (không giống như câu trả lời của gimmel). Nó cũng sẽ xử lý các giá trị null trong danh sách của bạn nếu bạn cần.


7

Đây là một phiên bản cực kỳ chung chung mà tôi đã xây dựng từ sự kết hợp của các đề xuất trước đó:

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)

có sẵn trong Java8 api.

thay thế cho (không cần thêm phụ thuộc google ổi):

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

5

Bạn có thể thử

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

4

Đây sẽ là giải pháp ngắn nhất cho đến nay, ngoại trừ việc sử dụng Guava hoặc Apache Commons

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

Tốt với danh sách 0,1 và n phần tử. Nhưng bạn sẽ cần phải kiểm tra danh sách rỗng. Tôi sử dụng cái này trong GWT, vì vậy tôi vẫn ổn khi không có StringBuilder ở đó. Và đối với danh sách ngắn chỉ với một vài yếu tố, nó cũng ổn ở những nơi khác;)


4

Trong trường hợp ai đó tình cờ gặp phải điều này trong thời gian gần đây, tôi đã thêm một biến thể đơn giản bằng cách sử dụng Java 8 reduce(). Nó cũng bao gồm một số giải pháp đã được đề cập bởi những người khác:

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

Trong Android, bạn nên sử dụng cái này:

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

4

Tôi nghĩ rằng nó không phải là một ý tưởng hay để cấu trúc sql nối các giá trị mệnh đề where như bạn đang làm:

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

Nơi valueXđến từ danh sách các Chuỗi.

Đầu tiên, nếu bạn đang so sánh các Chuỗi thì chúng phải được trích dẫn, điều này không phải là nhỏ nếu các Chuỗi có thể có một trích dẫn bên trong.

Thứ hai, nếu các giá trị đến từ người dùng hoặc hệ thống khác, thì một cuộc tấn công SQL injection là có thể xảy ra.

Nó dài dòng hơn nhiều nhưng những gì bạn nên làm là tạo một Chuỗi như thế này:

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

và sau đó ràng buộc các biến với Statement.setString(nParameter,parameterValue).


3

Chỉ là một phương pháp khác để đối phó với vấn đề này. Không phải là ngắn nhất, nhưng nó hiệu quả và hoàn thành công việc.

/**
 * 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

Có một số thư viện Java của bên thứ ba cung cấp phương thức nối chuỗi, nhưng bạn có thể không muốn bắt đầu sử dụng một thư viện chỉ cho một thứ đơn giản như vậy. Tôi chỉ tạo một phương thức trợ giúp như thế này, mà tôi nghĩ là tốt hơn một chút so với phiên bản của bạn, Nó sử dụng StringBuffer, sẽ hiệu quả hơn nếu bạn cần nối nhiều chuỗi và nó hoạt động trên một tập hợp thuộc bất kỳ loại nào.

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

Một gợi ý khác với việc sử dụng Collection.toString () ngắn hơn, nhưng điều đó dựa vào Collection.toString () trả về một chuỗi ở định dạng rất cụ thể, mà cá nhân tôi không muốn dựa vào.


2

Nếu bạn sử dụng Spring, bạn có thể làm:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(gói org.springframework.util)


1

Tôi không chắc cái này "phức tạp" đến mức nào, nhưng chắc chắn là nó ngắn hơn một chút. Nó sẽ hoạt động với nhiều kiểu tập hợp khác nhau, ví dụ như Set <Integer>, List <String>, v.v.

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) + ")";
}

Bài tập cho người đọc : sửa đổi phương thức này để nó xử lý chính xác một tập hợp rỗng / rỗng :)


1

Điều làm cho mã trở nên xấu xí là cách xử lý đặc biệt cho trường hợp đầu tiên. Hầu hết các dòng trong đoạn mã nhỏ này được dành, không phải để thực hiện công việc thông thường của mã, mà để xử lý trường hợp đặc biệt đó. Và đó là những gì các lựa chọn thay thế như gimel giải quyết, bằng cách di chuyển xử lý đặc biệt ra bên ngoài vòng lặp. Có một trường hợp đặc biệt (tốt, bạn có thể xem cả bắt đầu và kết thúc là các trường hợp đặc biệt - nhưng chỉ một trong số chúng cần được xử lý đặc biệt), vì vậy việc xử lý nó bên trong vòng lặp là phức tạp không cần thiết.


1

Tôi vừa đăng ký một bài kiểm tra cho đồng đô la thư viện của mình :

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

nó tạo ra một wrapper thạo xung quanh danh sách / mảng / dây / etc sử dụng chỉ có một khẩu tĩnh : $.

NB :

sử dụng các phạm vi, danh sách trước đó có thể được viết lại thành $(1, 5).join(",")


1

Điều thú vị về biểu thức IN là nếu bạn có các giá trị lặp lại, nó không thay đổi kết quả. Vì vậy, chỉ cần sao chép mục đầu tiên và xử lý toàn bộ danh sách. Điều này giả định rằng có ít nhất một mục trong danh sách. Nếu không có mục nào, tôi khuyên bạn nên kiểm tra điều đó trước và sau đó không thực thi SQL.

Điều này sẽ thực hiện thủ thuật, rõ ràng trong những gì nó đang làm và không dựa vào bất kỳ thư viện bên ngoài nào:

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

1

Mặc dù tôi nghĩ đặt cược tốt nhất của bạn là sử dụng Joiner từ Guava, nhưng nếu tôi viết mã bằng tay, tôi thấy cách tiếp cận này thanh lịch hơn khi gắn cờ 'đầu tiên' hoặc cắt bỏ dấu phẩy cuối cùng.

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

nếu bạn có một mảng, bạn có thể làm:

Arrays.asList(parameters).toString()

1

Một tùy chọn khác, dựa trên những gì tôi thấy ở đây (với các sửa đổi nhỏ).

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

Tham gia 'phương thức' có sẵn trong Mảng và các lớp mở rộng AbstractCollectionsnhưng không ghi đè toString()phương thức (như hầu như tất cả các tập hợp trong java.util).

Ví dụ:

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

Đó là một cách khá kỳ lạ vì nó chỉ hoạt động với các con số giống như dữ liệu SQL.


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

StringUtils từ springframeowrk: spring-core



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
Đây là một thực hành xấu. Bạn không thể giả định rằng việc triển khai toString thay đổi.
drindt

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

Biểu diễn chuỗi bao gồm danh sách các phần tử của tập hợp theo thứ tự chúng được trả về bởi trình lặp của nó, được đặt trong dấu ngoặc vuông ("[]"). Các phần tử liền kề được phân tách bằng các ký tự "," (dấu phẩy và dấu cách).

AbstractCollection javadoc


0

Mã thông báo danh sách = new ArrayList (kết quả); final StringBuilder builder = 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 ();

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.