ตัวยึดตำแหน่งที่ระบุชื่อในการจัดรูปแบบสตริง


175

ใน Python เมื่อทำการจัดรูปแบบสตริงฉันสามารถเติมตัวยึดตำแหน่งด้วยชื่อแทนที่จะตามตำแหน่งเช่นนั้น:

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

ฉันสงสัยว่าเป็นไปได้ใน Java (หวังว่าจะไม่มีห้องสมุดภายนอก)?


คุณอาจขยาย MessageFormat และปรับใช้ funtionality การแมปจากตัวแปรไปยังดัชนีในนั้น
vpram86


1
ประวัติบางส่วน: Java คัดลอก C / C ++ เป็นส่วนใหญ่ในเรื่องนี้เนื่องจากมันพยายามหลอกล่อนักพัฒนาจากโลก C ++ ซึ่ง%sเป็นเรื่องธรรมดา en.wikipedia.org/wiki/Printf_format_string#Historyโปรดทราบว่า IDE และ FindBugs บางตัวอาจตรวจจับการนับ% s และ% d ไม่ตรงกันโดยอัตโนมัติ แต่ฉันยังคงต้องการฟิลด์ที่มีชื่อ
Christophe Roussy

คำตอบ:


143

StrSubstitutor ของ jakarta commons lang เป็นวิธีที่มีน้ำหนักเบาในการทำสิ่งนี้หากค่าของคุณมีรูปแบบที่ถูกต้องแล้ว

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

ผลลัพธ์ข้างต้นเป็น:

"มีค่าไม่ถูกต้อง '1' ในคอลัมน์ # 2"

เมื่อใช้ Maven คุณสามารถเพิ่มการพึ่งพานี้กับ pom.xml ของคุณ:

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

2
ฉันพบว่ามันน่าผิดหวังที่ไลบรารี่จะไม่โยนหากไม่พบคีย์อย่างไรก็ตามถ้าคุณใช้ไวยากรณ์เริ่มต้น ( ${arg}) แทนที่จะเป็นแบบกำหนดเองด้านบน ( %(arg)) จากนั้น regex จะไม่คอมไพล์ซึ่งเป็นผลที่ต้องการ
John Lehmann

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 syntax Cleaner ถ้าคุณต้องการสร้างการแสดงออกที่ซับซ้อนรวมทั้งคุณไม่จำเป็นต้องสนใจเกี่ยวกับประเภทของวัตถุที่คุณใส่ลงในสตริง


ไม่แน่ใจว่าทำไมคุณไม่สามารถตำแหน่งในสตริงไม่สำคัญเฉพาะตำแหน่งในรายการของ args ซึ่งทำให้ปัญหาการเปลี่ยนชื่อ คุณรู้ชื่อของคีย์ซึ่งหมายความว่าคุณสามารถเลือกตำแหน่งสำหรับคีย์ในรายการอาร์กิวเมนต์ จากนี้ไปค่าจะเป็นที่รู้จักกันในชื่อ 0 และคอลัมน์เป็น 1: MessageeFormat.format ("มีค่าที่ไม่ถูกต้อง \" {0} \ "ในคอลัมน์ # {1} โดยใช้ {0} เป็นค่าอาจทำให้เกิดปัญหามากมาย", valueMap .get ('ค่า'), valueMap.get ('คอลัมน์'));
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.

หากคุณคาดว่าจะโหลดไฟล์ที่มีขนาดใหญ่มากฉันพบว่าไลบรารีนี้ยังสนับสนุนreplaceInค่าทดแทนที่ลงในบัฟเฟอร์: StringBuilder หรือ 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

สำหรับกรณีที่ง่ายมากคุณสามารถใช้การแทนที่สตริง hardcoded ไม่จำเป็นต้องมีไลบรารีที่นั่น:

    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 A :)
Christophe Roussy

2
คำเตือน:เพราะเทคนิคนี้ถือว่าผลการเปลี่ยนตัวกลางเป็นสตริงรูปแบบของตัวเองวิธีนี้คือความเสี่ยงที่จะเป็นรูปแบบการโจมตีสตริง โซลูชันที่ถูกต้องควรทำการส่งครั้งเดียวผ่านสตริงรูปแบบ
200_success

@ 200_ ความสำเร็จใช่จุดดีที่พูดถึงเรื่องความปลอดภัยแน่นอนว่ารหัสนี้ไม่เหมาะสำหรับการใช้งานจริง ...
Christophe Roussy

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/…ฟังก์ชัน combiner ต้องส่งคืนพารามิเตอร์ตัวที่สองหากพารามิเตอร์แรกเป็นตัวตน คนข้างต้นจะคืนค่าตัวตนแทน นอกจากนี้ยังละเมิดกฎนี้: 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

วิธีreplaceEachของ Apache Commons Lang อาจมีประโยชน์ขึ้นอยู่กับความต้องการเฉพาะของคุณ คุณสามารถใช้มันเพื่อแทนที่ตัวยึดตำแหน่งด้วยชื่อด้วยการเรียกใช้เมธอดเดียว:

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

ขอบคุณมากมันทำเกือบสิ่งที่ฉันต้องการ แต่สิ่งเดียวคือมันไม่ได้ปรับเปลี่ยนบัญชี (พิจารณา "% (กุญแจ) 08d")
แอนดี้

1
โปรดทราบด้วยว่าสิ่งนี้จะเข้าสู่วงวนไม่สิ้นสุดหากค่าใด ๆ ที่ใช้อยู่มีรายการที่สอดคล้องกัน
Aaron Dufour

1
คำเตือน:เพราะห่วงถือว่าผลการเปลี่ยนตัวกลางเป็นสตริงรูปแบบของตัวเองวิธีนี้คือความเสี่ยงที่จะเป็นรูปแบบการโจมตีสตริง โซลูชันที่ถูกต้องควรทำการส่งครั้งเดียวผ่านสตริงรูปแบบ
200_success

1

คำตอบของฉันคือ:

a) ใช้ StringBuilder เมื่อเป็นไปได้

b) Keep (ในรูปแบบใด ๆ : จำนวนเต็มเป็นตัวเลือกที่ดีที่สุดเช่น char macro Dollar เป็นต้น) ตำแหน่งของ "placeholder" จากนั้นใช้StringBuilder.insert()(อาร์กิวเมนต์ไม่กี่เวอร์ชัน)

ใช้ห้องสมุดภายนอกดูเหมือน overkill และฉันเชื่อว่าประสิทธิภาพลดลงอย่างมีนัยสำคัญเมื่อ 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;
    }

}

จากนั้นฉันสร้างคลาสStringFormatสำหรับการจัดรูปแบบ String:

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) ซึ่งสามารถจัดรูปแบบสตริงและแทนที่ตัวแปรที่เกิดขึ้น

เพื่อจุดประสงค์นี้ฉันใช้วิธีการ "appendReplacement" ของ Matchers ซึ่งทำการทดแทนและวนซ้ำเฉพาะส่วนที่ได้รับผลกระทบของสตริงรูปแบบเท่านั้น

คลาสตัวช่วยไม่ได้จัดทำเอกสาร javadoc เป็นอย่างดี ฉันจะเปลี่ยนแปลงสิ่งนี้ในอนาคต;) อย่างไรก็ตามฉันแสดงความคิดเห็นบรรทัดที่สำคัญที่สุด (ฉันหวังว่า)

    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 ขาดหายไป (ทำงานได้ทั้งสองวิธีเช่นชื่อตัวแปรผิดในรูปแบบสตริงหรือคีย์ที่ขาดหายไปในแผนที่) พฤติกรรมเริ่มต้นเป็นข้อยกเว้นที่ไม่ถูกตรวจสอบโยน (ไม่ถูกตรวจสอบเพราะฉันใช้ฟังก์ชั่นเริ่มต้น 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ซึ่งเป็นไลบรารี่ของ templating

ข้อความแสดงแทน


4
Freemarker? ฉันเดาว่าเขายินดีที่จะรู้วิธีการทำเช่นนี้ใน java ธรรมดา อย่างไรก็ตามถ้า Freemarker เป็นคำตอบที่น่าจะเป็นไปได้แล้วฉันจะบอกว่า JSP ก็จะเป็นคำตอบที่ถูกต้องหรือไม่?
Rakesh Juyal

1
ขอบคุณ แต่สำหรับงานของฉันในมือดูเหมือนจะเกินความจริงแล้ว แต่ขอบคุณ.
แอนดี้

1
@Rakesh JSP เป็นสิ่งเฉพาะ "view / FE" ที่เฉพาะเจาะจง ฉันเคยใช้ FreeMarker มาก่อนในการสร้าง XML และบางครั้งก็สร้างไฟล์ JAVA แอนดี้กลัวว่าคุณจะต้องเขียนยูทิลิตี้ตัวเดียว (หรือเหมือนกับที่กำหนดไว้ด้านบน)
Kannan Ekanath

@ บอริสเป็นที่หนึ่งที่ดีกว่า freemarker vs velocity เทียบกับ stringtemplate?
gaurav


0

https://dzone.com/articles/java-string-format-examples String.format (inputString, [listOfParams]) จะเป็นวิธีที่ง่ายที่สุด ตัวยึดตำแหน่งในสตริงสามารถกำหนดได้ตามคำสั่ง สำหรับรายละเอียดเพิ่มเติมตรวจสอบลิงค์ที่ให้ไว้


0

คุณควรมีลักษณะที่อย่างเป็นทางการห้องสมุด ICU4J มันมีคลาสMessageFormatคล้ายกับที่มีอยู่ใน JDK แต่อดีตนี้รองรับตัวยึดชื่อ

ไม่เหมือนโซลูชันอื่น ๆ ที่มีให้ในหน้านี้ ICU4j เป็นส่วนหนึ่งของโครงการ ICUที่ดูแลโดย IBM และอัพเดตเป็นประจำ นอกจากนี้ยังรองรับกรณีการใช้งานขั้นสูงเช่นหลายฝ่ายและอื่น ๆ อีกมากมาย

นี่คือตัวอย่างรหัส:

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) รองรับ Java 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.