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

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 ++와 유사한 언어 구조로 캐스트 연산자를 제공해야합니까?


답변

나는 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))하지만 난 그것을 사용하는 사람을 본 적이 – 다소 쓸모없는 컴파일러에이 최적화를 구축하는 노력을 기울이고있다.


답변

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

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

MyObject myObject = (MyObject) object

보다는

MyObject myObject = MyObject.class.cast(object)

편집 : 컴파일시 오류

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


답변

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

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

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


답변

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

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

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

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


답변

일반적으로 캐스트 연산자는보다 간결하고 컴파일러에서 분석하여 코드에 대한 노골적인 문제를 뱉어 낼 수 있기 때문에 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);
}


답변

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

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


답변

가장 많이 언급했듯이 추악한 캐스트 경고를 제거하는 것 외에도 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
    }