문자열 형식의 명명 된 자리 표시 자


175

파이썬에서 문자열을 포맷 할 때 자리 표시자를 위치가 아닌 이름으로 채울 수 있습니다.

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

Java에서 가능할지 궁금합니다 (외부 라이브러리없이).


MessageFormat을 확장하고 변수에서 색인으로의 맵핑 기능을 구현할 수 있습니다.
vpram86


1
일부 역사 : Java %s는 일반적으로 C ++ 세계에서 개발자를 유혹하려고 시도 하면서이 문제에 대해 C / C ++를 주로 복사 했습니다. en.wikipedia.org/wiki/Printf_format_string#History 또한 일부 IDE 및 FindBugs는 일치하지 않는 % s 및 % d 카운트를 자동으로 감지 할 수 있지만 여전히 명명 된 필드를 선호합니다.
Christophe Roussy

답변:


143

자카르타 커먼즈 랭의 StrSubstitutor는 값이 이미 올바르게 형식화 된 경우이를 수행하는 간단한 방법입니다.

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)");

위의 결과는 다음과 같습니다.

"2 번 열에 잘못된 값 '1'이 있습니다."

Maven을 사용할 때이 종속성을 pom.xml에 추가 할 수 있습니다.

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

2
키를 찾을 수없는 경우 라이브러리가 throw되지 않는 것이 실망 스러웠지만 ${arg}위의 사용자 정의 구문 ( ) 대신 기본 구문 ( )을 사용 %(arg)하면 정규식이 컴파일되지 않으므로 원하는 효과입니다.
John Lehmann 2016 년

2
키에 맵이 없으면 사용자 정의 VariableResolver를 설정하고 예외를 발생시킬 수 있습니다.
Mene

7
오래된 스레드이지만 3.6에서는 텍스트 패키지가 더 이상 사용되지 않아 일반 텍스트를 선호합니다. commons.apache.org/proper/commons-text
Jeff Walker

74

확실 하지 않지만 MessageFormat 을 사용 하여 하나의 값을 여러 번 참조 할 수 있습니다 .

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

위의 내용은 String.format ()으로도 수행 할 수 있지만 복잡한 표현식을 작성 해야하는 경우 messageFormat 구문 클리너를 찾고 문자열에 넣는 객체의 유형에 신경 쓰지 않아도됩니다.


왜 당신이 할 수 없는지, 문자열의 위치는 중요하지 않고 인수 목록의 위치 만 중요하므로 이름을 바꾸는 문제가됩니다. 키 이름을 알고 있으므로 인수 목록에서 키 위치를 결정할 수 있습니다. 이제부터 값은 0으로, 열은 1로 알려져 있습니다. MessageeFormat.format ( "{0}을 (를) 값으로 사용하면 많은 문제가 발생할 수 있습니다"{1} 열에 "\ {{0} \"값이 잘못되었습니다 ", valueMap .get ( 'value'), valueMap.get ( 'column'));
giladbu

1
단서 덕분에, 내가 원하는 것을 정확하게 수행하는 간단한 함수를 작성하는 데 도움이되었습니다 (아래에 넣었습니다).
Andy

1
동의, 구문이 훨씬 깨끗합니다. 너무 나쁜 MessageFormat은 숫자 값의 형식을 지정할 때 자체적으로 생각합니다.
Kees de Kooter

그리고 작은 따옴표로 둘러싸인 자리 표시자를 무시하는 것 같습니다.
Kees de Kooter

MessageFormat비교적 큰 JSON 콘텐츠에 대한 큰하지만 복잡
EliuX

32

단순한 이름 지정된 자리 표시자를위한 Apache Common StringSubstitutor 의 또 다른 예입니다 .

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.

매우 큰 파일을로드 할 것으로 예상되는 경우이 라이브러리는 replaceInStringBuilder 또는 TextStringBuilder와 같은 값을 버퍼로 대체 하는 것을 지원합니다 . 이 방법을 사용하면 파일의 전체 내용이 메모리에로드되지 않습니다.
Edward Corrigall

15

StringTemplate 라이브러리 를 사용할 수 있으며 원하는 것을 제공합니다.

import org.antlr.stringtemplate.*;

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

'문자에 문제가 있었다 :unexpected char: '''
AlikElzin-kilaka

11

내용은 매우 간단한 경우 당신은 단순히 하드 코드 된 문자열 교체가 도서관에 대한 필요를 사용할 수 없습니다 :

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

경고 : 방금 가장 간단한 코드를 보여주고 싶었습니다. 물론 주석에 명시된 바와 같이 보안이 중요한 심각한 생산 코드에는 사용하지 마십시오. 이스케이프, 오류 처리 및 보안이 여기에 있습니다. 그러나 최악의 경우 이제 '좋은'lib를 사용해야하는 이유를 알 수 있습니다 :-)


1
이것은 간단하고 쉽지만 단점은 값을 찾지 못하면 자동으로 실패한다는 것입니다. 자리 표시자를 원래 문자열에 그대로 둡니다.
kiedysktos

@kiedysktos, 당신은 확인하여 그것을 향상시킬 수 있지만, 당신이 모든 것을 원한다면 lib를 사용하십시오 :)
Christophe Roussy

2
경고 : 이 기술은 중간 대체 결과를 자체 형식 문자열로 취급하므로이 솔루션은 형식 문자열 공격에 취약합니다 . 올바른 솔루션은 형식 문자열을 한 번만 통과해야합니다.
200_success

@ 200_success 네 좋은 지적 물론이 코드는 심각한 생산 사용을 위해하지의 보안에 대해 이야기 ...
크리스토프 후씨

8

모든 도움을 주셔서 감사합니다! 모든 단서를 사용하여 원하는 것을 정확하게 수행하는 루틴을 작성했습니다. 사전을 사용하여 파이썬과 같은 문자열 형식. 내가 Java 초보자이기 때문에 모든 힌트를 주시면 감사하겠습니다.

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());
}

Lombo의 답변과 달리 이것은 포함 할 수 없으므로 무한 루프에 빠질 formatPos수 없습니다 formatKey.
Aaron Dufour

6
경고 : 루프는 중간 대체 결과를 자체 형식 문자열로 취급하므로이 솔루션은 형식 문자열 공격에 취약합니다 . 올바른 솔루션은 형식 문자열을 한 번만 통과해야합니다.
200_success

6

이것은 오래된 스레드이지만 레코드 용으로 다음과 같이 Java 8 스타일을 사용할 수도 있습니다.

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

용법:

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."));
}

출력은 다음과 같습니다.

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.

이것은 훌륭하지만 슬프게도 여기의 사양을 위반합니다 docs.oracle.com/javase/8/docs/api/java/util/stream/… 첫 번째 매개 변수가 ID이면 결합기 함수는 두 번째 매개 변수를 리턴해야합니다. 위의 것은 대신 신원을 반환합니다. 또한이 규칙을 위반합니다. combiner.apply (u, accumulator.apply (identity, t)) == accumulator.apply (u, t)
Ali Cheaito

흥미로운 ...하지만지도를 전달하는 더 좋은 방법을 제안하는 경우에만 가능하며 대부분의 서식 코드와 같은 템플릿 후에도 가능합니다.
Christophe Roussy

4
경고 : 때문에 .reduce()자신의 포맷 문자열로 취급 중간 대체 결과, 이 솔루션은 포맷 스트링 공격에 취약합니다 . 올바른 솔루션은 형식 문자열을 한 번만 통과해야합니다.
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());
}

예:

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
이것은 대부분의 다른 솔루션이 취약한 형식 문자열 공격을 피하기 때문에 답이되어야합니다. Java 9는 .replaceAll()문자열 대체 콜백 을 지원하여 훨씬 간단 합니다.
200_success

외부 라이브러리를 사용하지 않기 때문에 이것이 대답이어야합니다.
Bohao LI

3

나는 당신이 원하는 것을 정확하게 하는 작은 도서관 의 저자입니다 .

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);

또는 인수를 연결할 수 있습니다.

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

// Output: "5 + 10 = 15"

라이브러리로 조건 기반 형식을 지정할 수 있습니까?
gaurav

@gaurav 은별로 없습니다. 필요한 경우 모든 기능을 갖춘 템플릿 라이브러리가 필요합니다.
Andrei Ciobanu

2

Apache Commons Lang의 replaceEach 메소드는 특정 요구에 따라 유용 할 수 있습니다. 이 단일 메소드 호출로 이름별로 자리 표시자를 쉽게 대체 할 수 있습니다.

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

입력 텍스트가 주어지면 첫 번째 문자열 배열에서 모든 자리 표시자가 두 번째 배열의 해당 값으로 바뀝니다.


1

문자열 도우미 클래스에서 이와 같은 것을 가질 수 있습니다.

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

고마워요. 거의 내가 원하는 것을하지만, 유일한 것은 수정자를 설명하지 않는 것입니다 ( "% (key) 08d"고려)
Andy

1
또한 사용중인 값에 해당 항목이 포함되어 있으면 무한 루프 상태가됩니다.
Aaron Dufour

1
경고 : 루프는 중간 대체 결과를 자체 형식 문자열로 취급하므로이 솔루션은 형식 문자열 공격에 취약합니다 . 올바른 솔루션은 형식 문자열을 한 번만 통과해야합니다.
200_success

1

내 대답은 :

a) 가능하면 StringBuilder를 사용하십시오.

b) "자리 표시 자"의 위치 (정수는 달러 매크로와 같은 최고의 특수 문자) 위치를 유지 한 다음 StringBuilder.insert()(몇 가지 버전의 인수) 사용합니다.

외부 라이브러리를 사용하면 과도하게 보이며 StringBuilder가 내부적으로 String으로 변환 될 때 성능이 크게 저하됩니다.


1

내가 수업을 만든 답을 바탕으로 MapBuilder:

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;
    }

}

그런 다음 StringFormatString 형식의 클래스 를 만들었습니다 .

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;
    }

}

그런 식으로 사용할 수 있습니다.

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

1
경고 : 루프는 중간 대체 결과를 자체 형식 문자열로 취급하므로이 솔루션은 형식 문자열 공격에 취약합니다 . 올바른 솔루션은 형식 문자열을 한 번만 통과해야합니다.
200_success

1

또한 변수 발생을 대체하는 문자열을 형식화 할 수있는 util / helper 클래스 (jdk 8 사용)를 만들었습니다.

이를 위해 Matchers "appendReplacement"메소드를 사용하여 모든 대체를 수행하고 형식 문자열의 영향을받는 부분 만 반복합니다.

도우미 클래스는 현재 잘 문서화되어 있지 않습니다. 나는 이것을 나중에 바꿀 것이다;) 어쨌든 나는 가장 중요한 내용을 언급했다.

    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;
    }
}

다음과 같은 값 (또는 접미사 접두사 또는 noKeyFunction)을 사용하여 특정지도에 대한 클래스 인스턴스를 만들 수 있습니다.

    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?"

더욱이 값 Map의 키가 누락 된 경우 발생하는 상황을 정의 할 수 있습니다 (예 : 형식 문자열의 잘못된 변수 이름 또는 Map의 누락 ​​된 키). 기본 동작은 다음과 같이 확인되지 않은 예외가 발생합니다 (확인 된 예외를 처리 할 수없는 기본 jdk8 함수를 사용하므로 확인되지 않음).

    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.

생성자 호출에서 다음과 같이 사용자 정의 동작을 정의 할 수 있습니다.

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!"

또는 키가없는 기본 동작으로 다시 위임하십시오.

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

더 나은 처리를 위해 다음과 같은 정적 메소드 표현이 있습니다.

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

이것을 작성하는 순간 Java에는 내장 된 것이 없습니다. 나는 당신 자신의 구현을 쓰는 것이 좋습니다. 내가 선호하는 것은 맵을 생성하고 함수에 전달하는 대신 간단한 유창한 빌더 인터페이스를 선호하는 것입니다.

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

간단한 구현은 다음과 같습니다.

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

템플릿 라이브러리 인 Freemarker를 사용해보십시오 .

대체 텍스트


4
프리 마커? 나는 그가 일반 자바에서 이것을하는 방법을 기꺼이 알고 있다고 생각한다. 어쨌든 Freemarker가 가능한 대답이라면 JSP도 정답이라고 말할 수 있습니까?
Rakesh Juyal

1
고마워,하지만 내 임무에 이것은 일종의 과잉 것 같습니다. 하지만 고마워
Andy

1
@Rakesh JSP는 매우 "보기 / FE"에 특정한 것입니다. 과거에는 FreeMarker를 사용하여 XML을 생성하고 때로는 JAVA 파일을 생성했습니다. Andy는 유틸리티 하나를 직접 작성해야한다고 두려워합니다 (또는 위에서 설명한 것과 비슷 함)
Kannan Ekanath

@Boris 어느 것이 더 나은 프리 마커 대 속도 대 스트링 템플릿입니까?
gaurav



0

공식 ICU4J 라이브러리를 살펴 봐야 합니다 . JDK에서 사용할 수있는 것과 유사한 MessageFormat 클래스를 제공 하지만이 클래스는 명명 된 자리 표시자를 지원합니다.

이 페이지에서 제공되는 다른 솔루션과 달리 ICU4j는 IBM이 유지 보수하고 정기적으로 업데이트 하는 ICU 프로젝트의 일부입니다 . 또한 복수화 등과 같은 고급 사용 사례를 지원합니다.

다음은 코드 예입니다.

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

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

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

0

Java에서 문자열 보간을 사용하는 Java 플러그인이 있습니다 (예 : Kotlin, JavaScript). 지원 자바 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

문자열 리터럴에서 변수 사용 :

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

식 사용하기 :

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

기능 사용하기 :

@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;
}

자세한 내용은 README 프로젝트를 읽으십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.