Cách đơn giản nhất để phân tách danh sách bằng dấu phẩy?


104

Cách rõ ràng nhất để phân tách danh sách bằng dấu phẩy trong Java là gì?

Tôi biết một số cách để làm điều đó, nhưng tôi đang tự hỏi cách tốt nhất là gì (trong đó "tốt nhất" có nghĩa là rõ ràng nhất và / hoặc ngắn nhất, không phải là hiệu quả nhất.

Tôi có một danh sách và tôi muốn lặp lại nó, in từng giá trị. Tôi muốn in dấu phẩy giữa mỗi mục, nhưng không in sau mục cuối cùng (cũng không phải trước mục đầu tiên).

List --> Item ( , Item ) *
List --> ( Item , ) * Item

Giải pháp mẫu 1:

boolean isFirst = true;
for (Item i : list) {
  if (isFirst) {
    System.out.print(i);        // no comma
    isFirst = false;
  } else {
    System.out.print(", "+i);   // comma
  }
}

Giải pháp mẫu 2 - tạo danh sách phụ:

if (list.size()>0) {
  System.out.print(list.get(0));   // no comma
  List theRest = list.subList(1, list.size());
  for (Item i : theRest) {
    System.out.print(", "+i);   // comma
  }
}

Giải pháp mẫu 3:

  Iterator<Item> i = list.iterator();
  if (i.hasNext()) {
    System.out.print(i.next());
    while (i.hasNext())
      System.out.print(", "+i.next());
  }

Những thứ này đặc biệt coi trọng mặt hàng đầu tiên; thay vào đó người ta có thể đối xử đặc biệt với người cuối cùng.

Ngẫu nhiên, đây là cách List toString được triển khai (nó được kế thừa từ AbstractCollection), trong Java 1.6:

public String toString() {
    Iterator<E> i = iterator();
    if (! i.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = i.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! i.hasNext())
            return sb.append(']').toString();
        sb.append(", ");
    }
}

Nó thoát khỏi vòng lặp sớm để tránh dấu phẩy sau mục cuối cùng. BTW: đây là lần đầu tiên tôi nhớ lại thấy "(Bộ sưu tập này)"; đây là mã để kích động nó:

List l = new LinkedList();
l.add(l);
System.out.println(l);

Tôi hoan nghênh bất kỳ giải pháp nào, ngay cả khi họ sử dụng các thư viện không mong muốn (regexp?); và cả các giải pháp bằng các ngôn ngữ khác ngoài Java (ví dụ: tôi nghĩ Python / Ruby có một hàm xen kẽ - điều đó được thực hiện như thế nào?).

Làm rõ : theo các thư viện, ý tôi là các thư viện Java tiêu chuẩn. Đối với các thư viện khác, tôi xem xét chúng với các ngôn ngữ khác và muốn biết chúng được triển khai như thế nào.

Bộ công cụ EDIT đã đề cập đến một câu hỏi tương tự: Lần lặp cuối cùng của vòng lặp for nâng cao trong java

Và một điều khác: Phần tử cuối cùng trong một vòng lặp có xứng đáng được xử lý riêng không?


Câu trả lời:


226

Java 8 trở lên

Sử dụng StringJoinerlớp:

StringJoiner joiner = new StringJoiner(",");
for (Item item : list) {
    joiner.add(item.toString());
}
return joiner.toString();

Sử dụng StreamCollectors:

return list.stream().
       map(Object::toString).
       collect(Collectors.joining(",")).toString();

Java 7 trở về trước

Xem thêm # 285523

String delim = "";
for (Item i : list) {
    sb.append(delim).append(i);
    delim = ",";
}

1
Một cách rất đáng ngạc nhiên để lưu trữ trạng thái! Nó gần như đa hình OO. Tôi không thích việc gán không cần thiết cho mỗi vòng lặp, nhưng tôi cá là nó hiệu quả hơn if. Hầu hết các giải pháp lặp lại một thử nghiệm mà chúng tôi biết sẽ không đúng - mặc dù không hiệu quả, nhưng chúng có lẽ là rõ ràng nhất. Cảm ơn các liên kết!
13ren

1
Tôi thích điều này, nhưng vì nó thông minh và đáng ngạc nhiên, tôi nghĩ rằng nó sẽ không thích hợp để sử dụng (ngạc nhiên là không tốt!). Tuy nhiên, tôi không thể giúp mình. Tôi vẫn không chắc chắn về nó; tuy nhiên, nó quá ngắn nên rất dễ tìm ra miễn là có một bình luận để mách bạn.
13ren

6
@ 13ren: Bạn và chú Bob cần có một cuộc nói chuyện về mã "thông minh và đáng ngạc nhiên."
Stefan Kendall

Tôi cảm thấy như tôi đã lãng phí nhiều năm khi không biết điều này ^^;
Hồ

Tại sao Java cần biến tác vụ như vậy thành một đoạn mã xấu xí?
Eduardo

82
org.apache.commons.lang3.StringUtils.join(list,",");

1
Tìm thấy nguồn: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/… Phương thức tham gia có 9 quá tải. Những cái dựa trên sự lặp lại giống như "Giải pháp mẫu 3"; những cái dựa trên chỉ mục sử dụng: if (i> startIndex) {<thêm dấu phân tách>}
13ren

Tôi thực sự sau khi triển khai. Bạn nên gói gọn nó trong một hàm, nhưng trong thực tế, dấu phân cách của tôi đôi khi là một dòng mới và mỗi dòng cũng được thụt vào một số độ sâu cụ thể. Trừ khi ... tham gia (danh sách, "\ n" + thụt lề) ... sẽ luôn hoạt động chứ? Xin lỗi, tôi chỉ đang nghĩ to.
13ren

2
Theo tôi, đây là đẹp nhất và giải pháp ngắn nhất
Jesper Ronn-Jensen

có lẽ là list.iterator ()?
Vanuan

32

Java 8 cung cấp một số cách mới để thực hiện việc này:

  • Các joinphương pháp trên String.
  • Một joiningbộ sưu tập cho các luồng từ Collectorslớp.
  • Các StringJoinerlớp.

Thí dụ:

// Util method for strings and other char sequences
List<String> strs = Arrays.asList("1", "2", "3");
String listStr1 = String.join(",", strs);

// For any type using streams and collectors
List<Object> objs = Arrays.asList(1, 2, 3);
String listStr2 = objs.stream()
    .map(Object::toString)
    .collect(joining(",", "[", "]"));

// Using the new StringJoiner class
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.setEmptyValue("<empty>");
for (Integer i : objs) {
  joiner.add(i.toString());
}
String listStr3 = joiner.toString();

Phương pháp sử dụng các luồng giả định import static java.util.stream.Collectors.joining;.


Đây là IMHO, nếu bạn đang sử dụng java 8, câu trả lời tốt nhất.
scvy


7

Nếu bạn sử dụng Spring Framework, bạn có thể làm điều đó với StringUtils :

public static String arrayToDelimitedString(Object[] arr)
public static String arrayToDelimitedString(Object[] arr, String delim)
public static String collectionToCommaDelimitedString(Collection coll)
public static String collectionToCommaDelimitedString(Collection coll, String delim)

Cảm ơn, nhưng nó được thực hiện như thế nào?
13ren

6

Dựa trên triển khai List toString của Java:

Iterator i = list.iterator();
for (;;) {
  sb.append(i.next());
  if (! i.hasNext()) break;
  ab.append(", ");
}

Nó sử dụng một ngữ pháp như sau:

List --> (Item , )* Item

Bằng cách dựa trên cuối cùng thay vì dựa trên đầu tiên, nó có thể kiểm tra dấu phẩy bỏ qua với cùng một bài kiểm tra để kiểm tra cuối danh sách. Tôi nghĩ cái này rất thanh lịch, nhưng tôi không chắc về độ rõ ràng.


4
String delimiter = ",";
StringBuilder sb = new StringBuilder();
for (Item i : list) {
    sb.append(delimiter).append(i);
}
sb.toString().replaceFirst(delimiter, "");

4

Có một cách khá hay để đạt được điều này bằng cách sử dụng Java 8:

List<String> list = Arrays.asList(array);
String joinedString = String.join(",", list);

TextUtils.join (",", danh sách);
Arul Pandian

3
for(int i=0, length=list.size(); i<length; i++)
  result+=(i==0?"":", ") + list.get(i);

Vâng, nó hơi khó hiểu, nhưng là một giải pháp thay thế cho những gì đã được đăng.
cherouvim

1
Tôi nghĩ mã trông ổn, IMHO của nó đẹp hơn câu trả lời khác của bạn và hầu như không 'khó hiểu'. Kết thúc nó trong một phương pháp gọi là Tham gia và cười của bạn, tôi vẫn mong rằng ngôn ngữ này sẽ có một cách tốt hơn để giải quyết vấn đề thực sự là một vấn đề phổ biến.
tarn

@tarn: bạn nghĩ rằng nó có vẻ tốt vì bạn có Python / nền perl;) Tôi như nó là tốt
cherouvim

3

Một tùy chọn cho vòng lặp foreach là:

StringBuilder sb = new StringBuilder();
for(String s:list){
  if (sb.length()>0) sb.append(",");
  sb.append(s);
}

2
StringBuffer result = new StringBuffer();
for(Iterator it=list.iterator; it.hasNext(); ) {
  if (result.length()>0)
    result.append(", ");
  result.append(it.next());
}

Cập nhật : Như Dave Webb đã đề cập trong các nhận xét, điều này có thể không tạo ra kết quả chính xác nếu các mục đầu tiên trong danh sách là chuỗi trống.


meh .. vẫn có if trong vòng lặp.
sfossen

Bạn có thể sử dụng một vòng lặp foreach để làm cho nó ngắn hơn.
Peter Lawrey

Điều đó sẽ không hoạt động nếu mục đầu tiên trong danh sách là một chuỗi trống.
Dave Webb

@Dave Webb: Vâng, cảm ơn. Tuy nhiên, câu hỏi không có yêu cầu như vậy.
cherouvim

@cherovium: Câu hỏi không nói rằng không có mục nào sẽ là chuỗi trống, vì vậy đó là một yêu cầu ngầm. Nếu bạn đang tạo tệp CSV, các trường trống có thể khá phổ biến.
Dave Webb

2

Tôi thường làm điều này:

StringBuffer sb = new StringBuffer();
Iterator it = myList.iterator();
if (it.hasNext()) { sb.append(it.next().toString()); }
while (it.hasNext()) { sb.append(",").append(it.next().toString()); }

Mặc dù tôi nghĩ tôi sẽ kiểm tra điều này từ bây giờ về việc triển khai Java;)


Đó là logic tương tự như Mẫu 3, tôi thích nó vì nó không có gì không cần thiết. Tuy nhiên, tôi không biết nó có phải là rõ ràng nhất không. BTW: sẽ hiệu quả hơn một chút nếu bạn đặt thời gian bên trong if, bằng cách tránh hai lần kiểm tra khi danh sách trống. Nó cũng làm cho nó rõ ràng hơn, với tôi.
13ren

Vì Chúa, hãy sử dụng StringBuilder
scvy

2

Nếu bạn có thể sử dụng Groovy (chạy trên JVM):

def list = ['a', 'b', 'c', 'd']
println list.join(',')

cảm ơn, nhưng tôi quan tâm đến việc triển khai. Điều này được thực hiện như thế nào?
13ren

2

(Sao chép và dán câu trả lời của riêng tôi từ đây .) Nhiều giải pháp được mô tả ở đây hơi cao hơn IMHO, đặc biệt là những giải pháp dựa vào thư viện bên ngoài. Có một thành ngữ rõ ràng, dễ hiểu để đạt được danh sách được phân tách bằng dấu phẩy mà tôi luôn sử dụng. Nó dựa vào toán tử điều kiện (?):

Chỉnh sửa : Giải pháp ban đầu đúng, nhưng không tối ưu theo nhận xét. Thử lần thứ hai:

int[] array = {1, 2, 3};
StringBuilder builder = new StringBuilder();
for (int i = 0 ;  i < array.length; i++)
       builder.append(i == 0 ? "" : ",").append(array[i]);

Vậy là xong, trong 4 dòng mã bao gồm khai báo mảng và StringBuilder.

Chỉnh sửa lần 2 : Nếu bạn đang xử lý một Trình lặp lại:

    List<Integer> list = Arrays.asList(1, 2, 3);
    StringBuilder builder = new StringBuilder();
    for (Iterator it = list.iterator(); it.hasNext();)
        builder.append(it.next()).append(it.hasNext() ? "," : "");

Điều này và stackoverflow.com/questions/668952/… ở trên là tương tự. Nó ngắn gọn và rõ ràng, nhưng nếu bạn chỉ có một trình lặp, trước tiên bạn cần phải chuyển đổi. Không hiệu quả, nhưng có thể đáng giá vì sự rõ ràng của cách tiếp cận này?
13ren

Rất tiếc, việc nối chuỗi tạo ra một StringBuilder tạm thời trong việc sử dụng StringBuilder. Không hiệu quả. Tốt hơn (thích Java hơn, nhưng mã chạy tốt hơn) sẽ là "if (i> 0) {builder.append (',');}" theo sau là builder.append (array [i]);
Eddie

Vâng, nếu bạn nhìn vào mã bytecode, bạn đã đúng. Tôi không thể tin được một câu hỏi đơn giản như vậy lại có thể trở nên phức tạp như vậy. Tôi tự hỏi nếu trình biên dịch có thể tối ưu hóa ở đây.
Julien Chastang

Được cung cấp giải pháp thứ 2, hy vọng tốt hơn.
Julien Chastang

Có thể tốt hơn nếu tách chúng ra (để mọi người có thể bỏ phiếu cho một hoặc khác).
13ren

1

Tôi thường sử dụng một cái gì đó tương tự như phiên bản 3. Nó hoạt động tốt c / c ++ / bash / ...: P


1

Tôi đã không biên dịch nó ... nhưng sẽ hoạt động (hoặc gần làm việc).

public static <T> String toString(final List<T> list, 
                                  final String delim)
{
    final StringBuilder builder;

    builder = new StringBuilder();

    for(final T item : list)
    {
        builder.append(item);
        builder.append(delim);
    }

    // kill the last delim
    builder.setLength(builder.length() - delim.length());

    return (builder.toString());
}

Tôi thích cách này, tôi chỉ thích một định dạng khác. Nhưng phải đề cập đến một bản sửa lỗi nhỏ: Nếu danh sách trống, điều này sẽ ném ra một ngoại lệ IndexOutOfBounds. Vì vậy, một trong hai làm một kiểm tra trước khi setlength, hoặc sử dụng Math.max (0, ...)
tb-

1

Điều này rất ngắn, rất rõ ràng, nhưng mang lại cho tôi cảm giác kinh hoàng khủng khiếp. Cũng hơi khó xử khi thích ứng với các dấu phân cách khác nhau, đặc biệt nếu là Chuỗi (không phải ký tự).

for (Item i : list)
  sb.append(',').append(i);
if (sb.charAt(0)==',') sb.deleteCharAt(0);

Lấy cảm hứng từ: Lần lặp cuối cùng của vòng lặp for nâng cao trong java


1
StringBuffer sb = new StringBuffer();

for (int i = 0; i < myList.size(); i++)
{ 
    if (i > 0) 
    {
        sb.append(", ");
    }

    sb.append(myList.get(i)); 
}

Tôi thấy cái này rất dễ nhúng vào một lớp tiện ích tĩnh, đồng thời cũng rất dễ đọc và dễ hiểu. Nó cũng không yêu cầu bất kỳ biến hoặc trạng thái bổ sung nào ngoài danh sách, vòng lặp và dấu phân cách.
Joachim H. Skeie

Điều này sẽ cung cấp một cái gì đó như:Item1Item2, Item3, Item4,

chúa

1

Tôi hơi thích cách tiếp cận này, mà tôi đã tìm thấy trên một blog cách đây một thời gian. Rất tiếc, tôi không nhớ tên / URL của blog.

Bạn có thể tạo một lớp tiện ích / trợ giúp giống như sau:

private class Delimiter
{
    private final String delimiter;
    private boolean first = true;

    public Delimiter(String delimiter)
    {
        this.delimiter = delimiter;
    }

    @Override
    public String toString()
    {
        if (first) {
            first = false;
            return "";
        }

        return delimiter;
    }
}

Sử dụng lớp trợ giúp đơn giản như sau:

StringBuilder sb = new StringBuilder();
Delimiter delimiter = new Delimiter(", ");

for (String item : list) {
    sb.append(delimiter);
    sb.append(item);
}

Chỉ cần lưu ý: nó phải là "dấu phân cách", không phải "dấu phân cách".
Michael Myers

1

Vì dấu phân cách của bạn là "," bạn có thể sử dụng bất kỳ cách nào sau đây:

public class StringDelim {

    public static void removeBrackets(String string) {
        System.out.println(string.substring(1, string.length() - 1));
    }

    public static void main(String... args) {
        // Array.toString() example
        String [] arr = {"Hi" , "My", "name", "is", "br3nt"};
        String string = Arrays.toString(arr);
        removeBrackets(string);

        // List#toString() example
        List<String> list = new ArrayList<String>();
        list.add("Hi");
        list.add("My");
        list.add("name");
        list.add("is");
        list.add("br3nt");
        string = list.toString();
        removeBrackets(string);

        // Map#values().toString() example
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("1", "Hi");
        map.put("2", "My");
        map.put("3", "name");
        map.put("4", "is");
        map.put("5", "br3nt");
        System.out.println(map.values().toString());
        removeBrackets(string);

        // Enum#toString() example
        EnumSet<Days> set = EnumSet.allOf(Days.class);
        string = set.toString();
        removeBrackets(string);
    }

    public enum Days {
        MON("Monday"),
        TUE("Tuesday"),
        WED("Wednesday"),
        THU("Thursday"),
        FRI("Friday"),
        SAT("Saturday"),
        SUN("Sunday");

        private final String day;

        Days(String day) {this.day = day;}
        public String toString() {return this.day;}
    }
}

Nếu dấu phân tách của bạn là BẤT CỨ ĐIỀU GÌ khác thì điều này sẽ không hiệu quả với bạn.


1

Tôi thích giải pháp này:

String delim = " - ", string = "";

for (String item : myCollection)
    string += delim + item;

string = string.substring(delim.length());

Tôi cho rằng nó cũng có thể sử dụng StringBuilder.


1

Theo tôi, đây là cách đọc và hiểu đơn giản nhất:

StringBuilder sb = new StringBuilder();
for(String string : strings) {
    sb.append(string).append(',');
}
sb.setLength(sb.length() - 1);
String result = sb.toString();

0

Trong Python, nó dễ dàng

",". tham gia (danh sách của bạn)

Trong C # có một phương thức tĩnh trên lớp String

String.Join (",", yourlistofstrings)

Xin lỗi, tôi không chắc về Java nhưng tôi nghĩ tôi sẽ hiểu khi bạn hỏi về các ngôn ngữ khác. Tôi chắc chắn rằng sẽ có một cái gì đó tương tự trong Java.


IIRC, bạn nhận được một dấu vết, trong phiên bản .Net. :-(

1
Vừa mới được thử nghiệm, không có dấu phân cách ở cuối trong .NET hoặc Python
tarn

1
cảm ơn! Tôi đã tìm thấy nguồn của nó: svn.python.org/view/python/bragets/release22-branch/Objects/… tìm kiếm "Cung cấp mọi thứ". Nó bỏ qua dấu phân cách cho mục cuối cùng.
13ren

@ 13ren Làm rất tốt! Nó có ích gì không? Tôi đã có một khóa và nhanh chóng nhớ ra lý do tại sao tôi chọn không chương trình trong C những ngày này: P
Tarn

@tarn, vâng, đó là một ví dụ thú vị vì nó chỉ thêm dấu phẩy nếu nó không phải là mục cuối cùng: if (i <seqlen - 1) {<thêm dấu phân tách>}
13ren

0

Bạn cũng có thể thêm vô điều kiện chuỗi dấu phân cách và sau vòng lặp, hãy xóa dấu phân cách thừa ở cuối. Sau đó, "nếu danh sách trống thì trả về chuỗi này" ở đầu sẽ cho phép bạn tránh bị kiểm tra ở cuối (vì bạn không thể xóa các ký tự khỏi danh sách trống)

Vì vậy, câu hỏi thực sự là:

"Với một vòng lặp và một dấu nếu, bạn nghĩ cách rõ ràng nhất để kết hợp chúng với nhau là gì?"


0
public static String join (List<String> list, String separator) {
  String listToString = "";

  if (list == null || list.isEmpty()) {
   return listToString;
  }

  for (String element : list) {
   listToString += element + separator;
  }

  listToString = listToString.substring(0, separator.length());

  return listToString;
}

1
Tôi nghĩ bạn muốn nóilistToString.substring(0, listToString.length()-separator.length());
13ren 23/09/10

0
if (array.length>0)          // edited in response Joachim's comment
  sb.append(array[i]);
for (int i=1; i<array.length; i++)
  sb.append(",").append(array[i]);

Dựa trên cách rõ ràng nhất để phân cách bằng dấu phẩy một danh sách (Java)?

Sử dụng ý tưởng này: Phần tử cuối cùng trong vòng lặp có xứng đáng được xử lý riêng không?


1
Để điều này hoạt động, bạn cần biết rằng có ít nhất 1 phần tử trong danh sách, nếu không vòng lặp đầu tiên của bạn sẽ gặp sự cố với một IndexOutOfBoundsException.
Joachim H. Skeie

@Joachim cảm ơn, tất nhiên là bạn đúng rồi. Thật là một giải pháp xấu xí mà tôi đã viết! Tôi sẽ thay đổi for (int i=0; i<1; i++)thành if (array.length>0).
13ren

0
public String toString(List<Item> items)
{
    StringBuilder sb = new StringBuilder("[");

    for (Item item : items)
    {
        sb.append(item).append(", ");
    }

    if (sb.length() >= 2)
    {
        //looks cleaner in C# sb.Length -= 2;
        sb.setLength(sb.length() - 2);
    }

    sb.append("]");

    return sb.toString();
}

1
Chỉ cần nhận thấy rằng nó giống với câu trả lời mà
TofuBeer

0

Không có câu trả lời nào sử dụng đệ quy cho đến nay ...

public class Main {

    public static String toString(List<String> list, char d) {
        int n = list.size();
        if(n==0) return "";
        return n > 1 ? Main.toString(list.subList(0, n - 1), d) + d
                  + list.get(n - 1) : list.get(0);
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList(new String[]{"1","2","3"});
        System.out.println(Main.toString(list, ','));
    }

}

0
private String betweenComma(ArrayList<String> strings) {
    String united = "";
    for (String string : strings) {
        united = united + "," + string;
    }
    return united.replaceFirst(",", "");
}

-1

phương pháp

String join(List<Object> collection, String delimiter){
    StringBuilder stringBuilder = new StringBuilder();
    int size = collection.size();
    for (Object value : collection) {
        size --;
        if(size > 0){
            stringBuilder.append(value).append(delimiter);
        }
    }

    return stringBuilder.toString();
}

Sử dụng

Cho mảng [1,2,3]

join(myArray, ",") // 1,2,3
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.