Giữ chỗ được đặt tên theo định dạng chuỗi


174

Trong Python, khi định dạng chuỗi, tôi có thể điền vào chỗ dành sẵn theo tên chứ không phải theo vị trí, như thế:

print "There's an incorrect value '%(value)s' in column # %(column)d" % \
  { 'value': x, 'column': y }

Tôi tự hỏi nếu điều đó là có thể trong Java (hy vọng, không có thư viện bên ngoài)?


Bạn có thể mở rộng MessageFormat và thực hiện chức năng ánh xạ từ các biến đến các chỉ số trong đó.
vpram86


1
Một số lịch sử: Java chủ yếu sao chép C / C ++ về vấn đề này khi nó cố gắng thu hút các nhà phát triển từ thế giới C ++, nơi %sthông thường. vi.wikipedia.org/wiki/Printf_format_opes#History Cũng lưu ý rằng một số IDE và FindBugs có thể tự động phát hiện số không khớp% s và% d, nhưng tôi vẫn thích các trường được đặt tên.
Barshe Roussy

Câu trả lời:


143

StrSubstitutor của jakarta commons lang là một cách nhẹ nhàng để làm điều này với điều kiện giá trị của bạn đã được định dạng chính xác.

http://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/text/StrSubstitutor.html

Map<String, String> values = new HashMap<String, String>();
values.put("value", x);
values.put("column", y);
StrSubstitutor sub = new StrSubstitutor(values, "%(", ")");
String result = sub.replace("There's an incorrect value '%(value)' in column # %(column)");

Các kết quả trên trong:

"Có giá trị không chính xác '1' trong cột số 2"

Khi sử dụng Maven, bạn có thể thêm phụ thuộc này vào tệp pom.xml của mình:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

2
Tôi thấy thật đáng thất vọng khi thư viện không ném nếu không tìm thấy khóa, tuy nhiên, nếu bạn sử dụng cú pháp mặc định ( ${arg}) thay vì tùy chỉnh ở trên ( %(arg)) thì regex sẽ không biên dịch, đó là hiệu ứng mong muốn.
John Lehmann

2
Bạn có thể đặt Var biếnResolver tùy chỉnh và ném Ngoại lệ nếu khóa không có trong bản đồ.
Mene

7
Chủ đề cũ, nhưng kể từ 3.6, gói văn bản không được dùng cho văn bản chung. commons.apache.org/proper/commons-text
Jeff Walker

73

không hoàn toàn, nhưng bạn có thể sử dụng MessageFormat để tham chiếu một giá trị nhiều lần:

MessageFormat.format("There's an incorrect value \"{0}\" in column # {1}", x, y);

Ở trên cũng có thể được thực hiện với String.format (), nhưng tôi tìm thấy trình dọn dẹp cú pháp messageFormat nếu bạn cần xây dựng các biểu thức phức tạp, cộng với bạn không cần quan tâm đến loại đối tượng bạn đang đặt vào chuỗi


không chắc chắn tại sao bạn không thể, vị trí trong chuỗi không quan trọng, chỉ có vị trí trong danh sách các đối số, điều này làm cho nó trở thành một vấn đề đổi tên. Bạn biết tên của các khóa, có nghĩa là bạn có thể quyết định vị trí cho một khóa trong danh sách các đối số. từ bây giờ, giá trị sẽ được gọi là 0 và cột là 1: MessageeFormat.format ("Có giá trị không chính xác \" {0} \ "trong cột # {1}, sử dụng {0} làm giá trị có thể gây ra nhiều vấn đề", valueMap .get ('value'), valueMap.get ('cột'));
giladbu

1
Cảm ơn về một manh mối, nó đã giúp tôi viết hàm đơn giản thực hiện chính xác những gì tôi muốn (tôi đã đặt nó bên dưới).
Andy

1
Đồng ý, cú pháp sạch sẽ hơn nhiều. MessageFormat quá tệ đã có ý nghĩ của riêng mình khi định dạng các giá trị số.
Kees de Kooter

Và nó dường như bỏ qua giữ chỗ được bao quanh bởi dấu ngoặc đơn.
Kees de Kooter

MessageFormatlà tuyệt vời nhưng cồng kềnh cho nội dung json tương đối lớn
EliuX

32

Một ví dụ khác về Apache Common StringSubstitutor cho trình giữ chỗ có tên đơn giản.

String template = "Welcome to {theWorld}. My name is {myName}.";

Map<String, String> values = new HashMap<>();
values.put("theWorld", "Stackoverflow");
values.put("myName", "Thanos");

String message = StringSubstitutor.replace(template, values, "{", "}");

System.out.println(message);

// Welcome to Stackoverflow. My name is Thanos.

Nếu bạn muốn tải các tệp rất lớn, tôi thấy thư viện này cũng hỗ trợ replaceIncác giá trị thay thế vào bộ đệm: StringBuilder hoặc TextStringBuilder. Với phương pháp này, toàn bộ nội dung của tệp sẽ không được tải vào bộ nhớ.
Edward Corrigall

15

Bạn có thể sử dụng thư viện StringTemplate , nó cung cấp những gì bạn muốn và nhiều hơn nữa.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());

Gặp sự cố với 'char:unexpected char: '''
AlikElzin-kilaka

11

Đối với các trường hợp rất đơn giản, bạn chỉ cần sử dụng thay thế Chuỗi mã hóa cứng, không cần thư viện ở đó:

    String url = "There's an incorrect value '%(value)' in column # %(column)";
    url = url.replace("%(value)", x); // 1
    url = url.replace("%(column)", y); // 2

CẢNH BÁO : Tôi chỉ muốn hiển thị mã đơn giản nhất có thể. Tất nhiên KHÔNG sử dụng mã này cho mã sản xuất nghiêm trọng khi vấn đề bảo mật, như đã nêu trong các nhận xét: thoát, xử lý lỗi và bảo mật là một vấn đề ở đây. Nhưng trong trường hợp xấu nhất bây giờ bạn biết tại sao cần sử dụng lib 'tốt' :-)


1
Điều này là đơn giản và dễ dàng, nhưng nhược điểm là nó thất bại âm thầm khi không tìm thấy giá trị. Nó chỉ để lại chỗ giữ trong chuỗi ban đầu.
kiedysktos

@kiedysktos, bạn có thể cải thiện nó bằng cách kiểm tra, nhưng nếu bạn muốn có đầy đủ, hãy sử dụng lib :)
Barshe Roussy

2
Cảnh báo: Vì kỹ thuật này coi kết quả thay thế trung gian là chuỗi định dạng của riêng mình, giải pháp này dễ bị tấn công định dạng chuỗi . Bất kỳ giải pháp chính xác nào cũng phải thực hiện một lần chuyển qua chuỗi định dạng.
200_success

@ 200_success Có điểm hay khi nói về bảo mật, tất nhiên mã này không dành cho việc sử dụng sản xuất nghiêm trọng ...
Barshe Roussy

8

Cảm ơn tất cả sự giúp đỡ của bạn! Sử dụng tất cả các manh mối của bạn, tôi đã viết thói quen để thực hiện chính xác những gì tôi muốn - định dạng chuỗi giống như python bằng từ điển. Vì tôi là người mới sử dụng Java, nên mọi gợi ý đều được đánh giá cao.

public static String dictFormat(String format, Hashtable<String, Object> values) {
    StringBuilder convFormat = new StringBuilder(format);
    Enumeration<String> keys = values.keys();
    ArrayList valueList = new ArrayList();
    int currentPos = 1;
    while (keys.hasMoreElements()) {
        String key = keys.nextElement(),
        formatKey = "%(" + key + ")",
        formatPos = "%" + Integer.toString(currentPos) + "$";
        int index = -1;
        while ((index = convFormat.indexOf(formatKey, index)) != -1) {
            convFormat.replace(index, index + formatKey.length(), formatPos);
            index += formatPos.length();
        }
        valueList.add(values.get(key));
        ++currentPos;
    }
    return String.format(convFormat.toString(), valueList.toArray());
}

Không giống như trong câu trả lời của Lombo, điều này không thể bị mắc kẹt trong một vòng lặp vô hạn, vì formatPoskhông thể chứa formatKey.
Aaron Dufour

6
Cảnh báo: Bởi vì vòng lặp coi kết quả thay thế trung gian là chuỗi định dạng của riêng chúng, giải pháp này dễ bị tấn công định dạng chuỗi . Bất kỳ giải pháp chính xác nào cũng phải thực hiện một lần chuyển qua chuỗi định dạng.
200_success

6

Đây là một luồng cũ, nhưng chỉ để ghi lại, bạn cũng có thể sử dụng kiểu Java 8, như thế này:

public static String replaceParams(Map<String, String> hashMap, String template) {
    return hashMap.entrySet().stream().reduce(template, (s, e) -> s.replace("%(" + e.getKey() + ")", e.getValue()),
            (s, s2) -> s);
}

Sử dụng:

public static void main(String[] args) {
    final HashMap<String, String> hashMap = new HashMap<String, String>() {
        {
            put("foo", "foo1");
            put("bar", "bar1");
            put("car", "BMW");
            put("truck", "MAN");
        }
    };
    String res = replaceParams(hashMap, "This is '%(foo)' and '%(foo)', but also '%(bar)' '%(bar)' indeed.");
    System.out.println(res);
    System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(foo)', but also '%(bar)' '%(bar)' indeed."));
    System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(truck)', but also '%(foo)' '%(bar)' + '%(truck)' indeed."));
}

Đầu ra sẽ là:

This is 'foo1' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'MAN', but also 'foo1' 'bar1' + 'MAN' indeed.

Điều này thật tuyệt vời, nhưng thật đáng buồn, nó vi phạm các thông số kỹ thuật ở đây docs.oracle.com/javase/8/docs/api/java/util/stream/ trộm Hàm combiner phải trả về tham số thứ hai nếu tham số thứ nhất là định danh. Một ở trên sẽ trả lại danh tính thay thế. Nó cũng vi phạm quy tắc này: combiner.apply (u, accumulator.apply (danh tính, t)) == accumulator.apply (u, t)
Ali Cheaito

Thú vị ... nhưng chỉ khi bạn đề xuất một cách đẹp hơn để vượt qua bản đồ, cũng có thể nếu có thể sau mẫu giống như hầu hết mã định dạng.
Barshe Roussy

4
Cảnh báo: Bởi vì .reduce()xử lý kết quả thay thế trung gian dưới dạng chuỗi định dạng của riêng họ, giải pháp này dễ bị tấn công định dạng chuỗi . Bất kỳ giải pháp chính xác nào cũng phải thực hiện một lần chuyển qua chuỗi định dạng.
200_success

6
public static String format(String format, Map<String, Object> values) {
    StringBuilder formatter = new StringBuilder(format);
    List<Object> valueList = new ArrayList<Object>();

    Matcher matcher = Pattern.compile("\\$\\{(\\w+)}").matcher(format);

    while (matcher.find()) {
        String key = matcher.group(1);

        String formatKey = String.format("${%s}", key);
        int index = formatter.indexOf(formatKey);

        if (index != -1) {
            formatter.replace(index, index + formatKey.length(), "%s");
            valueList.add(values.get(key));
        }
    }

    return String.format(formatter.toString(), valueList.toArray());
}

Thí dụ:

String format = "My name is ${1}. ${0} ${1}.";

Map<String, Object> values = new HashMap<String, Object>();
values.put("0", "James");
values.put("1", "Bond");

System.out.println(format(format, values)); // My name is Bond. James Bond.

2
Đây phải là câu trả lời, vì nó tránh được các cuộc tấn công chuỗi định dạng mà hầu hết các giải pháp khác ở đây đều dễ bị tấn công. Lưu ý rằng Java 9 làm cho nó đơn giản hơn nhiều, với sự hỗ trợ cho .replaceAll()các cuộc gọi lại thay thế chuỗi .
200_success

Đây phải là câu trả lời, vì nó không sử dụng bất kỳ thư viện bên ngoài nào.
Bohao LI

3

Tôi là tác giả của một thư viện nhỏ thực hiện chính xác những gì bạn muốn:

Student student = new Student("Andrei", 30, "Male");

String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}")
                    .arg("id", 10)
                    .arg("st", student)
                    .format();
System.out.println(studStr);

Hoặc bạn có thể xâu chuỗi các đối số:

String result = template("#{x} + #{y} = #{z}")
                    .args("x", 5, "y", 10, "z", 15)
                    .format();
System.out.println(result);

// Output: "5 + 10 = 15"

Có thể thực hiện định dạng dựa trên điều kiện với thư viện của bạn?
gaurav

@gaurav không hẳn. Nếu bạn cần rằng bạn cần một thư viện templating đầy đủ tính năng.
Andrei Ciobanu

2

Phương pháp thay thế của Apache Commons Lang có thể có ích phụ thuộc vào nhu cầu cụ thể của bạn. Bạn có thể dễ dàng sử dụng nó để thay thế giữ chỗ theo tên bằng cuộc gọi phương thức duy nhất này:

StringUtils.replaceEach("There's an incorrect value '%(value)' in column # %(column)",
            new String[] { "%(value)", "%(column)" }, new String[] { x, y });

Đưa ra một số văn bản đầu vào, điều này sẽ thay thế tất cả các lần xuất hiện của trình giữ chỗ trong mảng chuỗi đầu tiên bằng các giá trị tương ứng trong chuỗi thứ hai.


1

Bạn có thể có một cái gì đó như thế này trên một lớp trình trợ giúp chuỗi

/**
 * An interpreter for strings with named placeholders.
 *
 * For example given the string "hello %(myName)" and the map <code>
 *      <p>Map<String, Object> map = new HashMap<String, Object>();</p>
 *      <p>map.put("myName", "world");</p>
 * </code>
 *
 * the call {@code format("hello %(myName)", map)} returns "hello world"
 *
 * It replaces every occurrence of a named placeholder with its given value
 * in the map. If there is a named place holder which is not found in the
 * map then the string will retain that placeholder. Likewise, if there is
 * an entry in the map that does not have its respective placeholder, it is
 * ignored.
 *
 * @param str
 *            string to format
 * @param values
 *            to replace
 * @return formatted string
 */
public static String format(String str, Map<String, Object> values) {

    StringBuilder builder = new StringBuilder(str);

    for (Entry<String, Object> entry : values.entrySet()) {

        int start;
        String pattern = "%(" + entry.getKey() + ")";
        String value = entry.getValue().toString();

        // Replace every occurence of %(key) with value
        while ((start = builder.indexOf(pattern)) != -1) {
            builder.replace(start, start + pattern.length(), value);
        }
    }

    return builder.toString();
}

Cảm ơn rất nhiều, nó thực hiện gần như những gì tôi muốn, nhưng điều duy nhất là nó không sửa đổi tài khoản (xem xét "% (khóa) 08d")
Andy

1
Cũng lưu ý rằng điều này đi vào một vòng lặp vô hạn nếu bất kỳ giá trị nào đang được sử dụng có chứa mục tương ứng.
Aaron Dufour

1
Cảnh báo: Bởi vì vòng lặp coi kết quả thay thế trung gian là chuỗi định dạng của riêng chúng, giải pháp này dễ bị tấn công định dạng chuỗi . Bất kỳ giải pháp chính xác nào cũng phải thực hiện một lần chuyển qua chuỗi định dạng.
200_success

1

Câu trả lời của tôi là:

a) sử dụng StringBuilder khi có thể

b) keep (dưới bất kỳ hình thức nào: số nguyên là vị trí tốt nhất, cụ thể như char macro, v.v.) của vị trí "giữ chỗ" và sau đó sử dụng StringBuilder.insert() (một vài phiên bản của đối số).

Sử dụng các thư viện bên ngoài có vẻ quá mức cần thiết và tôi tin rằng hiệu suất giảm đáng kể, khi StringBuilder được chuyển đổi thành String bên trong.


1

Dựa trên câu trả lời tôi đã tạo MapBuilderlớp:

public class MapBuilder {

    public static Map<String, Object> build(Object... data) {
        Map<String, Object> result = new LinkedHashMap<>();

        if (data.length % 2 != 0) {
            throw new IllegalArgumentException("Odd number of arguments");
        }

        String key = null;
        Integer step = -1;

        for (Object value : data) {
            step++;
            switch (step % 2) {
                case 0:
                    if (value == null) {
                        throw new IllegalArgumentException("Null key value");
                    }
                    key = (String) value;
                    continue;
                case 1:
                    result.put(key, value);
                    break;
            }
        }

        return result;
    }

}

sau đó tôi tạo lớp StringFormatđể định dạng chuỗi:

public final class StringFormat {

    public static String format(String format, Object... args) {
        Map<String, Object> values = MapBuilder.build(args);

        for (Map.Entry<String, Object> entry : values.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            format = format.replace("$" + key, value.toString());
        }

        return format;
    }

}

mà bạn có thể sử dụng như thế:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);

1
Cảnh báo: Bởi vì vòng lặp coi kết quả thay thế trung gian là chuỗi định dạng của riêng chúng, giải pháp này dễ bị tấn công định dạng chuỗi . Bất kỳ giải pháp chính xác nào cũng phải thực hiện một lần chuyển qua chuỗi định dạng.
200_success

1

Tôi cũng đã tạo một lớp sử dụng / trình trợ giúp (sử dụng jdk 8) có thể định dạng một chuỗi thay thế các lần xuất hiện của các biến.

Với mục đích này, tôi đã sử dụng phương thức "appendReplocation" của Matchers, tất cả chỉ thay thế và lặp trên các phần bị ảnh hưởng của chuỗi định dạng.

Lớp người trợ giúp hiện không được tài liệu javadoc tốt. Tôi sẽ thay đổi điều này trong tương lai;) Dù sao tôi đã nhận xét những dòng quan trọng nhất (tôi hy vọng).

    public class FormatHelper {

    //Prefix and suffix for the enclosing variable name in the format string.
    //Replace the default values with any you need.
    public static final String DEFAULT_PREFIX = "${";
    public static final String DEFAULT_SUFFIX = "}";

    //Define dynamic function what happens if a key is not found.
    //Replace the defualt exception with any "unchecked" exception type you need or any other behavior.
    public static final BiFunction<String, String, String> DEFAULT_NO_KEY_FUNCTION =
            (fullMatch, variableName) -> {
                throw new RuntimeException(String.format("Key: %s for variable %s not found.",
                                                         variableName,
                                                         fullMatch));
            };
    private final Pattern variablePattern;
    private final Map<String, String> values;
    private final BiFunction<String, String, String> noKeyFunction;
    private final String prefix;
    private final String suffix;

    public FormatHelper(Map<String, String> values) {
        this(DEFAULT_NO_KEY_FUNCTION, values);
    }

    public FormatHelper(
            BiFunction<String, String, String> noKeyFunction, Map<String, String> values) {
        this(DEFAULT_PREFIX, DEFAULT_SUFFIX, noKeyFunction, values);
    }

    public FormatHelper(String prefix, String suffix, Map<String, String> values) {
        this(prefix, suffix, DEFAULT_NO_KEY_FUNCTION, values);
    }

    public FormatHelper(
            String prefix,
            String suffix,
            BiFunction<String, String, String> noKeyFunction,
            Map<String, String> values) {
        this.prefix = prefix;
        this.suffix = suffix;
        this.values = values;
        this.noKeyFunction = noKeyFunction;

        //Create the Pattern and quote the prefix and suffix so that the regex don't interpret special chars.
        //The variable name is a "\w+" in an extra capture group.
        variablePattern = Pattern.compile(Pattern.quote(prefix) + "(\\w+)" + Pattern.quote(suffix));
    }

    public static String format(CharSequence format, Map<String, String> values) {
        return new FormatHelper(values).format(format);
    }

    public static String format(
            CharSequence format,
            BiFunction<String, String, String> noKeyFunction,
            Map<String, String> values) {
        return new FormatHelper(noKeyFunction, values).format(format);
    }

    public static String format(
            String prefix, String suffix, CharSequence format, Map<String, String> values) {
        return new FormatHelper(prefix, suffix, values).format(format);
    }

    public static String format(
            String prefix,
            String suffix,
            BiFunction<String, String, String> noKeyFunction,
            CharSequence format,
            Map<String, String> values) {
        return new FormatHelper(prefix, suffix, noKeyFunction, values).format(format);
    }

    public String format(CharSequence format) {

        //Create matcher based on the init pattern for variable names.
        Matcher matcher = variablePattern.matcher(format);

        //This buffer will hold all parts of the formatted finished string.
        StringBuffer formatBuffer = new StringBuffer();

        //loop while the matcher finds another variable (prefix -> name <- suffix) match
        while (matcher.find()) {

            //The root capture group with the full match e.g ${variableName}
            String fullMatch = matcher.group();

            //The capture group for the variable name resulting from "(\w+)" e.g. variableName
            String variableName = matcher.group(1);

            //Get the value in our Map so the Key is the used variable name in our "format" string. The associated value will replace the variable.
            //If key is missing (absent) call the noKeyFunction with parameters "fullMatch" and "variableName" else return the value.
            String value = values.computeIfAbsent(variableName, key -> noKeyFunction.apply(fullMatch, key));

            //Escape the Map value because the "appendReplacement" method interprets the $ and \ as special chars.
            String escapedValue = Matcher.quoteReplacement(value);

            //The "appendReplacement" method replaces the current "full" match (e.g. ${variableName}) with the value from the "values" Map.
            //The replaced part of the "format" string is appended to the StringBuffer "formatBuffer".
            matcher.appendReplacement(formatBuffer, escapedValue);
        }

        //The "appendTail" method appends the last part of the "format" String which has no regex match.
        //That means if e.g. our "format" string has no matches the whole untouched "format" string is appended to the StringBuffer "formatBuffer".
        //Further more the method return the buffer.
        return matcher.appendTail(formatBuffer)
                      .toString();
    }

    public String getPrefix() {
        return prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public Map<String, String> getValues() {
        return values;
    }
}

Bạn có thể tạo một thể hiện lớp cho một Bản đồ cụ thể với các giá trị (hoặc tiền tố hậu tố hoặc noKeyFunction) như:

    Map<String, String> values = new HashMap<>();
    values.put("firstName", "Peter");
    values.put("lastName", "Parker");


    FormatHelper formatHelper = new FormatHelper(values);
    formatHelper.format("${firstName} ${lastName} is Spiderman!");
    // Result: "Peter Parker is Spiderman!"
    // Next format:
    formatHelper.format("Does ${firstName} ${lastName} works as photographer?");
    //Result: "Does Peter Parker works as photographer?"

Hơn nữa, bạn có thể xác định điều gì sẽ xảy ra nếu một khóa trong các giá trị Bản đồ bị thiếu (hoạt động theo cả hai cách, ví dụ như tên biến sai trong chuỗi định dạng hoặc khóa bị thiếu trong Bản đồ). Hành vi mặc định là một ngoại lệ không được kiểm tra ném (không được kiểm tra vì tôi sử dụng Hàm jdk8 mặc định không thể xử lý các ngoại lệ được kiểm tra) như:

    Map<String, String> map = new HashMap<>();
    map.put("firstName", "Peter");
    map.put("lastName", "Parker");


    FormatHelper formatHelper = new FormatHelper(map);
    formatHelper.format("${missingName} ${lastName} is Spiderman!");
    //Result: RuntimeException: Key: missingName for variable ${missingName} not found.

Bạn có thể xác định một hành vi tùy chỉnh trong lệnh gọi hàm tạo như sau:

Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");


FormatHelper formatHelper = new FormatHelper(fullMatch, variableName) -> variableName.equals("missingName") ? "John": "SOMETHING_WRONG", values);
formatHelper.format("${missingName} ${lastName} is Spiderman!");
// Result: "John Parker is Spiderman!"

hoặc ủy quyền cho mặc định không có hành vi chính:

...
    FormatHelper formatHelper = new FormatHelper((fullMatch, variableName) ->   variableName.equals("missingName") ? "John" :
            FormatHelper.DEFAULT_NO_KEY_FUNCTION.apply(fullMatch,
                                                       variableName), map);
...

Để xử lý tốt hơn, cũng có các biểu diễn phương thức tĩnh như:

Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");

FormatHelper.format("${firstName} ${lastName} is Spiderman!", map);
// Result: "Peter Parker is Spiderman!"

1

Không có gì được tích hợp vào Java tại thời điểm viết bài này. Tôi sẽ đề nghị viết thực hiện của riêng bạn. Sở thích của tôi là dành cho một giao diện xây dựng trôi chảy đơn giản thay vì tạo bản đồ và chuyển nó sang chức năng - ví dụ, bạn kết thúc với một đoạn mã liền kề đẹp, ví dụ:

String result = new TemplatedStringBuilder("My name is {{name}} and I from {{town}}")
   .replace("name", "John Doe")
   .replace("town", "Sydney")
   .finish();

Đây là một cách thực hiện đơn giản:

class TemplatedStringBuilder {

    private final static String TEMPLATE_START_TOKEN = "{{";
    private final static String TEMPLATE_CLOSE_TOKEN = "}}";

    private final String template;
    private final Map<String, String> parameters = new HashMap<>();

    public TemplatedStringBuilder(String template) {
        if (template == null) throw new NullPointerException();
        this.template = template;
    }

    public TemplatedStringBuilder replace(String key, String value){
        parameters.put(key, value);
        return this;
    }

    public String finish(){

        StringBuilder result = new StringBuilder();

        int startIndex = 0;

        while (startIndex < template.length()){

            int openIndex  = template.indexOf(TEMPLATE_START_TOKEN, startIndex);

            if (openIndex < 0){
                result.append(template.substring(startIndex));
                break;
            }

            int closeIndex = template.indexOf(TEMPLATE_CLOSE_TOKEN, openIndex);

            if(closeIndex < 0){
                result.append(template.substring(startIndex));
                break;
            }

            String key = template.substring(openIndex + TEMPLATE_START_TOKEN.length(), closeIndex);

            if (!parameters.containsKey(key)) throw new RuntimeException("missing value for key: " + key);

            result.append(template.substring(startIndex, openIndex));
            result.append(parameters.get(key));

            startIndex = closeIndex + TEMPLATE_CLOSE_TOKEN.length();
        }

        return result.toString();
    }
}

0

Hãy thử Freemarker , thư viện templating.

văn bản thay thế


4
Người tự do? Tôi đoán anh ấy sẵn sàng để biết, làm thế nào để làm điều này trong java đơn giản. Dù sao, nếu Freemarker là câu trả lời có thể xảy ra thì tôi có thể nói rằng JSP cũng sẽ là câu trả lời đúng không?
Rakesh Juyal

1
Cảm ơn, nhưng đối với nhiệm vụ của tôi, điều này dường như là quá mức cần thiết. Nhưng cảm ơn.
Andy

1
@Rakesh JSP là một thứ rất "xem / FE". Tôi đã sử dụng FreeMarker trong quá khứ để tạo XML và đôi khi thậm chí tạo các tệp JAVA. Andy sợ bạn sẽ phải tự viết một tiện ích (hoặc giống như tiện ích được quy định ở trên)
Kannan Ekanath

@Boris cái nào là freemarker tốt hơn so với vận tốc so với chuỗi ký tự?
gaurav



0

Bạn nên xem qua thư viện ICU4J chính thức . Nó cung cấp một MessageFormat lớp tương tự như lớp có sẵn với JDK nhưng điều này trước đây hỗ trợ giữ chỗ có tên.

Không giống như các giải pháp khác được cung cấp trên trang này. ICU4j là một phần của dự án ICU được IBM duy trì và cập nhật thường xuyên. Ngoài ra, nó hỗ trợ các trường hợp sử dụng nâng cao như số nhiều và nhiều hơn nữa.

Đây là một ví dụ mã:

MessageFormat messageFormat =
        new MessageFormat("Publication written by {author}.");

Map<String, String> args = Map.of("author", "John Doe");

System.out.println(messageFormat.format(args));

0

Có Plugin Java để sử dụng phép nội suy chuỗi trong Java (như trong Kotlin, JavaScript). Hỗ trợ Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Sử dụng các biến trong chuỗi ký tự:

int a = 3;
int b = 4;
System.out.println("${a} + ${b} = ${a+b}");

Sử dụng biểu thức:

int a = 3;
int b = 4;
System.out.println("pow = ${a * a}");
System.out.println("flag = ${a > b ? true : false}");

Sử dụng các chức năng:

@Test
void functionCall() {
    System.out.println("fact(5) = ${factorial(5)}");
}

long factorial(int n) {
    long fact = 1;
    for (int i = 2; i <= n; i++) {
        fact = fact * i;
    }
    return fact;
}

Để biết thêm thông tin, xin vui lòng đọc dự án README.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.