Java Class.cast () против оператора приведения - PullRequest
98 голосов
/ 12 октября 2009

Будучи обученным в течение моих дней C ++ о пороках оператора приведения в стиле C, я был рад сначала обнаружить, что в Java 5 java.lang.Class появился метод cast.

Я думал, что, наконец, у нас есть ОО способ борьбы с кастингом.

Оказывается, Class.cast - это не то же самое, что static_cast в C ++. Это больше похоже на 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 ++?

Ответы [ 8 ]

105 голосов
/ 12 октября 2009

Я только когда-либо использовал 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)), но я никогда не видел, чтобы кто-нибудь его использовал - что делает усилия по внедрению этой оптимизации в компилятор несколько бесполезными.

18 голосов
/ 12 октября 2009

Во-первых, вам настоятельно не рекомендуется делать практически любой актерский состав, поэтому вам следует максимально ограничить его! Вы теряете преимущества строго типизированных функций Java во время компиляции.

В любом случае Class.cast() следует использовать в основном при получении токена Class с помощью отражения. Более идиоматично писать

MyObject myObject = (MyObject) object

вместо

MyObject myObject = MyObject.class.cast(object)

EDIT: ошибки во время компиляции

В целом, Java выполняет проверки приведения только во время выполнения. Однако компилятор может выдать ошибку, если он может доказать, что такие приведения никогда не будут успешными (например, приведение класса к другому классу, который не является супертипом, и приведение окончательного типа класса к классу / интерфейсу, который не находится в его иерархии типов). Здесь, поскольку Foo и Bar являются классами, которые не находятся в иерархии друг друга, приведение никогда не может быть успешным.

16 голосов
/ 12 октября 2009

Всегда сложно и часто вводить в заблуждение пытаться переводить конструкции и понятия между языками. Кастинг не исключение. Особенно потому, что Java - это динамический язык, а C ++ несколько отличается.

Все приведение в Java, независимо от того, как вы это делаете, выполняется во время выполнения. Тип информации хранится во время выполнения. C ++ - немного больше смеси. Вы можете преобразовать структуру в C ++ в другую, и это просто переосмысление байтов, которые представляют эти структуры. Java не работает таким образом.

Также дженерики в Java и C ++ сильно отличаются. Не беспокойтесь о том, как вы делаете C ++ в Java. Вам нужно научиться делать вещи в стиле Java.

10 голосов
/ 12 октября 2009

Class.cast() редко используется в коде Java. Если он используется, то обычно с типами, которые известны только во время выполнения (то есть через их соответствующие Class объекты и некоторый параметр типа). Это действительно полезно только в коде, который использует дженерики (это также причина, по которой он не был представлен ранее).

Это не похоже на reinterpret_cast, потому что не позволит вам сломать систему типов во время выполнения больше, чем обычное приведение (то есть вы можете "сломать" "параметры универсального типа, но не может" сломать "" настоящие "типы).

Зло оператора приведения в стиле C, как правило, не относится к Java. Код Java, который выглядит как приведение в стиле C, больше всего похож на dynamic_cast<>() со ссылочным типом в Java (помните: Java имеет информацию о типе среды выполнения).

Как правило, сравнивать операторы приведения C ++ с приведением Java довольно сложно, поскольку в Java вы можете только когда-либо приводить ссылку и преобразование не происходит с объектами (с помощью этого синтаксиса можно преобразовать только примитивные значения).

3 голосов
/ 12 октября 2009

C ++ и Java - это разные языки.

Оператор приведения в стиле Java C гораздо более ограничен, чем версия C / C ++. По сути, приведение Java похоже на динамическое преобразование C ++, если имеющийся у вас объект не может быть приведен к новому классу, вы получите исключение времени выполнения (или, если в коде достаточно информации во время компиляции). Таким образом, идея C ++ не использовать приведение типов C не является хорошей идеей в Java

2 голосов
/ 29 мая 2019

Обычно оператор приведения предпочтительнее метода приведения класса #, поскольку он более лаконичен и может быть проанализирован компилятором для выявления явных проблем с кодом.

Класс # cast принимает ответственность за проверку типов во время выполнения, а не во время компиляции.

Конечно, есть случаи использования для приведения класса #, особенно когда речь идет об отражающих операциях.

С тех пор как лямбды пришли в java, мне лично нравится использовать Class # cast с API-интерфейс collection / stream, если я работаю, например, с абстрактными типами.

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);
}
0 голосов
/ 02 марта 2018

Лично я использовал это раньше для создания конвертера JSON в POJO. В случае, если JSONObject, обработанный с помощью функции, содержит массив или вложенные JSONObjects (подразумевая, что данные здесь не имеют примитивного типа или String), я пытаюсь вызвать метод установки с использованием 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() Я могу вспомнить, по крайней мере, у вас есть другой пример сейчас.

0 голосов
/ 07 декабря 2017

В дополнение к удалению некрасивых предупреждений о приведении, как это уже упоминалось, Class.cast - это приведение во время выполнения, в основном используемое с обобщенным приведением, поскольку общая информация будет удалена во время выполнения и некоторые способы, которыми каждый обобщенный будет считаться объектом, приводит не бросать раннее исключение ClassCastException.

например, serviceLoder использует этот трюк при создании объектов, отметьте S p = service.cast (c.newInstance ()); это вызовет исключение приведения класса когда S P = (S) c.newInstance (); не будет и может отображать предупреждение 'Безопасность типов: непроверенное приведение от объекта к S' . (аналогично объекту P = (Object) c.newInstance ();)

- просто он проверяет, что приведенный объект является экземпляром класса приведения, а затем использует оператор приведения для приведения и скрытия предупреждения, подавляя его.

Java-реализация для динамического приведения:

@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
    }
...