Внедряет ли Java кастинг накладные расходы? Зачем?

Есть ли какие-либо накладные расходы, когда мы бросаем объекты одного типа в другой? Или компилятор просто решает все, и во время выполнения нет затрат?

Это общие вещи или есть разные случаи?

Например, предположим, что у нас есть массив Object [], где каждый элемент может иметь другой тип. Но мы всегда точно знаем, что, например, элемент 0 является двойным, элемент 1 является строкой. (Я знаю, что это неправильный дизайн, но позвольте предположить, что я должен был это сделать.)

Сохраняется ли информация о типе Java во время выполнения? Или все забыто после компиляции, и если мы делаем (Double) элементы [0], мы просто будем следовать указателю и интерпретировать эти 8 байтов как double, что бы это ни было?

Я очень не понимаю, как типы выполняются на Java. Если у вас есть какая-либо рекомендация по книгам или статьям, спасибо также.

Ответ 1

Существует 2 типа кастинга:

Неявное литье, когда вы отбрасываете тип с более широким типом, который выполняется автоматически, и нет накладных расходов:

String s = "Cast";
Object o = s; // implicit casting

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

Object o = someObject;
String s = (String) o; // explicit casting

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

Взято из JavaWorld: стоимость кастинга

Литье используется для преобразования между типы - между ссылочными типами в в частности, для типа литья операции, в которой мы заинтересованы Вот.

Операции Upcast (также называемые расширение конверсий в Java Language Specification) конвертировать ссылка подкласса на предка класс ссылки. Это литье операция обычно автоматическая, поскольку он всегда безопасен и может быть реализованный непосредственно компилятором.

Операции Downcast (также называемые сужение конверсий в Java Language Specification) конвертировать ссылка класса предка на подкласс Справка. Эта операция литья создает накладные расходы, поскольку Java требует, чтобы бросок был проверен на чтобы убедиться, что он действителен. Если ссылочный объект не является экземпляр либо целевого типа для литой или подклассом этого типа, попытка броска не разрешена и должен бросить java.lang.ClassCastException.

Ответ 2

Для разумной реализации Java:

Каждый объект имеет заголовок, содержащий, среди прочего, указатель на тип среды выполнения (например, Double или String, но он никогда не может быть CharSequence или AbstractList). Предполагая, что компилятор времени выполнения (как правило, HotSpot в случае Sun) не может определить тип статически, некоторые проверки должны выполняться сгенерированным машинным кодом.

Сначала нужно прочитать указатель на тип выполнения. Это необходимо для вызова виртуального метода в подобной ситуации.

Для кастинга типа класса известно точно, сколько суперклассов существует до тех пор, пока вы не нажмете java.lang.Object, поэтому тип можно прочитать с постоянным смещением от указателя типа (фактически первые восемь в HotSpot). Опять же это аналогично чтению указателя метода для виртуального метода.

Тогда значение чтения просто нуждается в сравнении с ожидаемым статическим типом приведения. В зависимости от архитектуры набора инструкций другой команде потребуется разветвление (или ошибка) на неправильной ветке. ISA, такие как 32-разрядная ARM, имеют условную инструкцию и могут иметь грубый путь через счастливый путь.

Интерфейсы сложнее из-за множественного наследования интерфейса. Как правило, последние два нажатия на интерфейсы кэшируются во время выполнения. В самые ранние дни (более десяти лет назад) интерфейсы были немного медленными, но это уже не актуально.

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

Ответ 3

Например, предположим, что у нас есть массив Object [], где каждый элемент может иметь другой тип. Но мы всегда точно знаем, что, например, элемент 0 является двойным, элемент 1 является строкой. (Я знаю, что это неправильный дизайн, но позвольте предположить, что я должен был это сделать.)

Компилятор не учитывает типы отдельных элементов массива. Он просто проверяет, что тип выражения каждого элемента можно присваивать типу элемента массива.

Сохраняется ли информация о типе Java во время выполнения? Или все забыто после компиляции, и если мы делаем (Double) элементы [0], мы просто будем следовать указателю и интерпретировать эти 8 байтов как double, что бы это ни было?

Некоторая информация хранится во время выполнения, но не статические типы отдельных элементов. Вы можете сказать это, глядя на формат файла класса.

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

Кроме того, люди все равно не должны писать код приложения.

Ответ 4

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

Для массивов Java сохраняет информацию о типе во время выполнения. В большинстве случаев компилятор будет улавливать ошибки типа для вас, но бывают случаи, когда вы будете сталкиваться с ArrayStoreException при попытке сохранить объект в массиве, но тип не соответствует (и компилятор не выполнял Лови). Спецификация языка Java дает следующий пример:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpa действителен, так как ColoredPoint является подклассом Point, но pa[0] = new Point() недействителен.

Это противоречит родовым типам, где информация о типе сохраняется во время выполнения. Компилятор при необходимости вставляет инструкции checkcast.

Это различие в типизации для типичных типов и массивов делает его часто непригодным для смешивания массивов и общих типов.

Ответ 5

В теории есть накладные расходы. Однако современные JVM являются умными. Каждая реализация различна, но неразумно предположить, что может существовать реализация, которая JIT оптимизировала прохождение кастинга, когда она могла гарантировать, что конфликта никогда не будет. Что касается конкретных JVM, я не могу сказать вам. Должен признаться, что я хотел бы знать специфику оптимизации JIT самостоятельно, но это для инженеров JVM, о которых нужно беспокоиться.

Мораль этой истории - сначала написать понятный код. Если вы столкнулись с замедлением, профилем и определите свою проблему. Коэффициенты хороши, что это не будет из-за кастинга. Никогда не жертвуйте чистым, безопасным кодом, пытаясь оптимизировать его, пока вы не знаете, что вам нужно.