views:

9585

answers:

21

Coming from Perl, I sure am missing the "here-document" means of creating a multi-line string in source code:

$string = <<"EOF"  # create a three line string
text
text
text
EOF

In Java I have to have cumbersome quotes and plus signs on every line as I concatenate my multiline string from scratch.

What are some better alternatives? Define my string in a properties file?

Edit: Two answers say StringBuilder.append() is preferable to the plus notation. Could anyone elaborate as to why they think so? It doesn't look more preferable to me at all. I'm looking for away around the fact that multiline strings are not a first-class language construct, which means I definitely don't want to replace a first-class language construct (string concatenation with plus) with method calls.

Edit: To clarify my question further, I'm not concerned about performance at all. I'm concerned about maintainability and design issues.

+1  A: 

The only way I know of is to concatenate multiple lines with plus signs

Josh Curren
See String.concat(String);
eleven81
+3  A: 

Instead of the annoying and unreadable

String newline = System.getProperty ("line.separator");
string1 + newline + string2 + newline + string3

But, the best alternative is to use String.format

   String multilineString = String.format("%s\n%s\n%s\n",line1,line2,line3);
Tom
How is StringBuilder less annoying and unreadable?
Michael Myers
My opinion is that it removes the plus signs and quotes, making it more readable, specially when there are more than just 3 lines. Not as good as String.format though.
Tom
Stringbuilder example is at least as unreadable. Also, don't forget that "\n" isn't always a newline, but it's fine for linux and unix machines.
Stefan Thyberg
Plus, just wanted to mention the existance of StringBuilder.
Tom
Replacing one plus sign with a six-character method name and parentheses doesn't look more readable to me, although apparently you're not the only one who thinks that way. It doesn't remove the quotes, though. They are still there.
skiphoppy
+12  A: 

Another option may be to store long strings in an external file and read the file into a string.

Josh Curren
Exactly. Large amounts of text don't belong in Java source; use a resource file of an appropriate format instead, loaded via a call to Class.getResource(String).
erickson
Right! You can use Locale + ResourceBundle's also to easily load the I18N text, and then the String.format() call will parse the "\n"'s as newlines :) Example: String readyStr = String.parse(resourceBundle.getString("introduction"));
ATorras
You shouldn't have to externalize a String just because it's multi-line. What if I have a regex that I want to break up into multiple lines with comments? It looks ugly in Java. The `@` syntax for C# is much cleaner.
Jeremy Stein
Skiphoppy doesn't want to bother with the overhead of dealing with files just to use a paragraph length string constant. I use multiline strings all the time in C++, embedded in my source code, where I want them.
Tim Cooper
+2  A: 

you can concatenate your appends in a separate method like

public static String multilineString(String... lines){
   StringBuilder sb = new StringBuilder();
   for(String s : lines){
     sb.append(s);
     sb.append ('\n');
   }
   return sb.toStirng();
}

either way, prefer StringBuilder to the plus notation.

Why do I prefer StringBuilder to the plus notation?
skiphoppy
Efficiency, or rather an often-misguided attempt at it.
Michael Myers
The attempt at efficiency is based, I think, on the fact that the Java compiler implements the string concatenation operator using StringBuilder (StringBuffer in pre-1.5 compilers). There is an old, but well-known article stating that there are performance benefits in certain situations to using StringBuffer (or StringBuilder, now). Here's the link: http://java.sun.com/developer/JDCTechTips/2002/tt0305.html
Paul Morie
+1  A: 

Define my string in a properties file?

Multiline strings aren't allowed in properties files. You can use \n in properties files, but I don't think that is much of a solution in your case.

Kip
The value in a properties file can span multiple lines: Just end all lines but the last with a backslash.This does leave the problem of what you use as the line separator, as this is platform-specific. I suppose you could use a simple \n and then in your code, after reading the property, do a search-and-replace of \n to line.separator. That seems a little kludgey, but I guess you could write a function that retrieves a property and does this manipulation at the same time. Well, all that assumes that you'd be writing these strings to a file, which is a big assumption.
Jay
+4  A: 

If you define your strings in a properties file it'll look much worse. IIRC, it'll look like:

string:text\u000atext\u000atext\u000a

Generally it's a reasonable idea to not embed large strings in to source. You might want to load them as resources, perhaps in XML or a readable text format. The text files can be either read at runtime or compiled into Java source. If you end up placing them in the source, I suggest putting the + at the front and omitting unnecessary new lines:

final String text = ""
    +"text "
    +"text "
    +"text"
;

If you do have new lines, you might want some of join or formatting method:

final String text = join("\r\n"
    ,"text"
    ,"text"
    ,"text"
);
Tom Hawtin - tackline
+1 I like the join look the best.
Blindy
+1  A: 

A quite efficient and platform independent solution would be using the system property for line separators and the StringBuilder class to build strings:

String separator = System.getProperty("line.separator");
String lines = {"Line 1", "Line 2" /*, ... */};

StringBuilder builder = new StringBuilder(lines[0]);
for (int i = 1; i < lines.length(); i++) {
    builder.append(separator).append(lines[i]);
}
String multiLine = builder.toString();
Andreas_D
+1 for mentioning the line.separator property :-)
Stefan Thyberg
This is the method that I usually use too.
Chris
+23  A: 

It sounds like you want to do a multiline literal, in which case your best option (IMHO) is going to be strings that are +'d together. The other options (StringBuilder and String.format) would only be preferable if you already had your string in an array.

Consider this:

String s = "It was the best of times, it was the worst of times,\n"
         + "it was the age of wisdom, it was the age of foolishness,\n"
         + "it was the epoch of belief, it was the epoch of incredulity,\n"
         + "it was the season of Light, it was the season of Darkness,\n"
         + "it was the spring of hope, it was the winter of despair,\n"
         + "we had everything before us, we had nothing before us";

Versus String Builder:

String s = new StringBuilder()
           .append("It was the best of times, it was the worst of times,\n")
           .append("it was the age of wisdom, it was the age of foolishness,\n")
           .append("it was the epoch of belief, it was the epoch of incredulity,\n")
           .append("it was the season of Light, it was the season of Darkness,\n")
           .append("it was the spring of hope, it was the winter of despair,\n")
           .append("we had everything before us, we had nothing before us")
           .toString();

Versus String.format:

String s = String.format("%s\n%s\n%s\n%s\n%s\n%s", 
               "It was the best of times, it was the worst of times,",
               "it was the age of wisdom, it was the age of foolishness,",
               "it was the epoch of belief, it was the epoch of incredulity,",
               "it was the season of Light, it was the season of Darkness,",
               "it was the spring of hope, it was the winter of despair,",
               "we had everything before us, we had nothing before us"
           );

If you want the newline for your particular system, you either need to use System.getProperty("line.separator"), or you can use %n in String.format.

Kip
Furthermore, the first version will be automatically concatenated by the compiler, since all the strings are known at compile time. Even if the strings are not known at compile time, it's no slower than StringBuilder or String.format(). The only reason to avoid concatenation with +'s is if you're doing it in a loop.
Michael Myers
+1 for the example text :)
Michael Borgwardt
The StringBuilder is what I've seen (again and again) as the maintainable and efficient code. The String.format is efficient, but uncommon enough for Java developers that the maintainability drops.
Dean J
A: 

As was mentioned here before me, extremely long strings don't belong into Java code. If you have a multi-line string, then put in in a file and read it in. Java is not a scripting language like e.g. PHP and you should not interleave your HTML (etc...) with logic of your code. Read it in from external files.

Could the down-voters please leave a comment? I am quite puzzled... Especially when I read the following in the question:

I'm concerned about maintainability and design issues.

If so, what's so bad about suggesting to use external files for long strings?

Peter Perháč
+1  A: 

Sadly, Java does not have multi-line string literals. You either have to concatenate string literals (using + or StringBuilder being the two most common approaches to this) or read the string in from a separate file.

For large multi-line string literals I'd be inclined to use a separate file and read it in using getResourceAsStream() (a method of the Class class). This makes it easy to find the file as you don't have to worry about the current directory versus where your code was installed. It also makes packaging easier, because you can actually store the file in your jar file.

Suppose you're in a class called Foo. Just do something like this:

Reader r = new InputStreamReader(Foo.class.getResourceAsStream("filename"), "UTF-8");
String s = Utils.readAll(r);

The one other annoyance is that Java doesn't have a standard "read all of the text from this Reader into a String" method. It's pretty easy to write though:

public static String readAll(Reader input) {
    StringBuilder sb = new StringBuilder();
    char[] buffer = new char[4096];
    int charsRead;
    while ((charsRead = input.read(buffer)) >= 0) {
        sb.append(buffer, 0, charsRead);
    }
    input.close();
    return sb.toString();
}
Laurence Gonsalves
+9  A: 

Stephen Colebourne has created a proposal for adding multi-line strings in Java 7.

Also, Groovy already has support for multi-line strings.

Paul Morie
+7  A: 

Pluses are converted to StringBuilder.append, except when both strings are constants so the compiler can combine them at compile time. At least, that's how it is in Sun's compiler, and I would suspect most if not all other compilers would do the same.

So:

String a="Hello";
String b="Goodbye";
String c=a+b;

normally generates exactly the same code as:

String a="Hello";
String b="Goodbye":
StringBuilder temp=new StringBuilder();
temp.append(a).append(b);
String c=temp.toString();

On the other hand:

String c="Hello"+"Goodbye";

is the same as:

String c="HelloGoodbye";

That is, there's no penalty in breaking your string literals across multiple lines with plus signs for readability.

Jay
to be technical, in your first example it generates something more like: String c = new StringBuilder().append(a).append(b).toString(); The difference being that the temporary string builder is out of scope and eligible for garbage collection immediately after the String c=... line, whereas the "temp" string builder would stay around a little longer.
Kip
True. My point, of course, is to distinguish when a function gets called at run-time versus when the work can be done at compile time. But you are correct.
Jay
A: 

When a long series of + are used, only one StringBuilder is created, unless the String is determined at compile time in which case no StringBuilder is used!

The only time StringBuilder is more efficient is when multiple statements are used to construct the String.

String a = "a\n";
String b = "b\n";
String c = "c\n";
String d = "d\n";

String abcd = a + b + c + d;
System.out.println(abcd);

String abcd2 = "a\n" +
        "b\n" +
        "c\n" +
        "d\n";
System.out.println(abcd2);

Note: Only one StringBuilder is created.

  Code:
   0:   ldc     #2; //String a\n
   2:   astore_1
   3:   ldc     #3; //String b\n
   5:   astore_2
   6:   ldc     #4; //String c\n
   8:   astore_3
   9:   ldc     #5; //String d\n
   11:  astore  4
   13:  new     #6; //class java/lang/StringBuilder
   16:  dup
   17:  invokespecial   #7; //Method java/lang/StringBuilder."<init>":()V
   20:  aload_1
   21:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   24:  aload_2
   25:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   28:  aload_3
   29:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   32:  aload   4
   34:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   37:  invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   40:  astore  5
   42:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   45:  aload   5
   47:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   50:  ldc     #12; //String a\nb\nc\nd\n
   52:  astore  6
   54:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   57:  aload   6
   59:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   62:  return

To clarify my question further, I'm not concerned about performance at all. I'm concerned about maintainability and design issues.

Make it as clear and simple as you can.

Peter Lawrey
A: 

A simple option is to edit your java-code with an editor like SciTE (http://www.scintilla.org/SciTEDownload.html), which allows you to WRAP the text so that long strings are easily viewed and edited. If you need escape characters you just put them in. By flipping the wrap-option off you can check that your string indeed is still just a long single-line string. But of course, the compiler will tell you too if it isn't.

Whether Eclipse or NetBeans support text-wrapping in an editor I don't know, because they have so many options. But if not, that would be a good thing to add.

Or, you can use SciTE just to edit your string in wrap-mode, double-quote it, then copy and paste it to Eclipse.
As others have suggested it is better to keep your long strings in external files, so you can modify them without having to recompile, you can pick different resource-directory for different audiences etc. The downside is that this r4equires a bit of overhead, knowing where to read the files from etc. I wish there was a simple but complete code-example on how best to do this
+2  A: 

I found this question because I want to have a here doc for multi-line help text in my little java command line utility. It looks like it's not possible currently (circa Java 1.6). However what really bothers me is the number of people who say that I shouldn't even want to have a here doc in java source. How do they know what the developer wants? Just because having a separate file works well in most circumstances does not mean that it works well in all circumstances. For example, I'm building a simple utility class that might get used once or twice a year -- I want to remember how to use it without looking at the source code. I want everything in the .class file, not hanging around in separate text files that I have to remember to copy around with the .class. A here document works perfectly in this case. It's not the best solution for every situation, but it is a good solution for some situations. Laying a blanket "don't do that because I wouldn't do it" is a sign of a closed mind.

JohnnyLambada
I agree with you strongly. I'm so tired of hearing "Why would you want to do that?" when I ask how to do something that I have used productively and maintainably in another language. If you have a tangible argument against using it, or if you want to explain a language's rationale for not providing something, I'll listen. If you just don't understand why I want to use it, then you don't have a tangible argument and might as well remain silent.
skiphoppy
A: 

This is something that you should never use without thinking about what it's doing. But for one-off scripts I've used this with great success:

Example:

    System.out.println(S(/*
This is a CRAZY " ' ' " multiline string with all sorts of strange 
   characters!
*/));

Code:

// From: http://blog.efftinge.de/2008/10/multi-line-string-literals-in-java.html
// Takes a comment (/**/) and turns everything inside the comment to a string that is returned from S()
public static String S() {
 StackTraceElement element = new RuntimeException().getStackTrace()[1];
 String name = element.getClassName().replace('.', '/') + ".java";
 StringBuilder sb = new StringBuilder();
 String line = null;
 InputStream in = classLoader.getResourceAsStream(name);
 String s = convertStreamToString(in, element.getLineNumber());
 return s.substring(s.indexOf("/*")+2, s.indexOf("*/"));
}

// From http://www.kodejava.org/examples/266.html
private static String convertStreamToString(InputStream is, int lineNum) {
    /*
     * To convert the InputStream to String we use the BufferedReader.readLine()
     * method. We iterate until the BufferedReader return null which means
     * there's no more data to read. Each line will appended to a StringBuilder
     * and returned as String.
     */
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    StringBuilder sb = new StringBuilder();

    String line = null; int i = 1;
    try {
        while ((line = reader.readLine()) != null) {
            if (i++ >= lineNum) {
                sb.append(line + "\n");
   }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return sb.toString();
}
Bob Albright
Requires shipping the Java code for the class with the final binary. Hmm.
Thorbjørn Ravn Andersen
A: 

An alternative I haven't seen as answer yet is the java.io.PrintWriter.

StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
writer.println("It was the best of times, it was the worst of times");
writer.println("it was the age of wisdom, it was the age of foolishness,");
writer.println("it was the epoch of belief, it was the epoch of incredulity,");
writer.println("it was the season of Light, it was the season of Darkness,");
writer.println("it was the spring of hope, it was the winter of despair,");
writer.println("we had everything before us, we had nothing before us");
String string = stringWriter.toString();

Also the fact that java.io.BufferedWriter has a newLine() method is unmentioned.

BalusC
A: 

I got a bit annoyed with reading that multiline syntax is indeed been planned for jdk7 (after about how many decades of java existence?). Funnily, there is not even yet a readAll() function for reading the complete contents of a file (from jdk7 only, huhh), so the code below reads single lines.

/*
  MakeMultiline v1.0 (2010) - Free to use and copy.

  Small gadget to turn text blobs into one java string literal
  (doing the split in lines, adding \n at each end and enclosing
  in double quotes). Does escape quotes encountered in the text blob.

  Useful for working around missing multiline string syntax in java
  prior jdk7. Use with:

     java MakeMultiline "    "
  or
     java MakeMultiline "    " mytextfile.txt
*/

import java.io.*;

class MakeMultiline {
  public static void main(String[] args) {
    try {
      // args[0]: indent
      // args[1]: filename to read (optional; stdin if not given)
      // Beware the nmb of newlines at the end when using stdin!

      String indent = (args.length > 0 ? args[0] : "");
      FileReader fr = null; BufferedReader br;
      if (args.length > 1)
        { fr =  new FileReader(args[1]); br = new BufferedReader(fr); }
      else
        { br = new BufferedReader(new InputStreamReader(System.in)); }
      String s; String res = "";
      while((s = br.readLine()) != null) {
        if (res.length() > 0) res += " +\n";
        res += indent + "\"" + s.replace("\"", "\\\"") + "\\n\"";
      }
      br.close(); if (fr != null) fr.close();
      System.out.println(res + ";");
    }
    catch(Exception e) {
      System.out.println("Exception: " + e);
    }
  }
}

This was the quickest solution for me. (2010-01-27)

ThomasP
+1  A: 

I suggest using a utility as suggested by ThomasP; and then link that into your build process. An external file is still present to contain the text, but the file is not read at runtime. The workflow is then:

  1. build a 'textfile to java code' utility & check into version control
  2. on each build, run the utility against the resource file to create revised java source
  3. the java source contains a header like class TextBlock {... followed by a static string which is auto-generated from the resource file
  4. build the generated java file with the rest of your code
horace
+1  A: 

You may use scala-code, which is compatible to java, and allows multiline-Strings enclosed with """:

package foobar
object SWrap {
  def bar = """John said: "This is
  a test
  a bloody test,
  my dear." and closed the door.""" 
}

(note the quotes inside the string) and from java:

String s2 = foobar.SWrap.bar ();

Whether this is more comfortable ...?

Another approach, if you often handle long text, which should be placed in your sourcecode, might be a script, which takes the text from an external file, and wrappes it as a multiline-java-String like this:

sed '1s/^/String s = \"/;2,$s/^/\t+ "/;2,$s/$/"/' file > file.java

so that you may cut-and-paste it easily into your source.

user unknown
A: 

I see at least one case where it should be avoided to use external files for long strings : if these long string are expected values in an unit-test file, because I think the tests should always be written in a way that they don't rely on any external resource.

Deltaplan