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


611

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

유형 안전 : 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.

이와 같은 HttpSession을 사용하는 경우 주제에 대한 Brian Goetz의 기사를 확인 하십시오
Tom Hawtin-tackline

확인되지 않은 캐스트를 피할 수없는 경우 캐스트를 유형을 논리적으로 나타내는 무언가 ( enum또는 인스턴스 와 같이)와 밀접하게 연결하여 Class<T>눈에 띄고 안전하다는 것을 알 수 있습니다.
Philip Guin

4
관련 /
속력


또한 문제의 코드가 포함 된 메서드 수준에서만 @SuppressWarnings ( "unchecked") 만 추가 할 수 있다는 것을 알았습니다. 그래서 나는 이것을 해야하는 루틴으로 코드를 나 code습니다. 나는 항상 당신이 문제의 줄 바로 위에서 이것을 할 수 있다고 생각했습니다.
JGFMK

답변:


557

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

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

예:

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

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


112
지역 변수를 사용할 수 있음을 지적 +1. Eclipse는 전체 메소드에 추가하는 기능 만 제공합니다 ...
thSoft

17
Eclipse 3.7 (Indigo)은 확인되지 않은 로컬 변수를 추가 할 수 있도록 지원합니다.
sweetfa

78
컴파일러가 캐스트가 안전하다는 것을 알지 못하기 때문에 경고가 아닙니다. 예를 들어 String s = (String) new Object() ;, 컴파일러가 캐스트가 안전하다는 것을 모르더라도 경고는 표시되지 않습니다. 경고는 컴파일러 (a)가 캐스트가 안전하다는 것을 알지 못하고 (b) 캐스트 시점에서 완전한 런타임 검사를 생성하지 않기 때문입니다. 이 검사는 Hashmap이지만 검사는하지 않습니다 HashMap<String,String>.
Theodore Norvell

9
슬프게도, 캐스트와 경고가 대입 에 대한 것이더라도 주석은 변수 선언으로 이동해야합니다. 따라서 선언과 대입이 다른 위치에있는 경우 (예 : 각각 'try'블록의 외부와 내부) 이제 Eclipse는 두 개의 경고, 즉 원래의 확인되지 않은 캐스트와 새로운 "불필요한 주석"진단을 생성합니다.
Ti Strga

6
실제 캐스트와 다른 행에 다른 범위에있을 수있는 로컬 변수 선언에 주석이 포함되어야하는 주석의 해결 방법은 캐스트 범위 내에 로컬 변수를 작성하여 동일한 행에서 캐스트를 수행하는 것입니다. 선언으로. 그런 다음이 변수를 다른 범위에있는 실제 변수에 지정하십시오. 이것은 주석을 여기에도 적용 할 수 없으므로 인스턴스 변수로 캐스트 할 때 경고를 억제하는 데 사용한 방법입니다.
Jeff Lockhart

168

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

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

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


6
여러 가지 이유로 skiphoppy가 제공하는 것보다 훨씬 더 나은 답변 : 1)이 코드는 훨씬 짧습니다. 2)이 코드는 실제로 예상대로 ClassCastException을 발생시킵니다. 3)이 코드는 소스 맵의 전체 사본을 수행하지 않습니다. 4) 루프는 어설트에 사용되는 별도의 방법으로 쉽게 감쌀 수 있으므로 생산 코드의 성능 저하를 쉽게 제거 할 수 있습니다.
Stijn de Witt

6
Java 컴파일러 또는 JIT 컴파일러가이 코드의 결과가 사용되고 있지 않다고 결정하고이를 컴파일하지 않음으로써 "최적화"할 가능성이 없습니까?
RenniePet

1
잠재적으로 예외를 던질 수 있다면 실제로 죽은 코드는 아닙니다. 오늘 사용중인 JIT 컴파일러에 대해 충분히 알지 못하여 아무도 엉망이되지 않을 것이라고 확신하지만, 그렇게하지 말아야한다고 확신합니다.
GrandOpener

3
동일한 맵을 계속 사용하고 있어도 유형 안전을 보장하지는 않습니다. 원래 Map <Object, Object>로 정의되었을 수 있습니다. 문자열과 숫자를 입력 한 다음 부울을 추가하면 나중에이 코드의 사용자는 혼란스럽고 놀라움을 추적하기가 어렵습니다. 유형 안전을 보장하는 유일한 방법은 요청 된 유형으로 새 맵에 복사하여 허용되는 내용을 보장하는 것입니다.
user2219808

112

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

문제는 캐스트가 확인되지 않는다는 것입니다. 따라서 직접 확인해야합니다. 런타임시 매개 변수화 된 유형 정보를 사용할 수없고 컴파일시 지워 지므로 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 외에 더 나은 것이 있습니까? 내가 더 잘 던질 수있는 것이 있습니까? 확인 된 예외를 만들어야합니까?


68
이 물건은 혼란 스럽지만, 당신이 한 모든 일은 AssertionErrors에 대해 ClassCastExceptions으로 거래 된 것 같습니다.
더스틴 게츠

59
야, 그건 그만한 가치가 없어! 돌아와서 그 엉망으로 코드를 수정 해야하는 가난한 수액을 상상해보십시오. 나는 경고를 억제하는 것을 좋아하지 않지만 여기서는 그보다 덜 악하다고 생각합니다.
Craig B

69
그것은 추악하고 혼란스럽고 혼란 스러울 뿐이 아닙니다. 컬렉션의 모든 요소를 ​​반복하면 캐스트가 O (1)에서 O (n) 연산으로 바뀝니다. 이것은 결코 예상 할 수없는 일이며 끔찍한 미스터리 속도 저하로 쉽게 변할 수 있습니다.
Dan은 불을 피우고있다

22
@ DanNeely 당신이 맞습니다. 일반적으로 아무도 이것을해서는 안됩니다.
skiphoppy

4
일부 의견 ... 메소드 서명은 지독한 것을 "전송"하지 않기 때문에 기존 맵을 새 맵에 복사하기 때문에 잘못되었습니다. 또한 모든 맵을 수락하도록 리팩토링 될 수 있으며 HashMap 자체에 의존하지 않습니다 (예 : 내부 유형이 HashMap 인 경우에도 메소드 서명에서 Map을 리턴하고 Map을 리턴 함). 어설 션 오류를 발생시키지 않으면 지정된 맵에 현재 올바른 유형의 맵이 있습니다. 일반 유형으로 새 맵을 작성하는 것은 여전히 ​​원시하고 무엇이든 넣을 수 있기 때문에 의미가 없습니다.
MetroidFan2002

51

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

이것은 질문의 의도, 즉

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

정신이 아니라면.


1
아, 고마워 :)에 " uses unchecked or unsafe operations."오류가 발생 javac했지만 추가하면 @SuppressWarnings("unchecked")이클립스가 불행 해져서 억제가 불필요하다고 주장했다. 이 상자를 선택 해제 하면 Eclipse가 javac작동하고 동일하게 작동합니다. 코드에서 경고를 명시 적으로 억제하는 것이 Eclipse 내부의 모든 곳에서 경고를 억제하는 것보다 훨씬 명확합니다.
dimo414

26

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

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


18
downvoting하지 않지만 래퍼는 경고를 억제하는 것 이상을 정확하게 추가하지 않습니다.
Dustin Getz

3
이 솔루션은 귀중한 코드 라인을 낭비하지 않기 때문에 +1입니다.
Tino

1
@ErikE 너무 많이. 낭비되는 모든 라인에 공간을 제공하기 위해 훨씬 더 비싸고 고해상도 모니터, 모든 더 큰 모니터를 배치 할 수있는 더 큰 책상, 더 큰 책상을 넣을 수있는 더 큰 공간 및 통찰력있는 보스 ..
Tino

1
@ErikE 스크롤바 vi? 농담 해?
티노

21

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

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

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

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


16

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

  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);

13

HTTP 세션 세계에서는 API가 그런 식으로 작성되기 때문에 캐스트를 실제로 피할 수 없습니다 (테이크 앤 리턴 만 Object).

약간의 작업만으로도 확인되지 않은 캐스트를 쉽게 피할 수 있습니다. 이것은 ClassCastException오류가 발생 하면 바로 거기에 권리를 주는 전통적인 캐스트로 바뀔 것임을 의미합니다 ). 검사되지 않은 예외 CCE는 캐스트 시점 대신 나중에 어느 시점에서든 변할 수 있습니다 (따라서 별도의 경고 인 이유).

HashMap을 전용 클래스로 바꾸십시오.

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Attributes extends AbstractMap<String, String> {
    final Map<String, String> content = new HashMap<String, String>();

    @Override
    public Set<Map.Entry<String, String>> entrySet() {
        return content.entrySet();
    }

    @Override
    public Set<String> keySet() {
        return content.keySet();
    }

    @Override
    public Collection<String> values() {
        return content.values();
    }

    @Override
    public String put(final String key, final String value) {
        return content.put(key, value);
    }
}

그런 다음 대신 해당 클래스로 캐스트하면 Map<String,String>코드를 작성하는 정확한 위치에서 모든 것이 검사됩니다. ClassCastExceptions나중에 예기치 않은 일이 없습니다 .


이것은 정말 유용한 답변입니다.
GPrathap

10

Android Studio에서 검사를 비활성화하려면 다음을 사용할 수 있습니다.

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();

2
이것은 IntelliJ IDE에서도 작동합니다
neXus

8

이 특별한 경우에는 직접 HttpSession에 Maps를 저장하지 않고 Map (클래스의 구현 세부 사항)을 포함하는 내 클래스의 인스턴스를 직접 저장합니다. 그런 다음 맵의 요소가 올바른 유형인지 확인할 수 있습니다.

그러나 어쨌든 맵의 내용이 올바른 유형인지 확인하려면 다음과 같은 코드를 사용할 수 있습니다.

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.put("b", 2);
    Object obj = map;

    Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
    Map<String, String> error = safeCastMap(obj, String.class, String.class);
}

@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
    checkMap(map);
    checkMapContents(keyType, valueType, (Map<?, ?>) map);
    return (Map<K, V>) map;
}

private static void checkMap(Object map) {
    checkType(Map.class, map);
}

private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        checkType(keyType, entry.getKey());
        checkType(valueType, entry.getValue());
    }
}

private static <K> void checkType(Class<K> expectedType, Object obj) {
    if (!expectedType.isInstance(obj)) {
        throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
    }
}

1
대박; 나는 그것을 매개 변수화하기 위해 내 대답과 결합 할 수 있다고 생각하고 경고를 완전히 억제하지 않아도됩니다!
skiphoppy

1
런타임 점검으로 안전하게하기위한 아마도 가장 좋은 레시피 (이해하기 쉽고 유지하기 쉬운 +1)
Tino

8

Esko Luontola가 위의 답변에서 Objects.Unchecked 유틸리티 기능을 사용하면 프로그램 혼란을 피할 수 있습니다.

전체 메소드에서 SuppressWarnings를 원하지 않으면 Java는 강제로 로컬에 배치합니다. 멤버에 캐스트가 필요한 경우 다음과 같은 코드가 발생할 수 있습니다.

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

이 유틸리티를 사용하는 것이 훨씬 깔끔하며 여전히 수행중인 작업이 분명합니다.

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

참고 : 때로는 경고가 실제로 다음과 같은 잘못하고 있음을 의미한다고 덧붙이는 것이 중요하다고 생각합니다.

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

컴파일러가 알려주는 것은 런타임 에이 캐스트를 확인하지 않으므로 일반 컨테이너의 데이터에 액세스하려고 할 때까지 런타임 오류가 발생하지 않는다는 것입니다.


5

경고 억제는 해결책이 아닙니다. 한 문장에서 두 가지 레벨 캐스팅을 수행해서는 안됩니다.

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {

    // first, cast the returned Object to generic HashMap<?,?>
    HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");

    // next, cast every entry of the HashMap to the required type <String, String>
    HashMap<String, String> returingHash = new HashMap<>();
    for (Entry<?, ?> entry : theHash.entrySet()) {
        returingHash.put((String) entry.getKey(), (String) entry.getValue());
    }
    return returingHash;
}

1
그의 5 살짜리 질문? 그렇게 많은 일을해야합니까? Java가 유형을 지울 경우 두 번째 해시 맵은 런타임시 첫 번째와 동일해야합니다. 항목을 반복하고 모든 문자열 인스턴스임을 확인하면 더 효율적이며 사본을 피할 수 있다고 생각합니다. 또는 TBH, 사용중인 서블릿 JAR의 소스를 검사하고 문자열 만 넣는 지 확인하십시오.
Rup

1
오늘날까지 나는 프로젝트 에서이 경고를보고 있습니다. 그의 문제는 유형을 확인하는 것이 아니라 캐스트되지 않은 맵에 "넣어"서 발생하는 경고였습니다.
abbas

2

코드를 게시하면 빠른 추측으로 말할 수 있지만 다음 줄을 따라 무언가를했을 수 있습니다.

HashMap<String, Object> test = new HashMap();

당신이해야 할 때 경고를 생성합니다

HashMap<String, Object> test = new HashMap<String, Object>();

그것은 볼 가치가있을 수 있습니다

자바 프로그래밍 언어의 제네릭

해야 할 일에 익숙하지 않은 경우.


1
불행히도 그렇게 쉬운 상황은 아닙니다. 코드가 추가되었습니다.
skiphoppy

1
나는 약간 다른 문제에 대한 답을 찾기 위해 여기에 왔습니다. 감사!
staticsan

2

나는 질문을 오해했을 수도 있지만 (예와 주변 라인이 좋을 것입니다) 항상 적절한 인터페이스 (및 Java5 +)를 사용하지 않는 이유는 무엇입니까? 나는 당신이 HashMap대신에 캐스트하고 싶은 이유가 없습니다 Map<KeyType,ValueType>. 사실, 내가 상상할 수 있는 에 변수의 유형을 설정하는 이유 HashMap대신을 Map.

그리고 왜 소스는 Object? 레거시 콜렉션의 매개 변수 유형입니까? 그렇다면 제네릭을 사용하고 원하는 유형을 지정하십시오.


2
이 경우 맵으로 전환해도 아무런 변화가 없지만 프로그래밍 팁 덕분에 일부 작업 방식이 더 좋을 수 있습니다. 객체의 소스는 내가 제어 할 수없는 API입니다 (코드 추가).
skiphoppy

2

제네릭을 지원하지 않는 API를 사용해야하는 경우 가능한 한 적은 수의 줄로 래퍼 루틴에서 해당 호출을 분리하려고 시도합니다. 그런 다음 SuppressWarnings 주석을 사용하고 동시에 유형 안전성 캐스트를 추가합니다.

이것은 가능한 한 깔끔하게 유지하기위한 개인적인 취향 일뿐입니다.


2

이것을 가져 가십시오. 새 HashMap을 만드는 것보다 훨씬 빠릅니다. 이미 하나이지만 각 요소가 유형에 대해 검사되므로 여전히 안전합니다 ...

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
       assert input instanceof Map : input;

       for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
           assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
           assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
       }

       if (input instanceof HashMap)
           return (HashMap<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }

key.isAssignableFrom(e.getKey().getClass())다음과 같이 작성할 수 있습니다key.isInstance(e.getKey())
user102008

1

캐스트하기 전에 입력하십시오.

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

그리고 묻는 사람에게는 유형이 확실하지 않은 객체를받는 것이 일반적입니다. 많은 레거시 "SOA"구현은 항상 신뢰할 수없는 다양한 개체를 전달합니다. (공포!)

편집 포스터의 업데이트와 일치하도록 예제 코드를 한 번 변경했으며 일부 의견을 따르면 instanceof가 제네릭과 잘 어울리지 않는다는 것을 알 수 있습니다. 그러나 외부 객체의 유효성을 검사하기 위해 검사를 변경하면 명령 줄 컴파일러에서 잘 작동하는 것 같습니다. 수정 된 예가 게시되었습니다.


8
불행하게도 제네릭은이를 불가능하게 만듭니다. 그것은 단지 HashMap이 아니라 유형 정보가있는 HashMap입니다. 해당 정보를 제거하면 경고를 다른 곳으로 푸시합니다.
skiphoppy

1

컴퓨터 과학의 거의 모든 문제는 간접적 인 수준 *을 추가하여 해결할 수 있습니다.

따라서보다 높은 수준의 제네릭이 아닌 객체를 소개하십시오 Map. 문맥이 없으면 매우 설득력이 없어 보이지만 어쨌든 :

public final class Items implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

* 너무 많은 수준의 간접 지정을 제외하고.


1
이 인용문은 David Wheeler 교수의 말에 기인합니다. en.wikipedia.org/wiki/…
Stephen C

1

equals()작업을 재정의 할 때 이것을 처리하는 한 가지 방법이 있습니다.

public abstract class Section<T extends Section> extends Element<Section<T>> {
    Object attr1;

    /**
    * Compare one section object to another.
    *
    * @param obj the object being compared with this section object
    * @return true if this section and the other section are of the same
    * sub-class of section and their component fields are the same, false
    * otherwise
    */       
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            // this exists, but obj doesn't, so they can't be equal!
            return false;
        }

        // prepare to cast...
        Section<?> other;

        if (getClass() != obj.getClass()) {
            // looks like we're comparing apples to oranges
            return false;
        } else {
            // it must be safe to make that cast!
            other = (Section<?>) obj;
        }

        // and then I compare attributes between this and other
        return this.attr1.equals(other.attr1);
    }
}

이것은 Java 8에서 작동하는 것 같습니다 (조차도 컴파일 됨 -Xlint:unchecked)


0

session.getAttribute ()에 의해 반환 된 형식이 HashMap 인 경우 해당 형식으로 형식 변환 할 수 없지만 일반 HashMap 만 검사해야합니다.

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

Eclipse는 경고를 놀라게 할 것입니다. 물론 이것은 디버깅하기 어려운 런타임 오류로 이어질 수 있습니다. 이 접근 방식은 운영에 중요한 컨텍스트가 아닌 컨텍스트에서만 사용합니다.


0

두 가지 방법, 하나는 태그를 완전히 피하는 방법이고 다른 하나는 장난 스럽지만 훌륭한 유틸리티 방법을 사용합니다.
문제는 사전에 생성 된 컬렉션입니다 ...
경험에 따르면 "한 번에 한 가지씩 객체 캐스트"-일반화 된 세계에서 원시 클래스를 사용하려고 할 때의 의미는 이 Map <?,?>에 있으며 (실제로 JVM이 맵이 아니라는 것을 알 수 있습니다!), 당신이 그것을 캐스팅 할 수 없다고 생각할 때 분명합니다. Map <String,?> map2가있는 경우 HashSet <String> keys = (HashSet <String>) map2.keySet ()은 컴파일러에 대한 "신념의 행동"임에도 불구하고 경고를 표시하지 않습니다. 그것은 TreeSet으로 판명 될 수 있습니다) ...하지만 그것은 단 하나 의 믿음의 행동 일뿐 입니다.

내 첫 번째 방법으로 "지루하고"시간이 걸리는 것처럼 이의를 제기하는 PS에 대한 대답은 "아무 고통도 없습니다"입니다. 일반화 된 컬렉션에는 Map.Entry <String, String> s 및 아무것도 포함되지 않습니다. 그밖에. 이 보증 비용을 지불해야합니다. 체계적으로 제네릭을 사용할 때이 지불은 아름답게 기계 시간이 아닌 코딩 규정 준수의 형태를 취합니다!
한 생각의 학교는 경고 대신 확인되지 않은 캐스트 오류를 ​​만들도록 Eclipse의 설정을 설정해야한다고 말할 수 있습니다. 이 경우 첫 번째 방법을 사용해야합니다.

package scratchpad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

public class YellowMouse {

    // First way

    Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
      return uncheckedCast( session.getAttribute("attributeKey") );
    }


    // NB this is a utility method which should be kept in your utility library. If you do that it will
    // be the *only* time in your entire life that you will have to use this particular tag!!

    @SuppressWarnings({ "unchecked" })
    public static synchronized <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}

댓글 권한이 없다는 사실은 다른 사람의 답변을 편집하여 댓글을 추가 할 수 없습니다. 다른 사람의 답변을 편집하여 형식, 구문 등을 개선하여 의견을 추가하지 않습니다. 당신이 50 담당자에 도달 할 때 당신은 모든 곳에서 의견을 말할 수있을 것입니다, 그 동안 나는 당신이 저항 할 수 있다고 확신합니다 (또는 정말로 할 수 없다면 게시물의 기존 답변에 의견을 쓰십시오). (다른 사람들을위한 참고 사항 : 검토 도구에서 다른 게시물에 대한 그의 제안 된 의견 편집을보고 거부했기 때문에 이것을 작성합니다)
Matteo Italia

-1

경고가 사라집니다.

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}

1
아닙니다. 실제로 이것은 처음에 하나의 경고가 두 곳에 생성됩니다.
Stijn de Witt

그래. 왜 그렇게 생각했는지 모르겠습니다.
lukewm

-3

솔루션 : Eclipse에서이 경고를 사용하지 마십시오. @SuppressWarnings하지 말고 완전히 비활성화하십시오.

위에 제시된 "솔루션"중 일부는 잘못된 경고를 표시하기 위해 코드를 읽을 수 없게 만듭니다.


9
왜 그런지 물어봐도 될까요? 전 세계적으로 경고를 비활성화하면이 문제가 실제로 발생하는 다른 장소가 숨겨집니다. 를 추가 @SuppressWarnings해도 코드를 읽을 수 없게되지는 않습니다.
MByD
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.