Chuyển tới nội dung chính

Text Blocks

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Hiểu Text Blocks (Java 15+) — multi-line string literals với triple quotes
  • Biết cách sử dụng indentation management và trailing whitespace
  • Nắm được string formatting với text blocks (formatted(), replace())
  • Phân biệt text blocks vs regular strings và khi nào dùng cái gì
  • Áp dụng text blocks cho SQL, JSON, HTML, và các template strings

Bài trước: var - Local Variable Type Inference — Đã học về var keyword và type inference. Bài này sẽ tìm hiểu Text Blocks — cách viết multi-line strings dễ đọc hơn.

Giới thiệu

Text blocks được giới thiệu như preview feature trong Java 13, và trở thành standard feature trong Java 15 (September 2020). Text blocks cho phép viết multi-line string literals một cách dễ đọc và dễ maintain hơn.

Vấn đề Text Blocks giải quyết

Trước Text Blocks (Java 14-)

// SQL query - khó đọc, nhiều escape characters
String query = "SELECT id, name, email\n" +
"FROM users\n" +
"WHERE status = 'ACTIVE'\n" +
" AND age > 18\n" +
"ORDER BY name";

// JSON - rất khó đọc
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30,\n" +
" \"address\": {\n" +
" \"city\": \"Hanoi\",\n" +
" \"country\": \"Vietnam\"\n" +
" }\n" +
"}";

// HTML - nightmare
String html = "<html>\n" +
" <body>\n" +
" <h1>Hello, World!</h1>\n" +
" <p>This is a paragraph.</p>\n" +
" </body>\n" +
"</html>";

Vấn đề:

  • Nhiều \n+ operators
  • Phải escape quotes: \"
  • Khó maintain và format
  • Không nhìn thấy structure thực sự

Với Text Blocks (Java 15+)

// SQL query - clean!
String query = """
SELECT id, name, email
FROM users
WHERE status = 'ACTIVE'
AND age > 18
ORDER BY name
""";

// JSON - readable!
String json = """
{
"name": "Alice",
"age": 30,
"address": {
"city": "Hanoi",
"country": "Vietnam"
}
}
""";

// HTML - beautiful!
String html = """
<html>
<body>
<h1>Hello, World!</h1>
<p>This is a paragraph.</p>
</body>
</html>
""";

Cú pháp Text Blocks

Triple-quote syntax

// Syntax: """..."""
String textBlock = """
Line 1
Line 2
Line 3
""";

// IMPORTANT: Opening """ PHẢI có newline sau nó
String valid = """
Content here
""";

// COMPILE ERROR - Không có newline sau """
String invalid = """Content here
""";
Opening triple-quote

""" mở PHẢI có line break ngay sau nó. Không thể viết content trên cùng dòng với """ mở.

// ERROR
String error = """Content
""";

// OK
String ok = """
Content
""";

Closing triple-quote position

Vị trí của """ đóng quyết định indentation:

// Closing """ ở đầu dòng - giữ indentation
String text1 = """
Line 1
Line 2
""";
// Result: "Line 1\nLine 2\n"

// Closing """ thụt vào - remove indentation
String text2 = """
Line 1
Line 2
""";
// Result: "Line 1\nLine 2\n"

// Closing """ cùng level content - no indentation removed
String text3 = """
Line 1
Line 2
""";
// Result: "Line 1\nLine 2\n"

Compiler Processing Pipeline

Compiler xử lý text block qua 3 bước tuần tự:

💡 Analogy — "Cắt ảnh": Hình dung text block như một bức ảnh chụp code. Bước 1: chuẩn hoá (normalize) — đảm bảo ảnh thẳng. Bước 2: cắt viền thừa (strip whitespace) — loại bỏ khoảng trắng "vô nghĩa" ở bên trái. Bước 3: xử lý ký tự đặc biệt — "rửa ảnh" để hiện escape sequences.

Vị trí """ đóng ảnh hưởng indentation

String a = """
Hello ← 4 spaces incidental
World ← 4 spaces incidental
"""; ← """ đóng thụt 4 spaces = xoá 4 spaces

// Result: "Hello\nWorld\n"

String b = """
Hello ← 4 spaces incidental
World ← 4 spaces incidental
"""; ← """ đóng ở column 0 = chỉ xoá 0 spaces!

// Result: " Hello\n World\n" (giữ 4 spaces)

Indentation Rules

Text blocks có 2 loại whitespace:

1. Incidental whitespace (bị xóa)

Incidental whitespace là khoảng trắng "thừa" để align code, sẽ bị compiler tự động xóa:

public void method() {
String sql = """
SELECT *
FROM users
WHERE id = ?
""";
// Compiler xóa 8 spaces đầu mỗi dòng (incidental)
// Result: "SELECT *\nFROM users\nWHERE id = ?\n"
}

2. Essential whitespace (được giữ)

Essential whitespace là khoảng trắng thực sự cần thiết:

String text = """
Line 1
Line 2 (2 spaces thụt vào)
Line 3
""";
// Compiler xóa 4 spaces chung (incidental)
// Giữ 2 spaces thêm ở Line 2 (essential)
// Result: "Line 1\n Line 2 (2 spaces thụt vào)\nLine 3\n"

Quy tắc xác định incidental whitespace

Compiler tìm dòng có ít whitespace nhất (không tính empty lines) và xóa số whitespace đó từ tất cả dòng:

String example = """
Line 1 (8 spaces)
Line 2 (6 spaces) <- MINIMUM
Line 3 (10 spaces)
"""; (8 spaces)

// Compiler xóa 6 spaces từ mọi dòng:
// Result:
// " Line 1\n" + (8-6 = 2 spaces)
// "Line 2\n" + (6-6 = 0 spaces)
// " Line 3\n" (10-6 = 4 spaces)

Minh họa quy tắc Indentation

String Methods cho Text Blocks

stripIndent()

Remove incidental whitespace manually (compiler tự động làm):

String text = """
Line 1
Line 2
""".stripIndent();
// Thường không cần vì compiler tự xóa

translateEscapes()

Process escape sequences (\n, \t, etc.):

String text = """
Line 1\\tTab
Line 2\\nNewline
""".translateEscapes();
// Converts \\t -> \t, \\n -> \n

formatted() / format()

Format text block với placeholders:

String template = """
{
"name": "%s",
"age": %d,
"email": "%s"
}
""";

String json = template.formatted("Alice", 30, "[email protected]");
// hoặc
String json = String.format(template, "Alice", 30, "[email protected]");

indent()

Add hoặc remove indentation:

String text = """
Line 1
Line 2
""";

String indented = text.indent(4);
// " Line 1\n Line 2\n"

String unindented = text.indent(-2);
// Removes 2 spaces từ mỗi dòng

Use Cases

Các trường hợp sử dụng Text Blocks

1. SQL Queries

// Before
String sql = "SELECT u.id, u.name, u.email, o.total\n" +
"FROM users u\n" +
"JOIN orders o ON u.id = o.user_id\n" +
"WHERE u.status = 'ACTIVE'\n" +
" AND o.created_at > ?\n" +
"ORDER BY o.created_at DESC\n" +
"LIMIT ?";

// After
String sql = """
SELECT u.id, u.name, u.email, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'ACTIVE'
AND o.created_at > ?
ORDER BY o.created_at DESC
LIMIT ?
""";

// Usage với PreparedStatement
try (var stmt = connection.prepareStatement(sql)) {
stmt.setTimestamp(1, yesterday);
stmt.setInt(2, 10);
var rs = stmt.executeQuery();
// ...
}

2. JSON

// API request body
String requestBody = """
{
"username": "%s",
"email": "%s",
"roles": ["USER", "ADMIN"],
"preferences": {
"language": "vi",
"timezone": "Asia/Ho_Chi_Minh"
}
}
""".formatted(username, email);

// Parse JSON
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(requestBody, User.class);

3. HTML Templates

public String generateEmailHtml(String name, String verificationLink) {
return """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Email Verification</title>
</head>
<body>
<h1>Welcome, %s!</h1>
<p>Please click the link below to verify your email:</p>
<a href="%s">Verify Email</a>
<p>If you didn't create this account, please ignore this email.</p>
</body>
</html>
""".formatted(name, verificationLink);
}

4. Regular Expressions

// Regex pattern - dễ đọc hơn
String emailPattern = """
^[A-Za-z0-9+_.-]+
@
[A-Za-z0-9.-]+
\\.[A-Za-z]{2,}$
""".replaceAll("\\s+", ""); // Remove whitespace

Pattern pattern = Pattern.compile(emailPattern);

5. Test Data

@Test
public void testJsonParsing() {
String json = """
{
"id": 1,
"name": "Test Product",
"price": 99.99,
"tags": ["electronics", "sale"]
}
""";

Product product = parser.parse(json);
assertEquals(1, product.getId());
assertEquals("Test Product", product.getName());
}

6. Configuration Files

// YAML configuration
String config = """
server:
port: 8080
host: localhost

database:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
password: secret

logging:
level: INFO
file: logs/app.log
""";

Text Blocks vs String Concatenation

Performance Comparison

// String concatenation
String concat = "Line 1\n" +
"Line 2\n" +
"Line 3\n";

// Text block
String textBlock = """
Line 1
Line 2
Line 3
""";
Performance

Text blocks và string concatenation compile thành cùng bytecode. Không có performance difference. Chọn text blocks vì readability, không phải performance.

So sánh quy trình tạo String Literal vs Text Block

💡 Khác biệt chính: String literal phải thêm \n+ thủ công, trong khi text block tự động xử lý line breaks và indentation. Kết quả cuối cùng giống nhau.

Code Comparison

AspectString ConcatenationText Blocks
Readability❌ Khó đọc✅ Dễ đọc
Maintainability❌ Khó sửa✅ Dễ sửa
Escape characters❌ Nhiều \n, \"✅ Ít escape
IDE support❌ Không syntax highlight✅ Có syntax highlight
Performance✅ Same✅ Same

Escape Sequences trong Text Blocks

So sánh các loại Escape Sequences

Common escapes

String text = """
Tab:\t<- tab character
Quote: " <- no escape needed!
Backslash: \\ <- needs escape
Newline: explicit\nnewline
""";

Escaping triple-quotes

// Include """ trong text block
String code = """
String text = \"""
Content here
\""";
""";
// Result: String text = """\n Content here\n """;

Line continuation với \

Để tránh newline ở cuối dòng:

// Với newline
String with = """
This is a very long line
that continues on next line
""";
// Result: "This is a very long line\nthat continues on next line\n"

// Không có newline (line continuation)
String without = """
This is a very long line \
that continues on next line
""";
// Result: "This is a very long line that continues on next line\n"

Preserve trailing whitespace với \s

// Trailing spaces bị xóa mặc định
String text1 = """
Line 1
Line 2
""";
// Spaces sau "Line 1" và "Line 2" bị xóa

// Keep trailing spaces với \s
String text2 = """
Line 1 \s
Line 2 \s
""";
// Spaces được giữ

Edge Cases

Text block rỗng

// Empty text block — chỉ chứa newline
String empty = """
""";
// Result: "" (empty string)

// Text block 1 dòng
String oneLine = """
Hello""";
// Result: "Hello" (không có trailing newline vì """ đóng cùng dòng)

String oneLineWithNewline = """
Hello
""";
// Result: "Hello\n" (có trailing newline)

Text block là compile-time constant

// Text block có thể dùng làm constant expression
final String GREETING = """
Hello""";

// Dùng trong switch (vì là constant)
switch (input) {
case "Hello" -> System.out.println("Match!");
// equivalent: case GREETING -> ...
}

// Text block CÓ THỂ dùng trong annotation
@SuppressWarnings("""
unchecked""")

Concatenation với text block

// ✅ OK — Concatenation trước/sau text block
String result = "Header: " + """
body content
""";

// ✅ OK — Nhưng KHÔNG nên dùng — khó đọc
String bad = """
part 1
""" + """
part 2
""";
// Result: "part 1\npart 2\n"
OCP Trap — Trailing whitespace bị strip tự động

Text block tự động strip trailing whitespace (spaces/tabs ở cuối mỗi dòng). Dùng \s (space escape, Java 15) để giữ lại:

String text = """
Name: Alice \s
Age: 30 \s
""";
// Không có \s → "Name: Alice" (spaces cuối bị xoá)
// Có \s → "Name: Alice " (spaces được giữ, \s thành space)
OCP Trap — Text block PHẢI bắt đầu với newline

""" mở BẮT BUỘC phải theo sau bởi line terminator. Không thể đặt content trên cùng dòng:

// ❌ Compile error!
String error = """Hello""";
String error2 = """Hello
World""";

// ✅ OK — newline sau """
String ok = """
Hello""";
📖 JEP 378

Text Blocks (JEP 378) xử lý nội dung qua 3 bước: (1) Line terminators normalized to LF, (2) Incidental white space removed, (3) Escape sequences interpreted. Thứ tự 3 bước này quan trọng — escape sequences được xử lý SAU khi strip whitespace, nên \s giữ được trailing space.

Tham khảo: JEP 378: Text Blocks

Best Practices

1. Dùng text blocks cho multi-line strings

// GOOD - SQL query
String sql = """
SELECT * FROM users
WHERE age > 18
""";

// BAD - Single line nhưng dùng text block
String name = """
Alice
""";
// Nên dùng: String name = "Alice";

2. Consistent indentation

// GOOD - Indentation nhất quán
public void method() {
String html = """
<html>
<body>
<h1>Title</h1>
</body>
</html>
""";
}

// BAD - Indentation lộn xộn
public void method() {
String html = """
<html>
<body>
<h1>Title</h1>
</body>
</html>
""";
}

3. Format với placeholders

// GOOD - Template rõ ràng
String template = """
Hello, %s!
Your order #%d has been shipped.
""";
String message = template.formatted(name, orderId);

// BAD - Concatenation trong text block
String message = """
Hello, """ + name + """!
Your order #""" + orderId + """ has been shipped.
""";

4. Closing """ alignment

// GOOD - Closing """ thụt vào cùng level
String text = """
Content
More content
""";

// ACCEPTABLE - Closing """ ở đầu dòng
String text = """
Content
More content
""";

// BAD - Inconsistent alignment
String text = """
Content
More content
""";

Ví dụ thực tế: Email Template

public class EmailService {
private static final String ORDER_CONFIRMATION_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; }
.header { background-color: #4CAF50; color: white; padding: 10px; }
.content { padding: 20px; }
.footer { background-color: #f1f1f1; padding: 10px; text-align: center; }
</style>
</head>
<body>
<div class="header">
<h1>Order Confirmation</h1>
</div>
<div class="content">
<p>Dear %s,</p>
<p>Thank you for your order!</p>
<h2>Order Details</h2>
<ul>
<li>Order ID: %s</li>
<li>Total: $%.2f</li>
<li>Delivery Address: %s</li>
</ul>
<p>Your order will be delivered within 3-5 business days.</p>
</div>
<div class="footer">
<p>&copy; 2024 Our Company. All rights reserved.</p>
</div>
</body>
</html>
""";

public String generateOrderConfirmationEmail(
String customerName,
String orderId,
double total,
String address
) {
return ORDER_CONFIRMATION_TEMPLATE.formatted(
customerName,
orderId,
total,
address
);
}
}

Ví dụ thực tế: GraphQL Query

public class GraphQLClient {
public String fetchUserWithOrders(long userId) {
String query = """
query {
user(id: %d) {
id
name
email
orders {
id
total
status
items {
product {
name
price
}
quantity
}
}
}
}
""".formatted(userId);

return executeQuery(query);
}
}

Ví dụ thực tế: Unit Test

@Test
public void testXmlParsing() {
String xml = """
<?xml version="1.0" encoding="UTF-8"?>
<order>
<id>12345</id>
<customer>
<name>John Doe</name>
<email>[email protected]</email>
</customer>
<items>
<item>
<productId>101</productId>
<quantity>2</quantity>
</item>
<item>
<productId>102</productId>
<quantity>1</quantity>
</item>
</items>
</order>
""";

Order order = xmlParser.parse(xml);

assertEquals(12345, order.getId());
assertEquals("John Doe", order.getCustomer().getName());
assertEquals(2, order.getItems().size());
}

Multiline Strings in Different Languages

LanguageSyntaxExample
Java 15+"""...""""""Line 1\nLine 2"""
Python"""...""" hoặc '''...'''"""Line 1\nLine 2"""
JavaScriptBackticks `...``Line 1\nLine 2`
Kotlin"""...""""""Line 1\nLine 2"""
Scala"""...""""""Line 1\nLine 2"""

Limitations

1. Không thể interpolation trực tiếp

Java text blocks KHÔNG hỗ trợ string interpolation như Kotlin/JavaScript:

// JavaScript/Kotlin - string interpolation
// var name = "Alice";
// var message = `Hello, ${name}!`; // JavaScript
// var message = "Hello, $name!" // Kotlin

// Java - KHÔNG có interpolation
var name = "Alice";
var message = """
Hello, ${name}!
"""; // ${name} KHÔNG được thay thế

// Phải dùng formatted()
var message = """
Hello, %s!
""".formatted(name);

2. Text block phải là compile-time constant

// OK - Literal
String text = """
Content
""";

// ERROR - Không thể runtime concatenation
String text = """
""" + variable + """
""";

Kết luận

Khi nào dùng Text Blocks

NÊN dùng khi:

  • Multi-line strings (SQL, JSON, HTML, XML)
  • Embedded languages (GraphQL, YAML)
  • Templates
  • Test data
  • Documentation strings

KHÔNG nên dùng khi:

  • Single-line strings
  • String cần interpolation phức tạp
  • Dynamic content (dùng templates thay vì)

Lợi ích

  • Readability: Dễ đọc hơn nhiều
  • Maintainability: Dễ sửa, format
  • Less errors: Ít escape characters, ít typo
  • IDE support: Syntax highlighting cho embedded languages

Bài tập thực hành

Bài 1: SQL Query

Viết method List<User> findActiveUsers(int minAge) với SQL query sử dụng text block.

Bài 2: JSON Template

Tạo method String generateProductJson(String name, double price, List<String> tags) tạo JSON string sử dụng text block và formatted().

Bài 3: HTML Email

Viết method String createWelcomeEmail(String name, String verificationLink) tạo HTML email với:

  • Header có logo
  • Welcome message với tên user
  • Verification button
  • Footer

Tài liệu tham khảo

Đọc thêm