한 번만 사용되는 경우 문자열 상수를 정의해야합니까?


24

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 줄의 코드를 작성하는 것을 막아줍니다.

따라서 일회용으로 문자열 상수를 정의 해야하는 이유가 있습니까? 아니면 문자열을 직접 사용하는 것이 허용됩니까?


추신. 누군가 열거 형 대신을 제안하기 전에 프로토 타입을 프로토 타입으로 만들었지 만 열거 형 변환은 간단한 문자열 비교보다 15 배 느리므로 고려하지 않습니다.


결론 : 아래 답변은 문자열 상수 이상 으로이 질문의 범위를 확장 했으므로 두 가지 결론이 있습니다.

  • 그것은,이 시나리오에서 문자열을 직접이 아닌 문자열 상수를 사용하는 것이 아마 OK입니다
  • 문자열을 사용하지 않는 방법이 더 좋을 수도 있습니다.

따라서 문자열을 완전히 피하는 래퍼 기술을 시도해 보겠습니다. 불행히도 우리는 아직 Java 7을 사용하지 않기 때문에 string switch 문을 사용할 수 없습니다. 궁극적으로 우리에게 가장 좋은 대답은 각 기술을 시도하고 그 성능을 평가하는 것입니다. 실제로 하나의 기술이 더 빠르면 아름다움이나 컨벤션 준수 여부에 관계없이 기술을 선택할 것입니다.


3
당신은 if 문을 수동으로 1000 건반 키보드를 계획하지 않습니까?
JeffO

1
일부 언어에서는이 단순한 것이 얼마나 불쾌한 지 슬프다…
Jon Purdy

5
Java 7에서는 문자열을 switch레이블 로 사용할 수 있습니다 . if캐스케이드 대신 스위치를 사용하십시오 .
Reinstate Monica-M. Schröder

3
문자열을 열거 형 값으로 변환하면 열거 형 변환이 15 배 느려집니다! 열거 형을 직접 전달하고 동일한 유형의 다른 열거 형 값과 비교하십시오!
Neil

2
HashMap 같은 냄새가 해결책이 될 수 있습니다.
MarioDS

답변:


5

이 시도. 초기 반향은 확실히 비싸지 만, 여러 번 사용하려고한다면, 아마도 내가 생각하는 것, 이것은 당신이 제안하는 것보다 가장 좋은 해결책입니다. 나는 리플렉션을 사용하는 것을 좋아하지 않지만 리플렉션에 대한 대안이 마음에 들지 않을 때 그것을 사용한다는 것을 알게되었습니다. 이것이 팀의 많은 두통을 덜어 줄 것이라고 생각하지만 메소드 이름을 소문자로 전달해야합니다.

즉, get 메소드의 이름이 "getFullName ()"이므로 "name"을 전달하는 대신 "fullname"을 전달합니다.

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 ()이 있고 전체 이름으로 호출하는 경우 연락처의 getFullName ()의 이름이 바뀌어도 항상 작동 함) 그렇게하기 전에 컴파일 오류가 발생합니다).

public class ContactWrapper {
    private Contact contact;

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

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

이 솔루션은 jsf 데이터 테이블에서 사용할 단일 데이터 표현을 원하거나 jasper를 사용하여 보고서로 데이터를 내 보내야 할 때 (내 경험에서 복잡한 객체 접근자를 잘 처리하지 못함) 여러 번 저장했습니다. .


나는 .invoke()문자열 상수를 완전히 없애기 때문에 via라는 메소드를 사용하여 래퍼 객체의 아이디어를 좋아합니다 . 나는 맵을 설정하기 위해 런타임 리플렉션에 열중하지는 않지만 블록 getMethodMapping()에서 실행 static하면 시스템이 일단 실행되는 대신 시작시 발생하도록 괜찮을 것입니다.
gutch

@ gutch, 래퍼 패턴은 인터페이스 / 컨트롤러와 관련된 많은 문제를 해결하는 경향이 있기 때문에 자주 사용하는 패턴입니다. 인터페이스는 항상 래퍼를 사용하고 만족할 수 있으며 그 동안 컨트롤러를 뒤집을 수 있습니다. 인터페이스에서 사용할 수있는 데이터 만 있으면됩니다. 다시 한 번 강조하지만, 일반적으로 리플렉션을 좋아하지 않지만 웹 응용 프로그램 인 경우 클라이언트가 대기 시간을 보지 않기 때문에 시작시 웹 응용 프로그램을 사용하면 완전히 허용됩니다.
Neil

@Neil Apache Commons에서 BeanUtils를 사용하지 않는 이유는 무엇입니까? 또한 포함 된 개체를 지원합니다. 당신은 전체 데이터 구조 obj.attrA.attrB.attrN을
겪을

지도와 매핑하는 대신 @Annotations로 이동합니다. JPA와 같은 것이 있습니다. 특정 attr 또는 getter를 사용하여 컨트롤러 항목 (문자열)을 매핑하기위한 고유 한 주석을 정의합니다. Annotation으로 작업하는 것은 매우 쉽고 Java 1.6에서 사용할 수 있습니다 (제 생각에)
Laiv

5

가능하면 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);
        }
    }
}

나는 측정하지는 않았지만 스위치 문은 긴 비교 목록 대신 점프 테이블로 컴파일된다고 생각합니다. 이것은 더 빨라야합니다.

실제 질문과 관련하여 : 한 번만 사용 하면 상수로 만들 필요가 없습니다. 그러나 상수는 문서화 되어 Javadoc에 표시 될 수 있습니다 . 사소하지 않은 문자열 값에 중요 할 수 있습니다.


2
점프 테이블에 대하여. 문자열 스위치는 스위치로 대체되며, 먼저 해시 코드를 기반으로하며 (동일한 해시 코드로 모든 상수에 대해 등식이 검사 됨) 분기 인덱스를 선택하고 분기 인덱스에서 두 번째 스위치를 선택하고 원래 분기 코드를 선택합니다. 후자는 분기 테이블에 명확하게 적합하며 전자는 해시 함수의 분포로 인한 것이 아닙니다. 따라서 모든 성능 이점은 해시 기반 구현으로 인한 것입니다.
scarfridge

아주 좋은 지적입니다. 그것이 잘 수행한다면 그것은 단지 이것을 위해 Java 7로 옮겨 갈 가치가있을 것입니다 ...
gutch

4

이것을 유지하려면 (사소한 종류의 변경은하지 마십시오 ) 실제로 주석 기반 코드 생성 ( CGLib 를 통해 )을 사용하거나 모든 코드를 작성하는 스크립트를 사용하는 것이 좋습니다. 고려중인 접근 방식으로 발생할 수있는 오타 및 오류 수를 상상해보십시오.


기존 메소드에 주석을 달 것을 고려했지만 일부 맵핑 object.getAddress().getCountry()은 주석으로 표현하기 어려운 여러 오브젝트 (예 : "국가"맵핑 )를 통과합니다 . if / else 문자열 비교는 쉽지 않지만 빠르고 유연하며 이해하기 쉽고 단위 테스트가 쉽습니다.
gutch

1
오타 및 오류 가능성에 대해 옳습니다. 내 유일한 방어는 단위 테스트입니다. 물론 그것은 더 많은 코드를 의미합니다 ...
gutch

2

나는 여전히 클래스 상단에 정의 된 상수를 사용합니다. 나중에 변경 될 수있는 내용 (필요한 경우)을 더 쉽게 볼 수 있으므로 코드 유지 관리가 더 쉽습니다. 예를 들어, "first_name"될 수있는 "firstName"몇 가지 나중에.


그러나이 코드가 자동으로 생성되고 상수가 다른 곳에서 사용되지 않으면 중요하지 않습니다 (OP는 100 클래스 에서이 작업을 수행해야한다고 말합니다).
NoChance

5
여기서는 "유지 보수성"각도를 보지 못하지만 어느 경우 든 한 곳에서 "first_name"을 "givenName"으로 한 번 변경했지만 명명 된 상수의 경우 이제는 "first_name"이라는 지저분한 변수가 남습니다. 문자열 "givenName"으로 변경하여 두 위치에서 세 가지 변경을 수행해야합니다.
James Anderson

1
올바른 IDE를 사용하면 이러한 변경 사항이 간단합니다. 내가 옹호하는 것은 클래스 상단에 상수를 선언하는 데 시간이 걸리고 클래스의 나머지 코드를 읽을 필요가 없기 때문에 이러한 변경을 수행 할 위치 가 더 분명하다는 것입니다. 변경하십시오.
Bernard

그러나 if 문을 읽을 때 돌아가서 상수에 포함 된 문자열이 포함되어 있는지 확인해야합니다.
James Anderson

1
아마도, 그러나 이것이 내 상수의 이름을 잘 지정하는 이유입니다.
Bernard

1

네이밍이 일관된 경우 (일명 "some_whatever"항상에 매핑 됨 getSomeWhatever()) 리플렉션을 사용하여 get 메소드를 결정하고 실행할 수 있습니다.


getSome_whatever ()가 더 좋습니다. 낙타 케이스를 깨뜨릴 수 있지만 반사가 제대로 작동하는지 확인하는 것이 훨씬 중요합니다. 또한 "도대체 왜 그렇게 했는가 .. 잠깐만. 잠깐만! 조지가 그 방법의 이름을 바꾸지 마라!"
Neil

0

주석이 없어도 주석 처리가 해결책이 될 수 있습니다. 그것은 당신을 위해 모든 지루한 코드를 생성 할 수있는 것입니다. 단점은 N 개의 모델 클래스에 대해 N 개의 생성 된 클래스를 얻게된다는 것입니다. 기존 클래스에는 아무것도 추가 할 수 없지만 다음과 같은 내용을 작성하십시오.

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

수업 당 한 번 문제가되지 않아야합니다. 또는 다음과 같이 쓸 수 있습니다.

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

공통 슈퍼 클래스에서.


코드 생성을 위해 주석 처리 대신 리플렉션을 사용할 수 있습니다. 단점은 리플렉션을 사용하기 전에 컴파일하기 위해 코드가 필요하다는 것입니다. 이는 일부 스텁을 생성하지 않으면 모델 클래스에서 생성 된 코드에 의존 할 수 없음을 의미합니다.


나는 또한 리플렉션의 직접적인 사용을 고려할 것입니다. 물론, 반사는 느리지 만 왜 느립니까? 필드 이름을 켜는 등 필요한 모든 작업을 수행해야하기 때문입니다.

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