Penggantian string di java, mirip dengan template kecepatan

96

Apakah ada Stringmekanisme penggantian di Java, di mana saya bisa melewatkan objek dengan teks, dan itu menggantikan string saat terjadi.
Misalnya, teksnya adalah:

Hello ${user.name},
    Welcome to ${site.name}. 

Objek yang saya miliki adalah "user"dan "site". Saya ingin mengganti string yang diberikan di dalam ${}dengan nilai yang setara dari objek. Ini sama seperti kita mengganti objek dalam templat kecepatan.

Joe
sumber
1
Ganti dimana? Kelas? JSP? String memiliki metode format jika Anda hanya:String.format("Hello %s", username);
Droo
1
@Droo: Dalam contoh, string adalah seperti Hello ${user.name}, bukan seperti, Hello %satau Hello {0}.
Adeel Ansari
2
Jika Anda membutuhkan sesuatu yang terlihat seperti velocity dan berbau seperti velocity, mungkinkah itu velocity? :)
serg
@Droo: Ini bukan kelas. Saya memiliki teks di atas dalam variabel "String" dan ingin mengganti semua kemunculan string di dalam $ {} dengan nilai dalam objek terkait. misalnya ganti semua $ {user.name} dengan properti name di objek "user".
Joe
@serg: Ya, itu adalah kode kecepatan. dan saya ingin menghapus kecepatan dari kode saya.
Joe

Jawaban:

142

Gunakan StringSubstitutordari Teks Apache Commons.

https://commons.apache.org/proper/commons-text/

Ini akan melakukannya untuk Anda (dan open source ...)

 Map<String, String> valuesMap = new HashMap<String, String>();
 valuesMap.put("animal", "quick brown fox");
 valuesMap.put("target", "lazy dog");
 String templateString = "The ${animal} jumped over the ${target}.";
 StringSubstitutor sub = new StringSubstitutor(valuesMap);
 String resolvedString = sub.replace(templateString);
JH.
sumber
1
Seharusnya Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones
3
StrSubstitutorsekarang tidak digunakan lagi di https://commons.apache.org/proper/commons-lang/ . Pengguna https://commons.apache.org/proper/commons-text/ sebagai gantinya
Lukuluba
7
StrSubstitutortidak digunakan lagi pada 1.3, gunakan StringSubstitutorsaja. Kelas ini akan dihapus di 2.0. Ketergantungan Gradle untuk impor StringSubstitutoradalahorg.apache.commons:commons-text:1.4
Yurii Rabeshko
apakah itu memungkinkan substitusi berdasarkan kondisi?
Gaurav
131

Lihat java.text.MessageFormatkelasnya, MessageFormat mengambil sekumpulan objek, memformatnya, lalu menyisipkan string yang telah diformat ke dalam pola di tempat yang sesuai.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
sumber
10
Terima kasih! Saya tahu java harus memiliki cara inbuilt untuk melakukan ini tanpa harus menggunakan mesin template panik untuk melakukan hal yang sederhana!
Joseph Rajeev Motha
2
Sepertinya String.format dapat melakukan apa pun yang dapat dilakukan ini - stackoverflow.com/questions/2809633/…
Noumenon
6
+1. Berhati-hatilah formatjuga menggunakan Object...vararg sehingga Anda dapat menggunakan sintaks yang lebih singkat ini jika lebih disukaiformat("{0} world {1}", "Hello", "!");
davnicwil
Perlu dicatat bahwa MessageFormathanya dapat digunakan dengan andal untuk senama, menampilkan pesan, bukan untuk keluaran di mana pemformatan teknis penting. Misalnya, angka akan diformat per pengaturan lokal, menjadikannya tidak valid untuk penggunaan teknis.
Marnes
24

Cara yang saya sukai adalah String.format()karena ini oneliner dan tidak memerlukan perpustakaan pihak ketiga:

String message = String.format("Hello! My name is %s, I'm %s.", name, age); 

Saya menggunakan ini secara teratur, misalnya dalam pesan pengecualian seperti:

throw new Exception(String.format("Unable to login with email: %s", email));

Petunjuk: Anda dapat memasukkan variabel sebanyak yang Anda suka karena format()menggunakan Varargs

artgrohe
sumber
Ini kurang berguna jika Anda perlu mengulangi argumen yang sama lebih dari sekali. Misalnya: String.format("Hello! My name is %s, I'm %s. Why is my name %s you ask? Well I'm only %s years old so I don't know", name, age, name, age);. Jawaban-jawaban lain di sini hanya perlu menentukan setiap argumen sekali.
asherbar
2
@asherbar Anda dapat menggunakan penentu indeks argumen dalam format string, misalnyaString.format("Hello! My name is %1$s, I'm %2$s. Why is my name %1$s you ask? Well I'm only %2$s years old so I don't know", name, age)
jazzpi
@jazzpi Saya tidak pernah tahu itu. Terima kasih!
asherbar
20

Saya mengumpulkan implementasi tes kecil ini. Ide dasarnya adalah memanggil formatdan meneruskan dalam format string, dan peta objek, dan nama yang mereka miliki secara lokal.

Output dari berikut ini adalah:

Anjing saya bernama fido, dan Jane Doe adalah pemiliknya.

public class StringFormatter {

    private static final String fieldStart = "\\$\\{";
    private static final String fieldEnd = "\\}";

    private static final String regex = fieldStart + "([^}]+)" + fieldEnd;
    private static final Pattern pattern = Pattern.compile(regex);

    public static String format(String format, Map<String, Object> objects) {
        Matcher m = pattern.matcher(format);
        String result = format;
        while (m.find()) {
            String[] found = m.group(1).split("\\.");
            Object o = objects.get(found[0]);
            Field f = o.getClass().getField(found[1]);
            String newVal = f.get(o).toString();
            result = result.replaceFirst(regex, newVal);
        }
        return result;
    }

    static class Dog {
        public String name;
        public String owner;
        public String gender;
    }

    public static void main(String[] args) {
        Dog d = new Dog();
        d.name = "fido";
        d.owner = "Jane Doe";
        d.gender = "him";
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("d", d);
        System.out.println(
           StringFormatter.format(
                "My dog is named ${d.name}, and ${d.owner} owns ${d.gender}.", 
                map));
    }
}

Catatan: Ini tidak dapat dikompilasi karena pengecualian yang tidak tertangani. Tapi itu membuat kode lebih mudah dibaca.

Selain itu, saya tidak suka Anda harus membuat sendiri peta dalam kode, tetapi saya tidak tahu cara mendapatkan nama variabel lokal secara terprogram. Cara terbaik untuk melakukannya, adalah dengan mengingat untuk meletakkan objek di peta segera setelah Anda membuatnya.

Contoh berikut menghasilkan hasil yang Anda inginkan dari contoh Anda:

public static void main(String[] args) {
    Map<String, Object> map = new HashMap<String, Object>();
    Site site = new Site();
    map.put("site", site);
    site.name = "StackOverflow.com";
    User user = new User();
    map.put("user", user);
    user.name = "jjnguy";
    System.out.println(
         format("Hello ${user.name},\n\tWelcome to ${site.name}. ", map));
}

Saya juga harus menyebutkan bahwa saya tidak tahu apa itu Velocity, jadi saya harap jawaban ini relevan.

jjnguy
sumber
Inilah yang saya cari. Terima kasih telah memberikan implementasi. Saya mencoba untuk itu dan mendapatkan hasil yang salah. : D. Pokoknya itu memecahkan masalah saya.
Joe
2
@ Joe, senang bisa membantu. Itu adalah alasan yang bagus bagi saya untuk akhirnya berlatih menulis beberapa kode yang menggunakan refleksi di Java.
jjnguy
6

Berikut adalah garis besar bagaimana Anda bisa melakukan ini. Ini harus relatif mudah untuk menerapkannya sebagai kode sebenarnya.

  1. Buat peta dari semua objek yang akan direferensikan di template.
  2. Gunakan ekspresi reguler untuk menemukan referensi variabel dalam template dan menggantinya dengan nilainya (lihat langkah 3). Kelas Matcher akan berguna untuk temukan-dan-ganti.
  3. Pisahkan nama variabel di titik. user.nameakan menjadi userdan name. Cari userdi peta Anda untuk mendapatkan objek dan gunakan refleksi untuk mendapatkan nilai dari nameobjek tersebut. Dengan asumsi objek Anda memiliki getter standar, Anda akan mencari metode getNamedan memanggilnya.
casablanca
sumber
Heh, baru saja melihat jawaban ini. Itu identik dengan milikku. Beri tahu saya pendapat Anda tentang penerapan saya.
jjnguy
5

Ada beberapa implementasi Bahasa Ekspresi di luar sana yang melakukan ini untuk Anda, mungkin lebih baik daripada menggunakan implementasi Anda sendiri jika atau jika persyaratan Anda bertambah, lihat misalnya JUEL dan MVEL

Saya suka dan telah berhasil menggunakan MVEL setidaknya dalam satu proyek.

Juga lihat posting Stackflow JSTL / JSP EL (Expression Language) dalam konteks non JSP (standalone)

Christoffer Soop
sumber
0

Tidak ada di luar kotak yang sebanding dengan kecepatan sejak kecepatan ditulis untuk menyelesaikan masalah itu dengan tepat. Hal terdekat yang dapat Anda coba adalah melihat ke Formatter

http://cupi2.uniandes.edu.co/site/images/recursos/javadoc/j2se/1.5.0/docs/api/java/util/Formatter.html

Namun pemformat sejauh yang saya tahu dibuat untuk menyediakan opsi pemformatan seperti C di Java sehingga mungkin tidak menggores Anda secara persis, tetapi Anda dipersilakan untuk mencoba :).

Mike Milkin
sumber
0

Saya menggunakan GroovyShell di java untuk mengurai template dengan Groovy GString:

Binding binding = new Binding();
GroovyShell gs = new GroovyShell(binding);
// this JSONObject can also be replaced by any Java Object
JSONObject obj = new JSONObject();
obj.put("key", "value");
binding.setProperty("obj", obj)
String str = "${obj.key}";
String exp = String.format("\"%s\".toString()", str);
String res = (String) gs.evaluate(exp);
// value
System.out.println(str);
Kan
sumber