Haruskah konstanta string didefinisikan jika hanya akan digunakan sekali?


24

Kami menerapkan adaptor untuk Jaxen (perpustakaan XPath untuk Java) yang memungkinkan kami untuk menggunakan XPath untuk mengakses model data aplikasi kami.

Ini dilakukan dengan mengimplementasikan kelas yang memetakan string (diteruskan kepada kami dari Jaxen) ke dalam elemen model data kami. Kami memperkirakan kami akan membutuhkan sekitar 100 kelas dengan total perbandingan 1000 string.

Saya pikir cara terbaik untuk melakukan ini adalah pernyataan if / else sederhana dengan string yang ditulis langsung ke dalam kode - daripada mendefinisikan setiap string sebagai sebuah konstanta. Sebagai contoh:

public Object getNode(String name) {
    if ("name".equals(name)) {
        return contact.getFullName();
    } else if ("title".equals(name)) {
        return contact.getTitle();
    } else if ("first_name".equals(name)) {
        return contact.getFirstName();
    } else if ("last_name".equals(name)) {
        return contact.getLastName();
    ...

Namun saya selalu diajarkan bahwa kita seharusnya tidak menanamkan nilai string secara langsung ke dalam kode, tetapi membuat konstanta string sebagai gantinya. Itu akan terlihat seperti ini:

private static final String NAME = "name";
private static final String TITLE = "title";
private static final String FIRST_NAME = "first_name";
private static final String LAST_NAME = "last_name";

public Object getNode(String name) {
    if (NAME.equals(name)) {
        return contact.getFullName();
    } else if (TITLE.equals(name)) {
        return contact.getTitle();
    } else if (FIRST_NAME.equals(name)) {
        return contact.getFirstName();
    } else if (LAST_NAME.equals(name)) {
        return contact.getLastName();
    ...

Dalam hal ini saya pikir itu ide yang buruk. Konstanta hanya akan digunakan sekali, dalam getNode()metode ini. Menggunakan string secara langsung sama mudahnya dibaca dan dipahami dengan menggunakan konstanta, dan menyelamatkan kita menulis setidaknya seribu baris kode.

Jadi apakah ada alasan untuk mendefinisikan konstanta string untuk penggunaan tunggal? Atau apakah dapat diterima untuk menggunakan string secara langsung?


PS. Sebelum ada yang menyarankan menggunakan enum, kami membuat prototipe, tetapi konversi enum 15 kali lebih lambat dari perbandingan string sederhana sehingga tidak dipertimbangkan.


Kesimpulan: Jawaban di bawah ini memperluas cakupan pertanyaan ini di luar konstanta string, jadi saya punya dua kesimpulan:

  • Mungkin OK untuk menggunakan string secara langsung daripada konstanta string dalam skenario ini, tetapi
  • Ada beberapa cara untuk menghindari penggunaan string sama sekali, yang mungkin lebih baik.

Jadi saya akan mencoba teknik pembungkus yang menghindari string sepenuhnya. Sayangnya kami tidak dapat menggunakan pernyataan saklar string karena kami belum berada di Java 7. Namun pada akhirnya, saya pikir jawaban terbaik bagi kami adalah mencoba setiap teknik dan mengevaluasi kinerjanya. Kenyataannya adalah bahwa jika satu teknik jelas lebih cepat maka kita mungkin akan memilihnya terlepas dari keindahan atau kepatuhannya pada konvensi.


3
Anda tidak berencana mengetik 1000 secara manual jika pernyataan benar?
JeffO

1
Saya merasa sangat sedih betapa tidak menyenangkannya sesuatu yang sederhana ini dalam beberapa bahasa ...
Jon Purdy

5
Java 7 memungkinkan Strings sebagai switchlabel. Gunakan sakelar alih-alih ifriam.
Pasang kembali Monica - M. Schröder

3
konversi enum 15 kali lebih lambat jika Anda mengonversi string ke nilai enumnya! Lulus enum secara langsung dan bandingkan dengan nilai enum lain dari jenis yang sama!
Neil

2
Bau seperti HashMap bisa menjadi solusi.
MarioDS

Jawaban:


5

Coba ini. Refleksi awal tentu mahal, tetapi jika Anda akan menggunakannya berkali-kali, yang saya pikir Anda akan lakukan, ini pasti merupakan solusi yang lebih baik apa yang Anda usulkan. Saya tidak suka menggunakan refleksi, tetapi saya mendapati diri saya menggunakannya ketika saya tidak suka alternatif untuk refleksi. Saya pikir ini akan menyelamatkan tim Anda banyak sakit kepala, tetapi Anda harus melewati nama metode (dalam huruf kecil).

Dengan kata lain, alih-alih memberikan "nama", Anda akan memberikan "nama lengkap" karena nama metode get adalah "getFullName ()".

Map<String, Method> methodMapping = null;

public Object getNode(String name) {
    Map<String, Method> methods = getMethodMapping(contact.getClass());
    return methods.get(name).invoke(contact);
}

public Map<String, Method> getMethodMapping(Class<?> contact) {
    if(methodMapping == null) {
        Map<String, Method> mapping = new HashMap<String, Method>();
        Method[] methods = contact.getDeclaredMethods();
        for(Method method : methods) {
            if(method.getParameterTypes().length() == 0) {
                if(method.getName().startsWith("get")) {
                    mapping.put(method.getName().substring(3).toLower(), method);
                } else if (method.getName().startsWith("is"))) {
                    mapping.put(method.getName().substring(2).toLower(), method);
                }
            }
        }
        methodMapping = mapping;
    }
    return methodMapping;
}

Jika Anda perlu mengakses data yang terkandung dalam anggota kontak, Anda dapat mempertimbangkan membangun kelas pembungkus untuk kontak yang memiliki semua metode untuk mengakses informasi yang diperlukan. Ini juga akan berguna untuk menjamin bahwa nama-nama bidang akses akan selalu tetap sama (yaitu jika kelas wrapper memiliki getFullName () dan Anda memanggil dengan nama lengkap, itu akan selalu berfungsi bahkan jika kontak getFullName () telah diganti namanya - itu akan menyebabkan kesalahan kompilasi sebelum Anda dapat melakukannya).

public class ContactWrapper {
    private Contact contact;

    public ContactWrapper(Contact contact) {
        this.contact = contact;
    }

    public String getFullName() {
        return contact.getFullName();
    }
    ...
}

Solusi ini telah menyelamatkan saya beberapa kali, yaitu ketika saya ingin memiliki representasi data tunggal untuk digunakan dalam jsf datatables dan ketika data itu perlu diekspor ke dalam laporan menggunakan jasper (yang tidak menangani aksesor objek yang rumit dengan baik dalam pengalaman saya) .


Saya suka ide objek pembungkus dengan metode yang disebut via .invoke(), karena tidak menghilangkan konstanta string sepenuhnya. Saya tidak begitu tertarik pada refleksi runtime untuk mengatur peta, meskipun mungkin mengeksekusi getMethodMapping()dalam sebuah staticblok akan baik-baik saja sehingga hal itu terjadi pada startup bukannya setelah sistem berjalan.
belanda

@ Belanda, pola pembungkus adalah salah satu yang sering saya gunakan, karena cenderung menimbulkan banyak masalah terkait dengan antarmuka / pengontrol. Antarmuka selalu dapat menggunakan pembungkus dan senang dengan itu dan pengontrol dapat diputarbalikkan sementara itu. Yang perlu Anda ketahui adalah data apa yang Anda inginkan tersedia di antarmuka. Dan lagi, saya katakan untuk penekanan, saya biasanya tidak suka refleksi, tetapi jika ini adalah aplikasi web, itu benar-benar dapat diterima jika Anda melakukannya pada startup karena klien tidak akan melihat waktu tunggu itu.
Neil

@Neil Mengapa tidak menggunakan BeanUtils dari Apache commons? Ini juga mendukung objek yang disematkan. Anda dapat melihat seluruh struktur data obj.attrA.attrB.attrN dan memiliki banyak kemungkinan lain :-)
Laiv

Alih-alih memetakan dengan Maps, saya akan memilih @Annotations. Sesuatu seperti JPA tidak. Untuk menentukan Anotasi saya sendiri untuk memetakan entri pengontrol (string) dengan attr atau pengambil tertentu. Untuk bekerja dengan Anotasi cukup mudah dan Tersedia dari Java 1.6 (saya pikir)
Laiv

5

Jika mungkin gunakan Java 7 yang memungkinkan Anda untuk menggunakan Strings dalam switchpernyataan.

Dari http://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html

public class StringSwitchDemo {

    public static int getMonthNumber(String month) {

        int monthNumber = 0;

        if (month == null) {
            return monthNumber;
        }

        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }

    public static void main(String[] args) {

        String month = "August";

        int returnedMonthNumber =
            StringSwitchDemo.getMonthNumber(month);

        if (returnedMonthNumber == 0) {
            System.out.println("Invalid month");
        } else {
            System.out.println(returnedMonthNumber);
        }
    }
}

Saya belum mengukur, tetapi saya percaya bahwa pernyataan switch mengkompilasi ke tabel lompatan alih-alih daftar panjang perbandingan. Ini harus lebih cepat.

Mengenai pertanyaan Anda yang sebenarnya: Jika Anda hanya menggunakannya sekali Anda tidak perlu membuatnya menjadi konstanta. Namun pertimbangkan bahwa konstanta dapat didokumentasikan dan muncul di Javadoc. Ini mungkin penting untuk nilai string non-sepele.


2
Tentang tabel lompatan. Switch string diganti oleh ke switch, pertama didasarkan pada kode hash (kesetaraan diperiksa untuk semua konstanta dengan kode hash yang sama) dan memilih indeks cabang, switch kedua pada indeks cabang dan memilih kode cabang asli. Yang terakhir jelas cocok untuk tabel cabang, yang pertama bukan karena distribusi fungsi hash. Jadi setiap keuntungan kinerja mungkin karena realisasi berbasis hash.
scarfridge

Poin yang sangat bagus; jika kinerjanya baik, mungkin lebih baik pindah ke Java 7 hanya untuk ini ...
gutch

4

Jika Anda akan mempertahankan ini (membuat segala perubahan nontrivial yang pernah ada), saya mungkin benar-benar mempertimbangkan untuk menggunakan semacam pembuatan kode berbasis anotasi (mungkin melalui CGLib ) atau bahkan hanya sebuah skrip yang menulis semua kode untuk Anda. Bayangkan jumlah kesalahan pengetikan dan kesalahan yang dapat merambah dengan pendekatan yang Anda pertimbangkan ...


Kami mempertimbangkan untuk membuat anotasi metode yang ada, tetapi beberapa pemetaan melintasi beberapa objek (misalnya pemetaan "negara" ke object.getAddress().getCountry()) yang sulit untuk diwakili dengan anotasi. Perbandingan string if / else tidak cantik, tetapi mereka cepat, fleksibel, mudah dimengerti dan mudah untuk unit test.
gutch

1
Anda benar tentang potensi kesalahan ketik dan kesalahan; satu-satunya pertahanan saya ada unit test. Tentu saja itu berarti lebih banyak kode ...
gutch

2

Saya masih akan menggunakan konstanta yang didefinisikan di bagian atas kelas Anda. Itu membuat kode Anda lebih mudah dikelola karena lebih mudah untuk melihat apa yang dapat diubah di lain waktu (jika perlu). Misalnya, "first_name"bisa menjadi "firstName"beberapa waktu kemudian.


Saya setuju, bagaimanapun, jika kode ini akan dihasilkan secara otomatis dan konstanta tidak digunakan di tempat lain, maka itu tidak masalah (OP mengatakan mereka perlu melakukan ini di 100 kelas).
NoChance

5
Saya hanya tidak melihat sudut "pemeliharaan 'di sini Anda mengubah" first_name "menjadi" givenName "sekali di satu tempat dalam kedua kasus tersebut. Namun dalam kasus konstanta bernama Anda sekarang dibiarkan dengan variabel berantakan" first_name "yang mengacu pada string "givenName" jadi Anda mungkin ingin mengubahnya juga sehingga Anda sekarang memiliki tiga perubahan di dua tempat
James Anderson

1
Dengan IDE yang tepat, perubahan ini sepele. Yang saya anjurkan adalah bahwa lebih jelas di mana membuat perubahan ini karena Anda telah meluangkan waktu untuk mendeklarasikan konstanta di bagian atas kelas dan tidak perlu membaca seluruh kode di kelas untuk lakukan perubahan ini.
Bernard

Tetapi ketika Anda membaca pernyataan if, Anda harus kembali dan memeriksa bahwa konstanta berisi string yang Anda pikir berisi - tidak ada yang disimpan di sini.
James Anderson

1
Mungkin, tetapi inilah sebabnya saya memberi nama konstanta saya dengan baik.
Bernard

1

Jika penamaan Anda konsisten (alias "some_whatever"selalu dipetakan ke getSomeWhatever()), Anda bisa menggunakan refleksi untuk menentukan dan menjalankan metode get.


Lebih baik getSome_whthing (). Mungkin saja memecahkan unta, tetapi jauh lebih penting untuk memastikan refleksi berfungsi. Ditambah lagi ia memiliki keuntungan tambahan yang membuat Anda berkata, "Kenapa kita melakukannya dengan cara itu ... oh tunggu .. Tunggu! George tidak mengubah nama metode itu!"
Neil

0

Saya kira, pemrosesan anotasi bisa menjadi solusinya, bahkan tanpa anotasi. Itu hal yang dapat menghasilkan semua kode membosankan untuk Anda. Kelemahannya adalah Anda akan mendapatkan kelas N yang dihasilkan untuk kelas model N. Anda juga tidak dapat menambahkan apa pun ke kelas yang ada, tetapi menulis sesuatu seperti

public Object getNode(String name) {
    return SomeModelClassHelper.getNode(this, name);
}

sekali per kelas seharusnya tidak menjadi masalah. Atau, Anda dapat menulis sesuatu seperti

public Object getNode(String name) {
    return getHelper(getClass()).getNode(this, name);
}

dalam superclass umum.


Anda dapat menggunakan refleksi alih-alih pemrosesan anotasi untuk pembuatan kode. Kelemahannya adalah Anda memerlukan kode Anda untuk dikompilasi sebelum Anda dapat menggunakan refleksi di atasnya. Ini berarti Anda tidak dapat mengandalkan kode yang dihasilkan di kelas model Anda, kecuali jika Anda menghasilkan beberapa bertopik.


Saya juga akan mempertimbangkan penggunaan refleksi secara langsung. Tentu, refleksi itu lambat, tetapi mengapa lambat? Itu karena harus melakukan semua hal yang perlu Anda lakukan, misalnya, aktifkan nama bidang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.