Java Class.cast () 대 캐스트 연산자


107

C ++ 시절에 C 스타일 캐스트 연산자의 악에 대해 배웠던 나는 처음에는 Java 5 java.lang.Class에서 cast메소드를 얻었음 을 알게되어 기뻤습니다 .

드디어 캐스팅에 대한 OO 방식이 있다고 생각했습니다.

결과 Class.caststatic_castC ++에서 와 동일하지 않습니다 . 더 비슷 reinterpret_cast합니다. 예상되는 곳에 컴파일 오류가 발생하지 않고 대신 런타임으로 지연됩니다. 다음은 다양한 동작을 보여주는 간단한 테스트 사례입니다.

package test;

import static org.junit.Assert.assertTrue;

import org.junit.Test;


public class TestCast
{
    static final class Foo
    {
    }

    static class Bar
    {
    }

    static final class BarSubclass
        extends Bar
    {
    }

    @Test
    public void test ( )
    {
        final Foo foo = new Foo( );
        final Bar bar = new Bar( );
        final BarSubclass bar_subclass = new BarSubclass( );

        {
            final Bar bar_ref = bar;
        }

        {
            // Compilation error
            final Bar bar_ref = foo;
        }
        {
            // Compilation error
            final Bar bar_ref = (Bar) foo;
        }

        try
        {
            // !!! Compiles fine, runtime exception
            Bar.class.cast( foo );
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }

        {
            final Bar bar_ref = bar_subclass;
        }

        try
        {
            // Compiles fine, runtime exception, equivalent of C++ dynamic_cast
            final BarSubclass bar_subclass_ref = (BarSubclass) bar;
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }
    }
}

그래서 이것이 제 질문입니다.

  1. Class.cast()제네릭 땅으로 추방 되어야합니까 ? 거기에는 합법적 인 용도가 꽤 있습니다.
  2. 컴파일러는를 Class.cast()사용할 때 컴파일 오류를 생성해야 하며 컴파일 타임에 잘못된 조건을 확인할 수 있습니까?
  3. Java는 C ++와 유사한 언어 구조로 캐스트 연산자를 제공해야합니까?

4
간단한 대답 : (1) "Generics land"는 어디에 있습니까? 현재 캐스트 연산자가 사용되는 방식과 어떻게 다른가요? (2) 아마. 그러나 지금까지 기록 된 모든 자바 코드의 99 %에서,이다 매우 누구나 사용하기 위해 가능성이 Class.cast()부정한 조건이 컴파일시에 확인할 수 있습니다 때. 이 경우에는 모두 표준 캐스트 연산자를 사용합니다. (3) Java에는 언어 구조로 캐스트 연산자가 있습니다. C ++와 비슷하지 않습니다. 많은 Java 언어 구조가 C ++와 유사하지 않기 때문입니다. 표면적 인 유사성에도 불구하고 Java와 C ++는 상당히 다릅니다.
Daniel Pryden 09-10-12

답변:


117

나는 Class.cast(Object)"제네릭 땅"에서 경고를 피하기 위해서만 사용 했습니다. 나는 종종 다음과 같은 일을하는 메소드를 본다.

@SuppressWarnings("unchecked")
<T> T doSomething() {
    Object o;
    // snip
    return (T) o;
}

다음과 같이 교체하는 것이 가장 좋습니다.

<T> T doSomething(Class<T> cls) {
    Object o;
    // snip
    return cls.cast(o);
}

그것이 Class.cast(Object)내가 본 유일한 사용 사례입니다 .

컴파일러 경고에 관하여 : 나는 그것이 Class.cast(Object)컴파일러에게 특별하지 않다고 생각합니다 . 정적으로 사용 (즉 때를 최적화 할 수있는 Foo.class.cast(o)것이 아니라 cls.cast(o))하지만 난 그것을 사용하는 사람을 본 적이 - 다소 쓸모없는 컴파일러에이 최적화를 구축하는 노력을 기울이고있다.


8
이 경우 올바른 방법은 다음과 같이 먼저 확인 인스턴스를 수행하는 것이라고 생각합니다. if (cls.isInstance (o)) {return cls.cast (o); } 물론 유형이 정확하다고 확신하는 경우는 예외입니다.
Puce

1
두 번째 변형이 더 나은 이유는 무엇입니까? 호출자의 코드가 어쨌든 동적 캐스트를 수행하기 때문에 첫 번째 변형이 더 효율적이지 않습니까?
user1944408

1
@ user1944408 성능에 대해 엄격하게 말하는 한 거의 무시할 수 있지만 성능에 대해 말할 수 있습니다. 나는 명백한 경우가없는 ClassCastExceptions를 얻는 아이디어가 마음에 들지 않습니다. 또한, 두 번째는 잘 작동 곳 컴파일러 수없는 T 추론, 예를 들면에는 list.add (이 <문자열> 해봐요 ().) 대에는 list.add (해봐요 (String.class))
sfussenegger

2
그러나 컴파일 타임에 경고를받지 않는 동안 cls.cast (o)를 호출하면 ClassCastExceptions를 얻을 수 있습니다. 첫 번째 변형을 선호하지만 예를 들어 캐스팅을 할 수없는 경우 null을 반환해야 할 때 두 번째 변형을 사용합니다. 이 경우 cls.cast (o)를 try catch 블록으로 래핑합니다. 나는 또한 컴파일러가 T를 추론 할 수 없을 때 이것이 더 좋다는 것에 동의합니다. 답장을 보내 주셔서 감사합니다.
user1944408

1
예를 들어 리플렉션을 사용하지만 다른 모든 경우에는 첫 번째를 선호하는 경우와 같이 Class <T> cls가 동적이어야 할 때 두 번째 변형을 사용합니다. 그건 개인적인 취향 일 뿐인 것 같아요.
user1944408 2014 년

20

첫째, 거의 모든 캐스트를하지 않도록 강력히 권장하므로 가능한 한 제한해야합니다! 자바의 강력한 컴파일 타임 기능의 이점을 잃게됩니다.

어쨌든 리플렉션을 통해 토큰을 Class.cast()검색 할 때 주로 사용해야합니다 Class. 쓰는 것이 더 관용적입니다.

MyObject myObject = (MyObject) object

보다는

MyObject myObject = MyObject.class.cast(object)

편집 : 컴파일시 오류

전반적으로 Java는 런타임에만 캐스트 검사를 수행합니다. 그러나 컴파일러는 그러한 캐스트가 결코 성공할 수 없다는 것을 증명할 수있는 경우 오류를 발행 할 수 있습니다 (예 : 클래스를 상위 유형이 아닌 다른 클래스로 캐스트하고 최종 클래스 유형을 해당 유형 계층 구조에없는 클래스 / 인터페이스로 캐스트). 여기부터 Foo하고 Bar서로 계층 구조에없는 클래스이며, 캐스트는 성공할 수 없다.


나는 캐스트가 드물게 사용되어야한다는 데 전적으로 동의합니다. 그래서 쉽게 검색 할 수있는 것을 갖는 것이 코드 리팩토링 / 유지 관리에 큰 이점이 될 것입니다. 그러나 문제는 언뜻보기 Class.cast에 청구서에 맞는 것처럼 보이지만 해결하는 것보다 더 많은 문제가 발생한다는 것입니다.
Alexander Pogrebnyak

16

언어간에 구문과 개념을 번역하고 번역하는 것은 항상 문제가되고 종종 오해의 소지가 있습니다. 캐스팅도 예외는 아닙니다. 특히 Java는 동적 언어이고 C ++는 다소 다릅니다.

Java의 모든 캐스팅은 수행 방법에 관계없이 런타임에 수행됩니다. 유형 정보는 런타임에 보관됩니다. C ++는 좀 더 혼합 된 것입니다. C ++의 구조체를 다른 구조체로 캐스팅 할 수 있으며 이는 해당 구조체를 나타내는 바이트의 재해 석일뿐입니다. Java는 그렇게 작동하지 않습니다.

또한 Java와 C ++의 제네릭은 크게 다릅니다. Java에서 C ++ 작업을 수행하는 방법에 지나치게 신경 쓰지 마십시오. Java 방식으로 작업하는 방법을 배워야합니다.


모든 정보가 런타임에 저장되는 것은 아닙니다. 내 예제에서 볼 수 있듯이 (Bar) foo컴파일 타임에 오류가 발생하지만 Bar.class.cast(foo)그렇지 않습니다. 제 생각에는 이런 식으로 사용된다면 그렇게해야합니다.
Alexander Pogrebnyak

5
@Alexander Pogrebnyak : 그럼 하지마! Bar.class.cast(foo)런타임에 캐스트를 수행 할 것을 컴파일러에 명시 적으로 알립니다. 캐스트의 유효성에 대한 컴파일 타임 검사를 원하는 경우 유일한 선택은 (Bar) foo스타일 캐스트 를 수행하는 것 입니다.
Daniel Pryden 09-10-12

같은 방식으로하는 방법이 무엇이라고 생각하십니까? Java는 다중 클래스 상속을 지원하지 않기 때문입니다.
Yamur

13

Class.cast()Java 코드에서는 거의 사용되지 않습니다. 사용되는 경우 일반적으로 런타임에만 알려진 유형 (즉, 각각의 Class객체 및 일부 유형 매개 변수를 통해)을 사용합니다. 제네릭을 사용하는 코드에서만 정말 유용합니다 (이게 이전에 소개되지 않은 이유이기도합니다).

그것은 것입니다 하지 유사 reinterpret_cast가 있기 때문에, 아니 당신이 더 이상 않는 정상 캐스트에 비해 런타임에 유형의 시스템을 망가뜨릴 수 (즉, 당신이 할 수 휴식 제네릭 형식 매개 변수를 할 수는 없지만 휴식 "진짜"유형).

C 스타일 캐스트 연산자의 악은 일반적으로 Java에 적용되지 않습니다. C 스타일 캐스트처럼 보이는 Java 코드는dynamic_cast<>() 의 참조 유형 와 (Java에는 런타임 유형 정보가 있음).

일반적으로 C ++ 캐스팅 연산자를 Java 캐스팅과 비교하는 것은 Java에서는 참조 만 캐스팅 할 수 있고 객체에 대한 변환이 발생하지 않기 때문에 매우 어렵습니다 (이 구문을 사용하여 기본 값만 변환 할 수 있음).


dynamic_cast<>()참조 유형으로.
Tom Hawtin-tackline

@Tom : 편집이 맞습니까? 내 C ++는 매우 녹슬 었습니다. Google을 많이 다시 검색해야했습니다 ;-)
Joachim Sauer

+1 for : "C 스타일 캐스트 연산자의 악은 일반적으로 Java에 적용되지 않습니다." 사실입니다. 질문에 대한 댓글로 정확한 단어를 게시하려고했습니다.
Daniel Pryden 09-10-12

4

일반적으로 캐스트 연산자는보다 간결하고 컴파일러에서 분석하여 코드에 대한 노골적인 문제를 뱉어 낼 수 있기 때문에 Class # cast 메서드보다 선호됩니다.

Class # cast는 컴파일이 아닌 런타임에 유형 검사를 담당합니다.

Class # cast에 대한 사용 사례가 있습니다. 특히 반사 작업과 관련하여 특히 그렇습니다.

람다가 자바에 왔기 때문에 예를 들어 추상 유형으로 작업하는 경우 컬렉션 / 스트림 API와 함께 Class # cast를 사용하는 것을 개인적으로 좋아합니다.

Dog findMyDog(String name, Breed breed) {
    return lostAnimals.stream()
                      .filter(Dog.class::isInstance)
                      .map(Dog.class::cast)
                      .filter(dog -> dog.getName().equalsIgnoreCase(name))
                      .filter(dog -> dog.getBreed() == breed)
                      .findFirst()
                      .orElse(null);
}

3

C ++와 Java는 서로 다른 언어입니다.

Java C 스타일 캐스트 연산자는 C / C ++ 버전보다 훨씬 더 제한적입니다. 효과적으로 자바 캐스트는 C ++ dynamic_cast와 비슷합니다. 만약 당신이 가지고있는 객체를 새로운 클래스로 캐스트 할 수 없다면 런타임 (또는 코드에 충분한 정보가있는 경우 컴파일 시간) 예외가 발생합니다. 따라서 C 유형 캐스트를 사용하지 않는다는 C ++ 아이디어는 Java에서 좋은 아이디어가 아닙니다.


0

가장 많이 언급했듯이 추악한 캐스트 경고를 제거하는 것 외에도 Class.cast는 일반적으로 일반 캐스팅과 함께 사용되는 런타임 캐스트입니다. 일반 정보가 런타임에 지워지고 각 일반이 Object로 간주되는 방식으로 인해 초기 ClassCastException을 던집니다.

예를 들어 serviceLoder는 객체를 생성 할 때이 트릭을 사용합니다. S p = service.cast (c.newInstance ()); SP = (S) c.newInstance (); 일 때 클래스 캐스트 예외가 발생합니다. 'Type safety : Unchecked cast from Object to S' 경고가 표시되지 않고 표시 될 수 있습니다 . (Object P = (Object) c.newInstance ();)

-단순히 캐스팅 된 개체가 캐스팅 클래스의 인스턴스인지 확인한 다음 캐스팅 연산자를 사용하여 경고를 표시하지 않고 숨 깁니다.

동적 캐스트를위한 자바 구현 :

@SuppressWarnings("unchecked")
public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj;
}




    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }

0

개인적으로 저는 JSON to POJO 변환기를 빌드하기 위해 이것을 사용했습니다. 함수로 처리 된 JSONObject에 배열 또는 중첩 된 JSONObject가 포함 된 경우 (여기에있는 데이터가 기본 유형이 아님을 의미 함을 의미 함 String) 다음과 같은 방식으로 사용하여 setter 메서드를 호출합니다 class.cast().

public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) {
    ...
    for(Method m : clazz.getMethods()) {
        if(!m.isAnnotationPresent(convertResultIgnore.class) && 
            m.getName().toLowerCase().startsWith("set")) {
        ...
        m.invoke(returnObject,  m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key))));
    }
    ...
}

이것이 매우 도움이되는지 확실하지 않지만, 앞서 언급했듯이, 리플렉션은 class.cast()제가 생각할 수있는 합법적 인 사용 사례 중 하나입니다. 적어도 지금은 다른 예가 있습니다.

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