33

I'm iterating through a HashMap (see my earlier question for more detail) and building a string consisting of the data contained in the Map. For each item, I will have a new line, but for the very last item, I don't want the new line. How can I achieve this? I was thinking I could so some kind of check to see if the entry is the last one or not, but I'm not sure how to actually do that.

Thanks!

Community
  • 1
  • 1
troyal
  • 2,499
  • 6
  • 25
  • 28
  • 1
    In normal text file format, the "newline" really isn't a newline, it is a line terminator. Thus to be correctly formed, a file should have one on the last line. I don't know if you're writing to a file or not, so it may not apply. Reference: http://en.wikipedia.org/wiki/Newline – rmeador Jan 15 '09 at 20:36
  • In this instance, I'm not writing to a file. But I typically end with a newline for writing files anyways. – troyal Jan 27 '09 at 17:47

16 Answers16

73

Change your thought process from "append a line break all but the last time" to "prepend a line break all but the first time":

boolean first = true;
StringBuilder builder = new StringBuilder();

for (Map.Entry<MyClass.Key,String> entry : data.entrySet()) {
    if (first) {
        first = false;
    } else {
        builder.append("\n"); // Or whatever break you want
    }
    builder.append(entry.key())
           .append(": ")
           .append(entry.value());
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Thanks! I especially appreciate the reference to a change in thought process. I often forget that there are myriad ways to solve these types of problems (you'd think I'd know better). – troyal Jan 15 '09 at 20:31
  • With ordered collections, you can get rid of the boolean by creating the StringBuilder with the first item, then running the loop from i to the end: new StringBuilder(collection.get(0)); for(int i = 1; i < collection.size(); i++). Be sure to guard against empty collections. – Tim Frey Jan 15 '09 at 20:40
  • 1
    @Blue: +1 for using "myriad" properly. Far too many people say "a myriad" which is like saying "the hoi polloi" :( – Jon Skeet Jan 15 '09 at 20:47
  • This is even nicer when factoring the if/else into a Separator Class whose #toString method prints empty string in the first call, and the separator on all subsequent calls. – akuhn Mar 05 '09 at 23:42
  • This loop could be optimized to avoid creating 2 strings for each iteration through the loop. builder.append('\n'); and builder.append(':').append(' '); – Fostah Jun 23 '09 at 20:25
  • 1
    @JonSkeet I always learn something new in your answers. Thanks a lot to keep answering every day in SO. – Michel Ayres Jul 31 '12 at 16:24
  • @akuhn: but only if you define 'harder to read' as nicer and like the fact, that Java already requires you to have much more classes than actually needed. ;) – Bouncner Mar 06 '13 at 16:26
73

one method (with apologies to Jon Skeet for borrowing part of his Java code):

StringBuilder result = new StringBuilder();

string newline = "";  
for (Map.Entry<MyClass.Key,String> entry : data.entrySet())
{
    result.append(newline)
       .append(entry.key())
       .append(": ")
       .append(entry.value());

    newline = "\n";
}
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 9
    Oh that's very nice indeed. I like that a *lot*. So tempting to register more users to upvote more times ;) – Jon Skeet Jan 15 '09 at 20:48
  • I do it that way, too *thumbs up* – Xn0vv3r Jan 16 '09 at 05:57
  • Personally I see this as the lesser evil. I use it, but I don't like it. The code is cleaner than an if/else version, but you still have to look at it an extra moment to understand it. And it codes the delimiter at the end of the block rather than the top. I just don't know anything better. – Joel Coehoorn Jan 16 '09 at 14:34
  • Which operation is cheaper among assignment(Joel's) and condition(Jon's) that are done in every iteration? – grayger Mar 06 '09 at 03:49
  • If it actually does the operation, I suspect condition. But I also expect the assignment to be jit'd away, so you'll have to experiment/profile to know for sure. Either way, the code clarity is probably more important. – Joel Coehoorn Mar 06 '09 at 04:50
  • Perhaps clarity could be helped by using the variable name `newlineOrBlank` rather than `newline`. – Basil Bourque Oct 29 '16 at 04:09
  • If I were to change the `newline` variable name, I'd use `delimiter`. – Joel Coehoorn Aug 28 '20 at 19:52
13

What about this?

StringBuilder result = new StringBuilder();

for(Map.Entry<MyClass.Key,String> entry : data.entrySet())
{
    builder.append(entry.key())
       .append(": ")
       .append(entry.value())
       .append("\n");
}

return builder.substring(0, builder.length()-1);

Obligatory apologies and thanks to both Jon and Joel for "borrowing" from their examples.

Jay R.
  • 31,911
  • 17
  • 52
  • 61
  • 1
    This is my typical approach to creating delimited strings. – Wes P Jan 16 '09 at 19:16
  • 3
    this gives a java.lang.StringIndexOutOfBoundsException with a empty list. – pvgoddijn Jan 20 '09 at 12:52
  • 1
    That exception is easy to fix with a simple if which checks if builder length is greater than 0. – Domchi Mar 06 '09 at 00:05
  • 1
    +1, this solution has better performance then the repetitive if-else check or the repetitive assignment to the newline string – Yoni Jul 20 '10 at 10:49
  • 5
    It would be more self-documenting if you used some constant `DELIMITER = "\n"`,`...append(DELIMITER)`, and then used `..., builder.length()-DELIMITER.length()`. This would avoid bugs where someone changes the delimiter but forgets to update the -1 accordingly. – Christophe Roussy Feb 04 '13 at 15:03
9

Ususally for these kind of things I use apache-commons-lang StringUtils#join. While it's not really hard to write all these kinds of utility functionality, it's always better to reuse existing proven libraries. Apache-commons is full of useful stuff like that!

André
  • 12,971
  • 3
  • 33
  • 45
5

If you use iterator instead of for...each your code could look like this:

StringBuilder builder = new StringBuilder();

Iterator<Map.Entry<MyClass.Key, String>> it = data.entrySet().iterator();

while (it.hasNext()) {
    Map.Entry<MyClass.Key, String> entry = it.next();

    builder.append(entry.key())
    .append(": ")
    .append(entry.value());

    if (it.hasNext()) {
        builder.append("\n");
    }
}
jan
  • 273
  • 1
  • 6
  • +1 - whenever I need to special-case the first/last element, I prefer doing it this way rather than using a boolean variable to detect the first character. – Andrzej Doyle Jan 16 '09 at 14:20
3

This is probably a better example...

final StringBuilder buf = new StringBuilder();
final String separator = System.getProperty("line.separator"); // Platform new line
for (Map.Entry<MyClass.Key,String> entry: data.entrySet()) {
    builder.append(entry.key())
       .append(": ")
       .append(entry.value())
       .append(separator);
}
// Remove the last separator and return a string to use.
// N.b. this is likely as efficient as just using builder.toString() 
// which would also copy the buffer, except we have 1 char less 
// (i.e. '\n').
final String toUse = builder.substring(0, builder.length()-separator.length()-1);
Ben
  • 31
  • 1
2

Here's my succinct version, which uses the StringBuilder's length property instead of an extra variable:

StringBuilder builder = new StringBuilder();

for (Map.Entry<MyClass.Key,String> entry : data.entrySet())
{
    builder.append(builder.length() > 0 ? "\n" : "")
           .append(entry.key())
           .append(": ")
           .append(entry.value());
}

(Apologies and thanks to both Jon and Joel for "borrowing" from their examples.)

LukeH
  • 263,068
  • 57
  • 365
  • 409
2

One solution is to create a custom wrapper to StringBuilder. It can't be extended, thus a wrapper is required.

public class CustomStringBuilder {

final String separator = System.getProperty("line.separator");

private StringBuilder builder = new StringBuilder();

public CustomStringBuilder appendLine(String str){
    builder.append(str + separator);
    return this;
}

public CustomStringBuilder append(String str){
    builder.append(str);
    return this;
}

public String toString() {
    return this.builder.toString();
}

}

Implementation like such:

CustomStringBuilder builder = new CustomStringBuilder();

//iterate over as needed, but a wrapper to StringBuilder with new line features.

builder.appendLine("data goes here");
return builder.toString();

This does have some downsides:

  • Writing code that's typically not "domain / business" centric
  • Not using open source standard solution like: StringUtils.join
  • Forced to maintain a class that wraps a JDK class that's final and thus updates required long term.

I went with the StringUtils.join solution for iterating over collections and even building lightweight build method patterns in the code.

Nick N
  • 984
  • 9
  • 22
1

Assuming your foreach loop goes through the file in order just add a new line to every string and remove the last new line when your loop exits.

Jared
  • 39,513
  • 29
  • 110
  • 145
1

Not sure if this is the best, but it´s the easier way to do:

loop through all the values and append the \n normally in the stringbuffer. Then, do something like this

sb.setLength(sb.length()-1); 
Tiago
  • 9,457
  • 5
  • 39
  • 35
  • this works for linux/unix where line separator char is '\n' and for mac where it is '\r' but for other OSes like windows which is "\r\n" this doesn't really work, you should check the length of the line terminator from the system properties. – Paulo Lopes Jan 15 '09 at 21:36
  • The op explicitly said he was appending '\n', so the length is known. – Richard Campbell Jan 15 '09 at 22:02
0

Let libraries do this for you.

import com.sun.deploy.util.StringUtils;

as well as many others have StringUtils, which has a join method. You can do this in one line:

StringUtils.join(list, DELIMITER);

But for more context, here is how you could do it with a hashmap.

public static String getDelimitatedData(HashMap<String, String> data) {
    final String DELIMITER = "\n"; //Should make this a variable
    ArrayList<String> entries = new ArrayList<>();

    for (Map.Entry<String, String> entry : data.entrySet()) {
        entries.add(entry.getKey() + ": " + entry.getValue());
    }

    return StringUtils.join(entries, DELIMITER);
}
Nick Humrich
  • 14,905
  • 8
  • 62
  • 85
0

This is where a join method, to complement split, would come in handy, because then you could just join all the elements using a new line as the separator, and of course it doesn't append a new line to the end of the result; that's how I do it in various scripting languages (Javascript, PHP, etc.).

Dexygen
  • 12,287
  • 13
  • 80
  • 147
0

Use the JDK string joining APIs:

String result = data.stream()
    .map((k,v) -> String.format("%s : %s", k, v)
    .collect(Collectors.joining("\n"));
Jan Nielsen
  • 10,892
  • 14
  • 65
  • 119
0

You can also achieve this using the trim() method if you are not concerned about the whitespace, and only if you are not going to be appending anything more to the StringBuilder.

Here's some example code

StringBuilder sb = new StringBuilder();

for (Map.Entry<String,String> entry : myMap.entrySet()) {
    builder.append(entry.key())
           .append(": ")
           .append(entry.value())
           .append("\n");
}
return sb.toString().trim();
Aditya Vikas Devarapalli
  • 3,186
  • 2
  • 36
  • 54
0

If you use Class Separator, you can do

StringBuilder buf = new StringBuilder();
Separator separator = new Separator("\n");
for (Map.Entry<MyClass.Key,String> entry: data.entrySet()) {
    builder.append(separator)
       .append(entry.key())
       .append(": ")
       .append(entry.value());
}

The separator prints in empty string upon its first use, and the separator upon all subsequent uses.

akuhn
  • 27,477
  • 2
  • 76
  • 91
0

Ha! Thanks to this post I've found another way to do this:

public static class Extensions
{
    public static string JoinWith(this IEnumerable<string> strings, string separator)
    {
        return String.Join(separator, strings.ToArray());
    }
}

Of course this is in C# now and Java won't (yet) support the extension method, but you ought to be able to adapt it as needed — the main thing is the use of String.Join anyway, and I'm sure java has some analog for that.

Also note that this means doing an extra iteration of the strings, because you must first create the array and then iterate over that to build your string. Also, you will create the array, where with some other methods you might be able to get by with an IEnumerable that only holds one string in memory at a time. But I really like the extra clarity.

Of course, given the Extension method capability you could just abstract any of the other code into an extension method as well.

Community
  • 1
  • 1
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794