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


107

Навчившись протягом моїх днів 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()слід виганяти на землю Generics? Там він має досить багато законних цілей.
  2. Чи повинні компілятори генерувати помилки компіляції при Class.cast()використанні, а незаконні умови можна визначити під час компіляції?
  3. Чи повинна Java надавати оператор литових структур як мовну конструкцію, схожу на C ++?

4
Проста відповідь: (1) Де "Земля генериків"? Чим це відрізняється від того, як зараз використовується оператор лиття? (2) Напевно. Але в 99% усіх написаних кодом Java, які коли-небудь написані, вкрай малоймовірно, щоб хтось використовував, Class.cast()коли незаконні умови можуть бути визначені під час компіляції. У цьому випадку всі, окрім вас, просто використовують стандартний оператор кастингу. (3) У Java є оператор передачі як мовна конструкція. Він не схожий на C ++. Це тому, що багато мовних конструкцій Java не схожі на C ++. Незважаючи на поверхневу схожість, Java і C ++ досить різні.
Даніель Приден

Відповіді:


117

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


8
Я думаю, що в цьому випадку правильним способом було б зробити екземпляр перевірки спочатку таким чином: якщо (cls.isInstance (o)) {return cls.cast (o); } За винятком випадків, якщо ви впевнені, що тип буде правильним, звичайно.
Пуче

1
Чому другий варіант кращий? Чи не перший варіант є більш ефективним, оскільки код абонента все одно зробить динамічний викид?
користувач1944408

1
@ user1944408 до тих пір, поки ви суворо говорите про ефективність роботи, хоча вона і зовсім незначна. Мені не хотілося б ідеї отримати ClassCastExceptions там, де немає очевидного випадку. Крім того, другий працює краще, коли компілятор не може зробити висновок T, наприклад list.add (це. <String> doSomething ()) проти list.add (doSomething (String.class))
sfussenegger

2
Але ви все одно можете отримати ClassCastExceptions, коли ви телефонуєте cls.cast (o), поки ви не отримуєте жодних попереджень за час компіляції. Я вважаю за краще перший варіант, але я би використовував другий варіант, коли мені потрібно, наприклад, повернути null, якщо я не можу робити кастинг. У такому випадку я б загорнув cls.cast (o) у блок спробувати. Я також згоден з вами, що це також краще, коли компілятор не може зробити висновок Т. Дякую за відповідь.
користувач1944408

1
Я також використовую другий варіант, коли мені потрібно, щоб клас <T> cls був динамічним, наприклад, якщо я використовую рефлексію, але в усіх інших випадках я віддаю перевагу першому. Ну, мабуть, це лише особистий смак.
користувач1944408

20

По-перше, ви сильно не бажаєте робити майже будь-яку ролю, тому вам слід максимально обмежити це! Ви втрачаєте переваги сильно набраних функцій часу компіляції Java.

У будь-якому випадку, його Class.cast()слід використовувати в основному, коли ви отримуєте Classмаркер за допомогою відображення. Більш ідіоматично писати

MyObject myObject = (MyObject) object

а не

MyObject myObject = MyObject.class.cast(object)

EDIT: Помилки під час компіляції

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


Я повністю погоджуюся з тим, що касти повинні використовуватися ощадливо, тому для рефакторингу / підтримки коду було б корисно мати те, що можна легко шукати. Але проблема полягає в тому, що хоча на перший погляд Class.castздається, що він відповідає законопроекту, він створює більше проблем, ніж вирішує.
Олександр Погребняк

16

Завжди проблематично і часто вводити в оману спроби і перекладати конструкції та поняття між мовами. Кастинг не є винятком. Тим більше, що Java - це динамічна мова, а C ++ дещо відрізняється.

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

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


Не вся інформація зберігається під час виконання. Як видно з мого прикладу, (Bar) fooсправді генерується помилка під час компіляції, але Bar.class.cast(foo)це не так. На мою думку, якщо він використовується таким чином, він повинен.
Олександр Погребняк

5
@ Олександр Погребняк: Не роби цього тоді! Bar.class.cast(foo)явно повідомляє компілятору, що ви хочете виконати ролі під час виконання. Якщо ви хочете перевірити час компіляції на час компіляції, ваш єдиний вибір - це зробити (Bar) fooакторський стиль.
Даніель Приден

Як ви думаєте, як це зробити так само? оскільки java не підтримує множинний клас успадкування.
Ямур

13

Class.cast()рідко коли-небудь використовується в коді Java. Якщо він використовується, то зазвичай з типами, які відомі лише під час виконання (тобто через їх відповідні Classоб'єкти та за певним параметром типу). Він корисний лише в коді, який використовує дженерики (це також причина, що не була введена раніше).

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

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

Взагалі порівняння операторів лиття C ++ з литтям Java є досить важким, оскільки в Java ви можете лише приводити посилання, і до об'єктів не відбувається жодного перетворення (за допомогою цього синтаксису можна конвертувати лише примітивні значення).


dynamic_cast<>()з еталонним типом.
Том Хотін - тайклін

@Tom: це правильне редагування? Мій C ++ дуже іржавий, мені довелося багато переробити його ;-)
Йоахім Зауер

+1 для: "Злості оператора лиття в стилі C зазвичай не стосуються Java." Цілком правда. Я збирався опублікувати ці точні слова як коментар до питання.
Даніель Приден

4

Як правило, оператор литого віддається перевазі методу виклику класу #, оскільки він більш стислий і може бути проаналізований компілятором, щоб виплюнути кричущі проблеми з кодом.

Клас # cast бере на себе відповідальність за перевірку типу під час виконання, а не під час компіляції.

Звичайно, існують випадки використання для класу # класу, особливо якщо мова йде про відображаючі операції.

Так як лямбда прийшла до Java, мені особисто подобається використовувати клас класу # для 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);
}

3

C ++ та Java - це різні мови.

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


0

На додаток до видалення негарних попереджень про викиди, як це було зазначено в більшості випадків, Class.cast - це час, який використовується в основному для загального кастингу, оскільки загальна інформація буде стерта під час виконання, а те, як кожна загальна мова буде вважатися Об'єктом, це призводить до того, що не кинути раннє ClassCastException.

наприклад, serviceLoder використовує цей трюк під час створення об'єктів, позначте S p = service.cast (c.newInstance ()); це викине виняток для класів, коли SP = (S) c.newInstance (); не відображатиметься і не може відображати попередження "Тип безпеки: Неперевірена передача з Об'єкта на S" (те саме, що "Об'єкт P = (" Об'єкт))

-просто він перевіряє, що закинутий об'єкт є екземпляром класу кастингу, тоді він використовуватиме оператор cast для передачі та приховування попередження, придушуючи його.

реалізація 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

Особисто я раніше це використовував для створення перетворювача 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()я можу придумати, принаймні, у вас є інший приклад зараз.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.