web-dev-qa-db-ja.com

文字列定数は、一度しか使用されない場合に定義する必要がありますか?

XPathを使用してアプリケーションのデータモデルにアクセスできるようにするJaxen(JavaのXPathライブラリ)のアダプターを実装しています。

これは、(Jaxenから渡された)文字列をデータモデルの要素にマップするクラスを実装することによって行われます。合計で1000を超える文字列比較を行う約100のクラスが必要になると予測しています。

これを行う最善の方法は、各文字列を定数として定義するのではなく、文字列をコードに直接書き込んだ単純なif/elseステートメントであると思います。例えば:

_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();
    ...
_

ただし、文字列値をコードに直接埋め込むのではなく、代わりに文字列定数を作成する必要があると常に教えられていました。これは次のようになります。

_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();
    ...
_

この場合、それは悪い考えだと思います。定数は、getNode()メソッドで一度だけ使用されます。文字列を直接使用すると、定数を使用するのと同じくらい簡単に読み、理解でき、少なくとも1000行のコードを書く手間が省けます。

では、1回の使用で文字列定数を定義する理由はありますか?または、文字列を直接使用することは受け入れられますか?


PS。代わりに列挙型の使用を提案する前に、プロトタイプを作成しましたが、列挙型変換は単純な文字列比較より15倍遅いため、考慮されていません。


結論:以下の回答により、この質問の範囲が文字列定数だけにとどまらず、2つの結論が得られました。

  • このシナリオでは、文字列定数ではなく文字列を直接使用しても問題ありませんbut
  • 文字列をまったく使用しないようにする方法がいくつかあります。

そこで、文字列を完全に回避するラッパー手法を試します。残念ながら、Java 7にまだ対応していないため、string switchステートメントを使用できません。ただし、最終的には、各手法を試し、そのパフォーマンスを評価することが最善の答えだと思います。現実は、1つの手法が明らかに高速である場合、その美しさや慣習の順守に関係なく、おそらくそれを選択するでしょう。

26
gutch

これを試して。最初のリフレクションは確かにコストがかかりますが、何度も使用する場合(おそらくそうなると思います)、これは間違いなく提案しているものより優れたソリューションです。リフレクションを使用するのは好きではありませんが、リフレクションの代わりが気に入らないときに使用していることに気づきました。これにより、チームの頭痛が大幅に軽減されると思いますが、メソッド名(小文字)を渡す必要があります。

つまり、「name」を渡すのではなく、「fullname」を渡すことになります。これは、getメソッドの名前が「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;
}

連絡先のメンバーに含まれるデータにアクセスする必要がある場合は、必要な情報にアクセスするためのすべてのメソッドを持つ連絡先のラッパークラスを作成することを検討してください。これは、アクセスフィールドの名前が常に同じであることを保証するのにも役立ちます(つまり、ラッパークラスにgetFullName()があり、fullnameで呼び出した場合、連絡先のgetFullName()の名前が変更されていても常に機能します-それはそれを行う前にコンパイルエラーが発生します)。

public class ContactWrapper {
    private Contact contact;

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

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

このソリューションにより、jsfデータテーブルで使用する単一のデータ表現が必要な場合や、jasperを使用してそのデータをレポートにエクスポートする必要がある場合(これは、私の経験では複雑なオブジェクトアクセサーをうまく処理しません) 。

5
Neil

可能な限りJava 7を使用すると、switchステートメントで文字列を使用できます。

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

私は測定していませんが、switchステートメントは長い比較リストではなくジャンプテーブルにコンパイルされると思います。これはさらに高速になるはずです。

あなたの実際の質問について:あなたがそれを使うだけなら一度あなたはそれを定数にする必要はありません。ただし、定数はdocumentedであり、Javadocに表示される可能性があることを考慮してください。これは、重要な文字列値にとって重要な場合があります。

6
user1249

これを維持する場合(これまでにない重要な変更を加える場合)は、実際に何らかのアノテーション駆動型のコード生成(おそらく CGLib を使用)を使用することを検討するかもしれません。すべてのコードを記述します。あなたが検討しているアプローチで忍び込むことができるタイプミスとエラーの数を想像してください...

4

クラスの先頭で定義されている定数を使用します。 (必要に応じて)後で何を変更できるかを確認しやすくなるため、コードの保守が容易になります。たとえば、"first_name"は後で"firstName"になる可能性があります。

2
Bernard

名前が一貫している場合(別名_"some_whatever"_は常にgetSomeWhatever()にマップされます)、リフレクションを使用してgetメソッドを決定および実行できます。

1
scarfridge

おそらく、注釈がなくても、注釈処理が解決策になる可能性があります。それはあなたのためにすべての退屈なコードを生成できるものです。欠点は、N個のモデルクラスに対してN個の生成されたクラスを取得することです。また、既存のクラスには何も追加できませんが、次のように記述します

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

クラスごとに1回は問題になりません。あるいは、次のように書くこともできます

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

共通のスーパークラスで。


コード生成には、アノテーション処理の代わりにリフレクションを使用できます。欠点は、リフレクションを使用する前にコードをコンパイルする必要があることです。これは、スタブを生成しない限り、モデルクラスで生成されたコードに依存できないことを意味します。


リフレクションを直接使用することも検討します。確かに、反射は遅いですが、なぜ遅いのですか?これは、フィールド名をオンにするなど、必要なすべてのことを行う必要があるためです。

0
maaartinus