it-swarm.com.ru

Java Class.cast () против оператора приведения

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

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

Оказывается, 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++?
93
Alexander Pogrebnyak

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

100
sfussenegger

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

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

MyObject myObject = (MyObject) object

скорее, чем 

MyObject myObject = MyObject.class.cast(object)

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

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

17
notnoop

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

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

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

16
cletus

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

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

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

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

10
Joachim Sauer

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

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

3
Mark

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

0
Wep0n

В дополнение к удалению уродливых предупреждений о приведении, как это уже упоминалось, 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
    }
0
Amjad Abdul-Ghani