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

Когда я учился на 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 ++?

person Alexander Pogrebnyak    schedule 12.10.2009    source источник
comment
Простой ответ: (1) Где земля дженериков? Чем это отличается от того, как сейчас используется оператор приведения? (2) Возможно. Но в 99% всего когда-либо написанного кода Java крайне маловероятно, чтобы кто-либо использовал Class.cast(), когда во время компиляции могут быть определены недопустимые условия. В этом случае все, кроме вас, просто используют стандартный оператор приведения. (3) В Java есть оператор приведения в качестве языковой конструкции. Он не похож на C ++. Это потому, что многие языковые конструкции Java не похожи на C ++. Несмотря на внешнее сходство, Java и C ++ совершенно разные.   -  person Daniel Pryden    schedule 12.10.2009


Ответы (8)


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

person sfussenegger    schedule 12.10.2009
comment
Мое последнее слово: если Foo.class.cast(o) будет вести себя так, как (Foo) и (Foo) были запрещены, это привело бы к двум результатам: менее случайное приведение типов (никто не любит много печатать). Упрощенный поиск экземпляров приведения в исходном коде. На этом я покину Страну Грез и изгоню Class.cast в Страну Generics :). - person Alexander Pogrebnyak; 12.10.2009
comment
Я думаю, что правильным способом в этом случае было бы сначала выполнить экземпляр проверки следующим образом: if (cls.isInstance (o)) {return cls.cast (o); } Если, конечно, вы не уверены, что тип будет правильным. - person Puce; 19.01.2012
comment
Я думаю, что cast () неплохо работает в некоторых шаблонах фабричного типа. Например, приведение данных из некоторого XDR к некоторому внутреннему представлению на основе определения .class. Это работает так же, как литье ObjectiveC. - person Daniel Voina; 19.06.2013
comment
Почему второй вариант лучше? Разве первый вариант не более эффективен, потому что код вызывающего абонента в любом случае будет выполнять динамическое приведение? - person user1944408; 04.03.2014
comment
@ user1944408, если вы строго говорите о производительности, это может быть, хотя в значительной степени незначительно. Я бы не хотел получить ClassCastExceptions там, где нет очевидного случая. Кроме того, второй работает лучше там, где компилятор не может вывести T, например list.add (this. ‹String› doSomething ()) vs. list.add (doSomething (String.class)) - person sfussenegger; 04.03.2014
comment
Но вы все равно можете получить ClassCastExceptions при вызове cls.cast (o), пока вы не получаете никаких предупреждений во время компиляции. Я предпочитаю первый вариант, но я бы использовал второй вариант, когда мне нужно, например, вернуть null, если я не могу выполнить кастинг. В этом случае я бы заключил cls.cast (o) в блок try catch. Я также согласен с вами, что это также лучше, когда компилятор не может вывести T. Спасибо за ответ. - person user1944408; 04.03.2014
comment
Я также использую второй вариант, когда мне нужно, чтобы классы Class ‹T› были динамическими, например, если я использую отражение, но во всех остальных случаях я предпочитаю первый. Думаю, это просто личный вкус. - person user1944408; 04.03.2014
comment
как насчет производительности Class.cast (объект) по сравнению с (T) объектом в сценарии с высоким QPS? - person lily; 22.02.2021

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

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

MyObject myObject = (MyObject) object

скорее, чем

MyObject myObject = MyObject.class.cast(object)

РЕДАКТИРОВАТЬ: ошибки во время компиляции

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

person notnoop    schedule 12.10.2009
comment
Я полностью согласен с тем, что приведение типов следует использовать экономно, поэтому наличие чего-то, что можно было бы легко найти, было бы большим преимуществом для рефакторинга / поддержки кода. Но проблема в том, что, хотя на первый взгляд кажется, что Class.cast отвечает всем требованиям, это создает больше проблем, чем решает. - person Alexander Pogrebnyak; 12.10.2009

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

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

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

person cletus    schedule 12.10.2009
comment
Не вся информация хранится во время выполнения. Как вы можете видеть из моего примера, (Bar) foo действительно генерирует ошибку во время компиляции, а Bar.class.cast(foo) - нет. На мой взгляд, если он используется таким образом, он должен. - person Alexander Pogrebnyak; 12.10.2009
comment
@ Александр Погребняк: Тогда не делай этого! Bar.class.cast(foo) явно сообщает компилятору, что вы хотите выполнить приведение во время выполнения. Если вы хотите проверить правильность преобразования во время компиляции, единственный выбор - выполнить приведение стиля (Bar) foo. - person Daniel Pryden; 12.10.2009
comment
Как вы думаете, как поступить так же? поскольку java не поддерживает множественное наследование классов. - person Yamur; 08.12.2017

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

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

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

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

person Joachim Sauer    schedule 12.10.2009
comment
dynamic_cast<>() со ссылочным типом. - person Tom Hawtin - tackline; 12.10.2009
comment
@Tom: это правильно? Мой C ++ очень ржавый, пришлось много его заново гуглить ;-) - person Joachim Sauer; 12.10.2009
comment
+1 за: Пороки оператора приведения в стиле C обычно не относятся к Java. Совершенно верно. Я как раз собирался опубликовать именно эти слова в качестве комментария к вопросу. - person Daniel Pryden; 12.10.2009

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

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

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

Поскольку лямбда пришла в java, мне лично нравится использовать приведение Class # с API коллекций / потоков, например, если я работаю с абстрактными типами.

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);
}
person Caleb    schedule 29.05.2019

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

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

person mmmmmm    schedule 12.10.2009

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

например serviceLoder использует этот трюк при создании объектов, проверьте S p = service.cast (c.newInstance ()); это вызовет исключение приведения класса, когда S P = (S) c.newInstance (); не будет и может отображать предупреждение «Типовая безопасность: отключенное приведение от Object к S». (то же, что и Object 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
    }
person Amjad Abdul-Ghani    schedule 07.12.2017

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

person Wep0n    schedule 02.03.2018