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

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

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

Выключается Class.cast не совпадает с static_cast в С++. Это больше похоже на 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 );
        }
    }
}

Итак, это мои вопросы.

  • Должны ли Class.cast() быть отправлены на землю дженериков? Там он имеет довольно много законных целей.
  • Если компиляторы генерируют ошибки компиляции, когда используется Class.cast(), и во время компиляции можно определить нелегальные условия?
  • Должна ли Java предоставлять оператор литья в качестве языковой конструкции, аналогичной С++?

Ответ 1

Я использовал когда-либо Class.cast(Object), чтобы избежать предупреждений в "generics land". Я часто вижу методы, которые делают такие вещи:

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

Ответ 2

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

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

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

Ответ 3

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

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

MyObject myObject = (MyObject) object

а не

MyObject myObject = MyObject.class.cast(object)

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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
    }