来自Perl,我肯定错过了在源代码中创建多行字符串的“here-document”方法:

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

在Java中,当我从头开始连接多行字符串时,我必须在每一行上使用繁琐的引号和加号。

有什么更好的选择吗?在属性文件中定义我的字符串?

编辑:有两个答案说StringBuilder.append()比加号更可取。谁能详细解释一下他们为什么这么想?在我看来,这一点也不可取。我正在寻找一种方法来解决多行字符串不是一级语言结构这一事实,这意味着我绝对不想用方法调用取代一级语言结构(字符串连接与加号)。

编辑:为了进一步澄清我的问题,我根本不关心性能。我关心的是可维护性和设计问题。


当前回答

    import org.apache.commons.lang3.StringUtils;

    String multiline = StringUtils.join(new String[] {
        "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",
        }, "\n");

其他回答

Java 13及以上版本

Java中现在通过文本块支持多行字符串。在Java 13和14中,这个特性要求你在构建和运行项目时设置——enable-preview选项。在Java 15及以后版本中,由于文本块已成为标准特性,因此不再需要此选项。查看官方的文本块编程指南,了解更多细节。

现在,在Java 13之前,你是这样写查询的:

List<Tuple> posts = entityManager
.createNativeQuery(
    "SELECT *\n" +
    "FROM (\n" +
    "    SELECT *,\n" +
    "           dense_rank() OVER (\n" +
    "               ORDER BY \"p.created_on\", \"p.id\"\n" +
    "           ) rank\n" +
    "    FROM (\n" +
    "        SELECT p.id AS \"p.id\",\n" +
    "               p.created_on AS \"p.created_on\",\n" +
    "               p.title AS \"p.title\",\n" +
    "               pc.id as \"pc.id\",\n" +
    "               pc.created_on AS \"pc.created_on\",\n" +
    "               pc.review AS \"pc.review\",\n" +
    "               pc.post_id AS \"pc.post_id\"\n" +
    "        FROM post p\n" +
    "        LEFT JOIN post_comment pc ON p.id = pc.post_id\n" +
    "        WHERE p.title LIKE :titlePattern\n" +
    "        ORDER BY p.created_on\n" +
    "    ) p_pc\n" +
    ") p_pc_r\n" +
    "WHERE p_pc_r.rank <= :rank\n",
    Tuple.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setParameter("rank", 5)
.getResultList();

多亏了Java 13 Text Blocks,你可以像下面这样重写这个查询:

List<Tuple> posts = entityManager
.createNativeQuery("""
    SELECT *
    FROM (
        SELECT *,
               dense_rank() OVER (
                   ORDER BY "p.created_on", "p.id"
               ) rank
        FROM (
            SELECT p.id AS "p.id",
                   p.created_on AS "p.created_on",
                   p.title AS "p.title",
                   pc.id as "pc.id",
                   pc.created_on AS "pc.created_on",
                   pc.review AS "pc.review",
                   pc.post_id AS "pc.post_id"
            FROM post p
            LEFT JOIN post_comment pc ON p.id = pc.post_id
            WHERE p.title LIKE :titlePattern
            ORDER BY p.created_on
        ) p_pc
    ) p_pc_r
    WHERE p_pc_r.rank <= :rank
    """,
    Tuple.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setParameter("rank", 5)
.getResultList();

可读性强多了,对吧?

支持的想法

IntelliJ IDEA提供了将传统字符串连接块转换为新的多行字符串格式的支持:

Json, html, XML

多行String在编写JSON、HTML或XML时特别有用。

考虑下面的例子,使用String连接来构建一个JSON字符串文字:

entityManager.persist(
    new Book()
    .setId(1L)
    .setIsbn("978-9730228236")
    .setProperties(
        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.99," +
        "   \"reviews\": [" +
        "       {" +
        "           \"reviewer\": \"Cristiano\", " +
        "           \"review\": \"Excellent book to understand Java Persistence\", " +
        "           \"date\": \"2017-11-14\", " +
        "           \"rating\": 5" +
        "       }," +
        "       {" +
        "           \"reviewer\": \"T.W\", " +
        "           \"review\": \"The best JPA ORM book out there\", " +
        "           \"date\": \"2019-01-27\", " +
        "           \"rating\": 5" +
        "       }," +
        "       {" +
        "           \"reviewer\": \"Shaikh\", " +
        "           \"review\": \"The most informative book\", " +
        "           \"date\": \"2016-12-24\", " +
        "           \"rating\": 4" +
        "       }" +
        "   ]" +
        "}"
    )
);

由于转义字符和大量的双引号和加号,您几乎无法阅读JSON。

使用Java文本块,JSON对象可以这样写:

entityManager.persist(
    new Book()
    .setId(1L)
    .setIsbn("978-9730228236")
    .setProperties("""
        {
           "title": "High-Performance Java Persistence",
           "author": "Vlad Mihalcea",
           "publisher": "Amazon",
           "price": 44.99,
           "reviews": [
               {
                   "reviewer": "Cristiano",
                   "review": "Excellent book to understand Java Persistence",
                   "date": "2017-11-14",
                   "rating": 5
               },
               {
                   "reviewer": "T.W",
                   "review": "The best JPA ORM book out there",
                   "date": "2019-01-27",
                   "rating": 5
               },
               {
                   "reviewer": "Shaikh",
                   "review": "The most informative book",
                   "date": "2016-12-24",
                   "rating": 4
               }
           ]
        }
        """
    )
);

自从我在2004年使用c#以来,我一直希望在Java中有这个功能,现在我们终于有了它。

我有时使用一个并行groovy类来充当一个字符串包

这里的java类

public class Test {
    public static void main(String[] args) {
        System.out.println(TestStrings.json1);
        // consume .. parse json
    }
}

以及TestStrings.groovy中令人垂涎的多行字符串

class TestStrings {
    public static String json1 = """
    {
        "name": "Fakeer's Json",
        "age":100,
        "messages":["msg 1","msg 2","msg 3"]
    }""";
}

当然,这只适用于静态字符串。如果我必须在文本中插入变量,我会将整个文件更改为groovy。只要保持强类型实践,它就可以实现。

当我读到多行语法确实是为jdk7计划的(在java存在了多少年之后?)时,我有点恼火。有趣的是,甚至还没有readAll()函数用于读取文件的完整内容(仅从jdk7读取,啊啊),因此下面的代码读取单行。

/*
  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);
    }
  }
}

这对我来说是最快的解决办法。(2010-01-27)

这个问题有两个答案:

In you want to stick to pure Java, with Java 14 being released in March 2020, you can leverage the JEP 368 - Text Blocks, in Second Preview mode. Actually the feature is in preview mode in other releases (at least 13 has it). I created and example set here. While this feature is useful, it can be easily abused. Remember that Java requires compilation - having large character arrays in your code can be an easy way to shoot yourself in the leg (if you want a quick change, you will need recompilation - that toolset might not be available to the guy operating your application).

根据我的经验,建议在配置文件中保留大字符串(通常是应用程序操作员可以/应该在运行时更改的字符串)。

总结:负责地使用文本块:)。

参见Java Stringfier。如果需要,将文本转换为StringBuilder java块转义。