Зачем мне использовать java.lang.Class.cast

Недавно я наткнулся на такой фрагмент кода:

Object o = .. ;
Foo foo = Foo.class.cast(o);

На самом деле я даже не знал, что java.lang.Class имеет метод приведения, поэтому я заглянул в документацию и, насколько я понял, он просто выполняет приведение к классу, который представляет объект Class. Таким образом, приведенный выше код будет примерно эквивалентен

Object o = ..;
Foo foo = (Foo)o;

Поэтому я задался вопросом, почему я хотел бы использовать метод приведения вместо того, чтобы просто выполнять приведение «по-старому». Есть ли у кого-нибудь хороший пример, когда использование метода приведения выгоднее простого приведения?


person Jan Thomä    schedule 26.10.2011    source источник


Ответы (8)


Я не думаю, что его часто используют именно так, как вы показали. Наиболее частое использование, которое я видел, - это когда люди, использующие дженерики, пытаются сделать эквивалент этого:

public static <T extends Number> T castToNumber(Object o) {
    return (T)o;
}

Что на самом деле не делает ничего полезного из-за стирания типа.

Принимая во внимание, что это работает и безопасно по типу (по модулю ClassCastExceptions):

public static <T extends Number> T castToNumber(Object o, Class<T> clazz) {
    return clazz.cast(o);
}

РЕДАКТИРОВАТЬ: пара примеров использования google guava:

person clstrfsck    schedule 26.10.2011
comment
Это по-прежнему эквивалентно (Number) o, поскольку приведение не влияет на фактический тип объекта. - person nfechner; 26.10.2011
comment
Не совсем, но имейте в виду, что это упрощенный пример, и обычно o и clazz определяются во время выполнения, а не жестко запрограммированы. Integer x = castToNumber(o, Integer.class) здесь допустимо, а Integer x = (Number)o - нет. Может, стоило для примера не использовать бит Number? Может быть, castToNumber был неудачным выбором для названия метода? - person clstrfsck; 26.10.2011
comment
Я пытаюсь подчеркнуть, что у вас все еще должна быть левая сторона вашего назначения, где вам нужно указать явный класс. Так что вы всегда можете просто написать T x = (T) o; - person nfechner; 26.10.2011
comment
Я имел в виду T как общий заполнитель для любого класса, о котором вы можете подумать, а не как обобщенную конструкцию. С точки зрения реального кода, вроде этого: Object o = new Object(); Integer i = (Integer) o; Очевидно, этот код будет выдавать ClassCastException во время выполнения, но он не покажет предупреждения компилятора. - person nfechner; 26.10.2011
comment
На мгновение я неправильно понял ваш ответ, но теперь думаю, что понимаю. Ваш комментарий является разумным для клиентского кода, но если вы пишете библиотеку, не всегда ясно, каково будет ее использование. Я добавил несколько конкретных вариантов использования библиотек guava. - person clstrfsck; 26.10.2011

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

public static void doSomething(Class<? extends SomeBaseClass> whatToCastAs,Object o)
    {
        SomeBaseClass castObj =  whatToCastAs.cast(o);
        castObj.doSomething();
    }

В общем, используйте более простое приведение, если этого недостаточно.

person Krystian Cybulski    schedule 26.10.2011
comment
Ты ведь шутишь, правда? Почему бы вам просто не написать ((SomeBaseClass) o).doSomething();? - person nfechner; 26.10.2011
comment
@nfechner, потому что ты не всегда можешь это сделать. Думаю, лучший пример - (часть) реализации метода JDBC public ‹T› T unwrap (Class ‹T› iface). Там вы можете сделать что-то вроде: return iface.cast (this); - person Mark Rotteveel; 26.10.2011
comment
(CastTarget) o - время компиляции, foo.getClass (). Cast (0) - время выполнения - person Konstantin Pribluda; 26.10.2011

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

person RAY    schedule 26.10.2011

Абсолютно незачем писать Foo.class.cast(o), это эквивалент (Foo)o.

В общем, если X является реифицируемым типом и Class<X> clazz, то clazz.cast(o) совпадает с (X)o.

Если все типы реифицируемы, метод Class.cast() будет избыточным и бесполезным.

К сожалению, из-за стирания в текущей версии Java не все типы реифицируемы. Например, переменные типа не могут быть преобразованы.

Если T является переменной типа, приведение (T)o не отмечено флажком, поскольку во время выполнения точный тип T неизвестен JVM, JVM не может проверить, действительно ли o является типом T. Приведение может быть разрешено ошибочно, что может вызвать проблемы позже.

Это не большая проблема; обычно, когда программист делает (T)o, он уже рассудил, что приведение безопасно и не вызовет никаких проблем во время выполнения. Приведение проверяется логикой приложения.

Предположим, что Class<T> clazz доступен в точке приведения, тогда мы действительно знаем, что T во время выполнения; мы можем добавить дополнительную проверку времени выполнения, чтобы убедиться, что o действительно T.

check clazz.isInstance(o);
(T)o;

И это, по сути, то, что делает Class.cast().

Мы ни в коем случае не ожидаем, что приведение типов завершится неудачно, поэтому в правильно реализованном приложении проверка clazz.isInstance(o) всегда должна завершаться успешно, поэтому clazz.cast(o) эквивалентно (T)o - опять же, в предположении, что код правильный.

Если можно доказать, что код правильный и приведение безопасно, можно было бы предпочесть (T)o clazz.cast(o) по соображениям производительности. В примере MutableClassToInstanceMap, поднятого в другом ответе, мы, очевидно, видим, что приведение безопасно, поэтому простого (T)o было бы достаточно.

person irreputable    schedule 26.10.2011
comment
Если все типы реифицируемы, метод Class.cast (), следовательно, избыточен и бесполезен. Не правда. Class.cast () необходим, когда вы не знаете класс во время компиляции и имеете объект класса только во время выполнения. - person newacct; 26.10.2011
comment
@newacct Если java полностью реифицируемый, среда выполнения знает фактический тип T, поэтому может проверить (T)o, чтобы убедиться, что o действительно относится к типу T. Тогда нет необходимости в Class<T>.cast(o). - person irreputable; 27.10.2011

class.cast предназначен для типа generics.

Когда вы создаете класс с универсальным параметром T, вы можете передать Class. Затем вы можете выполнить приведение как со статической, так и с динамической проверкой, чего не дает (T). Он также не выдает непроверенных предупреждений, потому что он отмечен (в этот момент).

person aleroot    schedule 26.10.2011

Обычный пример для этого - когда вы извлекаете из постоянного слоя коллекцию сущностей, на которые ссылается объект класса и некоторые условия. Возвращенная коллекция может содержать непроверенные объекты, поэтому, если вы просто приведете ее как указанную G_H, вы вызовете исключение Cast Exception в этот момент, а не при доступе к значениям.

Один из примеров этого - когда вы извлекаете коллекцию из DAO, которая возвращает непроверенную коллекцию, и в своей службе вы выполняете итерацию по ней, эта ситуация может привести к ClassCastException.

Один из способов решить эту проблему, поскольку у вас есть требуемый класс и непроверенная коллекция, - это перебрать его и передать его внутри DAO, преобразовав коллекцию в проверенную коллекцию и затем вернув ее.

person Francisco Spaeth    schedule 26.10.2011

Потому что у вас может быть что-то такое:

Number fooMethod(Class<? extends Number> clazz) {
    return clazz.cast(var);
}
person G_H    schedule 26.10.2011

"Приведение" в Java, например (Number)var, где объект в круглых скобках является ссылочным типом, на самом деле состоит из двух частей:

  • Время компиляции: результат выражения приведения имеет тип типа, к которому вы приводите
  • Время выполнения: он вставляет операцию проверки, которая в основном говорит, что если объект не является экземпляром этого класса, затем выдает ClassCast Exception (если то, что вы выполняете, является переменной типа, тогда проверяемый класс будет нижняя граница переменной типа)

Чтобы использовать синтаксис, вам нужно знать класс во время написания кода. Предположим, вы не знаете во время компиляции, к какому классу хотите привести приведение; вы знаете это только во время выполнения.

Вы спросите, а в чем тогда смысл кастинга? Разве цель кастинга не заключается в том, чтобы во время компиляции преобразовать выражение в желаемый тип? Так что, если вы не знаете тип во время компиляции, тогда нет никакого преимущества во время компиляции, верно? Верно, но это только первый пункт выше. Вы забываете компонент времени выполнения приведения (второй элемент выше): он проверяет объект на соответствие классу.

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

if (!clazz.isInstance(var))
    throw new ClassCastException();

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

person newacct    schedule 26.10.2011