[java] 확인되지 않은 캐스트 경고는 어떻게 해결합니까?

이클립스는 나에게 다음과 같은 형태의 경고를 주었다.

유형 안전 : Object에서 HashMap으로 캐스트되지 않은 캐스트

이것은 API를 호출하여 Object를 반환하는 제어권이 없습니다.

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
  HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey");
  return theHash;
}

이론적으로 적어도 잠재적 인 코드 문제를 나타 내기 때문에 가능하면 Eclipse 경고를 피하고 싶습니다. 그래도 이것을 제거하는 좋은 방법을 찾지 못했습니다. 메소드 자체와 관련된 단일 행을 추출하고 @SuppressWarnings("unchecked")해당 메소드에 추가 하여 경고를 무시하는 코드 블록을 갖는 영향을 제한 할 수 있습니다. 더 나은 옵션이 있습니까? Eclipse에서 이러한 경고를 끄고 싶지 않습니다.

코드에 오기 전에 더 간단했지만 여전히 경고를 유발했습니다.

HashMap getItems(javax.servlet.http.HttpSession session) {
  HashMap theHash = (HashMap)session.getAttribute("attributeKey");
  return theHash;
}

해시를 사용하려고 할 때 문제가 발생하여 경고가 표시됩니다.

HashMap items = getItems(session);
items.put("this", "that");

Type safety: The method put(Object, Object) belongs to the raw type HashMap.  References to generic type HashMap<K,V> should be parameterized.



답변

물론 명백한 대답은 확인되지 않은 캐스트를 수행하지 않는 것입니다.

절대적으로 필요한 경우 적어도 @SuppressWarnings주석 의 범위를 제한하십시오 . Javadocs 에 따르면 로컬 변수를 사용할 수 있습니다. 이런 식으로 전체 방법에도 영향을 미치지 않습니다.

예:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

Map실제로 일반 매개 변수를 가져야 하는지 여부를 판별 할 방법이 없습니다 <String, String>. 매개 변수가 무엇인지 미리 알고 있어야합니다 (또는 얻을 때 알 수 있습니다 ClassCastException). 컴파일러가 안전한지 여부를 알 수 없기 때문에 코드가 경고를 생성하는 이유입니다.


답변

불행히도 여기에는 훌륭한 옵션이 없습니다. 이 모든 목표는 형식 안전성을 유지하는 것입니다. ” Java Generics “는 일반화되지 않은 레거시 라이브러리를 처리하기위한 솔루션을 제공하며 특히 8.2 절에서 “빈 루프 기술”이라고합니다. 기본적으로 안전하지 않은 캐스트를 만들고 경고를 억제하십시오. 그런 다음 다음과 같이 맵을 반복하십시오.

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

예기치 않은 유형이 발생하면 runtime을 얻지 ClassCastException만 최소한 문제의 원인과 가까운 곳에서 발생합니다.


답변

와; 내 질문에 대한 답을 찾은 것 같습니다. 그만한 가치가 있는지 잘 모르겠습니다! 🙂

문제는 캐스트가 확인되지 않는다는 것입니다. 따라서 직접 확인해야합니다. 런타임시 매개 변수화 된 유형 정보를 사용할 수없고 컴파일시 지워 지므로 instanceof를 사용하여 매개 변수화 된 유형을 확인할 수는 없습니다.

그러나 instanceof를 사용하여 해시의 모든 항목을 검사 할 수 있으며 그렇게하면 형식이 안전한 새 해시를 구성 할 수 있습니다. 그리고 경고를 유발하지 않습니다.

mmyers와 Esko Luontola 덕분에 원래 여기에 작성한 코드를 매개 변수화하여 어딘가에 유틸리티 클래스로 싸서 매개 변수화 된 HashMap에 사용할 수 있습니다. 더 잘 이해하고 제네릭에 익숙하지 않은 경우이 답변의 편집 기록을 보는 것이 좋습니다.

public static <K, V> HashMap<K, V> castHash(HashMap input,
                                            Class<K> keyClass,
                                            Class<V> valueClass) {
  HashMap<K, V> output = new HashMap<K, V>();
  if (input == null)
      return output;
  for (Object key: input.keySet().toArray()) {
    if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
        Object value = input.get(key);
        if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
            K k = keyClass.cast(key);
            V v = valueClass.cast(value);
            output.put(k, v);
        } else {
            throw new AssertionError(
                "Cannot cast to HashMap<"+ keyClass.getSimpleName()
                +", "+ valueClass.getSimpleName() +">"
                +", value "+ value +" is not a "+ valueClass.getSimpleName()
            );
        }
    } else {
        throw new AssertionError(
            "Cannot cast to HashMap<"+ keyClass.getSimpleName()
            +", "+ valueClass.getSimpleName() +">"
            +", key "+ key +" is not a " + keyClass.getSimpleName()
        );
    }
  }
  return output;
}

많은 보상이 필요합니다. 아마도 그것을 사용할지 잘 모르겠습니다. 사람들이 가치가 있다고 생각하는지 여부에 대한 의견을 보내 주셔서 감사합니다. 또한 개선 제안에 감사드립니다 : Throw AssertionErrors 외에 더 나은 것이 있습니까? 내가 더 잘 던질 수있는 것이 있습니까? 확인 된 예외를 만들어야합니까?


답변

Eclipse 환경 설정에서 Java-> 컴파일러-> 오류 / 경고-> 일반 유형으로 이동하여 확인란을 선택하십시오 Ignore unavoidable generic type problems.

이것은 질문의 의도, 즉

Eclipse 경고를 피하고 싶습니다 …

정신이 아니라면.


답변

다음과 같은 유틸리티 클래스를 작성하고이를 사용하여 확인되지 않은 경고를 억제 할 수 있습니다.

public class Objects {

    /**
     * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

다음과 같이 사용할 수 있습니다.

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
      return uncheckedCast(session.getAttribute("attributeKey"));
}

이에 대한 자세한 내용은 다음과 같습니다.
http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html


답변

이 물건은 어렵다, 그러나 나의 현재 생각은 여기있다 :

API가 Object를 반환하면 할 수있는 작업이 없습니다. Java가 ClassCastExceptions를 던지거나 각 요소를 직접 확인하여 Assertions 또는 IllegalArgumentExceptions 또는 이와 유사한 것을 던질 수 있지만 이러한 런타임 검사는 모두 동일합니다. 런타임에 수행하는 작업에 관계없이 컴파일 시간을 검사하지 않은 캐스트 를 억제 해야합니다.

API를 반환해야하는 것을 “알고”일반적으로 API가 작동한다고 가정하기 때문에 블라인드 캐스트를 사용하고 JVM이 런타임 검사를 수행하도록하는 것이 좋습니다. 필요한 경우 캐스트 위의 모든 곳에 제네릭을 사용하십시오. 여전히 단일 블라인드 캐스트가 있기 때문에 실제로는 아무것도 구매하지 않지만 적어도 거기에서 제네릭을 사용할 수 있으므로 JVM은 다른 코드 조각에서 블라인드 캐스트를 피할 수 있습니다.

이 특별한 경우에, 아마도 당신은 SetAttribute에 대한 호출을 볼 수 있고 타입이 들어오고있는 것을 볼 수 있습니다. SetAttribute를 참조하는 주석을 추가하고 완료하십시오.


답변

다음은 다른 답변에서 언급 한 두 가지 전략을 사용 하여 “확인되지 않은 캐스트”경고를 피하는 단축 된 예입니다 .

  1. 런타임에 관심있는 유형의 클래스를 매개 변수로 전달하십시오 ( Class<T> inputElementClazz). 그런 다음 다음을 사용할 수 있습니다.inputElementClazz.cast(anyObject);

  2. 콜렉션의 유형 캐스팅에는 와일드 카드? 레거시 코드 ( Collection<?> unknownTypeCollection) 에서 어떤 종류의 객체를 기대할 수 있는지 실제로 알지 못하도록 일반 유형 T 대신 . 결국, 이것은 “체크되지 않은 캐스트”경고가 우리에게 말하고자하는 Collection<T>Collection<?>입니다. 꼭 필요한 경우 알려진 유형의 컬렉션을 계속 만들 수 있습니다 ( Collection<T> knownTypeCollection).

아래 예제에서 인터페이스 된 레거시 코드에는 StructuredViewer에 “input”속성이 있습니다 (StructuredViewer는 트리 또는 테이블 위젯, “input”은 그 뒤에있는 데이터 모델 임). 이 “입력”은 모든 종류의 Java 콜렉션 일 수 있습니다.

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // legacy code returns an Object from getFirstElement,
    // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // legacy code returns an object from getInput, so we deal with it as a Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // for some operations we do not even need a collection with known types
    unknownTypeCollection.remove(firstElement);

    // nothing prevents us from building a Collection of a known type, should we really need one
    Collection<T> knownTypeCollection = new ArrayList<T>();
    for (Object object : unknownTypeCollection) {
        T aT = inputElementClazz.cast(object);
        knownTypeCollection.add(aT);
        System.out.println(aT.getClass());
    }

    structuredViewer.refresh();
}

당연히, 위의 코드는 잘못된 데이터 유형으로 레거시 코드를 사용하는 경우 (예를 들어 배열을 Java Collection 대신 StructuredViewer의 “입력”으로 설정 한 경우) 런타임 오류를 발생시킬 수 있습니다.

메소드 호출의 예 :

dragFinishedStrategy.dragFinished(viewer, Product.class);