Text Blocks
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
\nvà+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
""";
""" 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
""";
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
\nvà+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
| Aspect | String Concatenation | Text 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"
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)
""" 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""";
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>© 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
| Language | Syntax | Example |
|---|---|---|
| Java 15+ | """...""" | """Line 1\nLine 2""" |
| Python | """...""" hoặc '''...''' | """Line 1\nLine 2""" |
| JavaScript | Backticks `...` | `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