Сколько обработки и использования памяти занимает кастинг в Java?

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

Сколько системных ресурсов при этом использует:

objectName.functionOne();
((SubClass) objectName).functionOther();

Это лучше, чем:

SuperClass objectA = (SuperClass) getSameInstance();
SubClass objectB = getSameInstance();
objectA.functionOne();
objectB.functionOther();

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

((SubClass) objectName).functionOther();

Однако стоит ли оно того?

Спасибо,
Грэ

ОБНОВИТЬ:

В моем вопросе были некоторые неясные моменты. По сути, у меня есть суперкласс, который я использую в большой функции. Он работает с тремя подклассами. Какой-то суперкласс работает как хотелось бы. Однако я столкнулся с препятствием в нескольких местах, где мне пришлось использовать функцию одного из трех различных подклассов; функция, которая находится только в одном из подклассов.

Я мог бы просто:

SuperClass instanceRef;
SubClass instanceRef2;

instanceRef.etc()
instanceRef.etc2()
instanceRef.etc3()
instanceRef2.specialSubClassOnlyCall();
instanceRef2.specialSubClassOnlyCall2();

или я мог бы:

SuperClass instanceRef;

instanceRef.etc()
instanceRef.etc2()
instanceRef.etc3()
((SpecialSubClass)instanceRef).specialSubClassOnlyCall();
((SpecialSubClass)instanceRef).specialSubClassOnlyCall2();

Однако я не знаю, что более эффективно.

ОБНОВЛЕНИЕ 2:

Вот пример, чтобы показать вам, о чем я говорю:

class Shape
Triangle extends Shape
Square extends Shape
Circle extends Shape
Cube extends Shape

Пример с двумя указателями: (Обратной стороной является дополнительный указатель.)

Shape pointer1 = (Shape) getSomeRandomShape();
Cube pointer2 = null;

pointer1.getWidth();
pointer1.getHeight();
pointer1.generalShapeProp();
pointer1.generalShapeProp2();
pointer1.generalShapeProp3();

if(sure_its_cube)
{
   pointer2 = (Cube) pointer1;
   pointer2.getZAxis();
   pointer2.getOtherCubeOnlyThing();
   pointer2.getOtherCubeOnlyThing2();
   pointer2.getOtherCubeOnlyThing3();
   pointer2.getOtherCubeOnlyThing4();
}

Или я мог сделать это так. (Даунсайд куча кастингов.)

Shape pointer1 = (Shape) getSomeRandomShape();

pointer1.getWidth();
pointer1.getHeight();
pointer1.generalShapeProp();
pointer1.generalShapeProp2();
pointer1.generalShapeProp3();

if(sure_its_cube)
{
   ((Cube)pointer1).getZAxis();
   ((Cube)pointer1).getOtherCubeOnlyThing();
   ((Cube)pointer1).getOtherCubeOnlyThing2();
   ((Cube)pointer1).getOtherCubeOnlyThing3();
   ((Cube)pointer1).getOtherCubeOnlyThing4();
}

Так что, пять приведений хуже одного лишнего указателя? Что это было шесть забросов или 20? На одно приведение хуже, чем указатель.

Грэ


person GC_    schedule 19.10.2010    source источник
comment
Сохранять два - не лучшая практика, можете ли вы опубликовать свою актуальную проблему, чтобы мы могли предложить лучший дизайн. Я не голосовал против   -  person jmj    schedule 19.10.2010
comment
Приведение к суперклассу должно быть бесплатным. А о скольких указателях здесь идет речь? Это всего лишь несколько локальных переменных или тысячи?   -  person CodesInChaos    schedule 19.10.2010
comment
Стоит прочитать ответы на этот вопрос о накладных расходах на кастинг - stackoverflow.com/questions/2170872/   -  person Tom Castle    schedule 19.10.2010
comment
stackoverflow.com/questions/ 2170872 / - это то, что вы ищете.   -  person Robert Munteanu    schedule 19.10.2010
comment
Я выбрал Петера Торёка, потому что он написал: «Оптимизируйте только тогда, когда вы знаете, что вам нужно», и доказал (с помощью профилировщика), где узкое место находится в вашем коде, что я считаю хорошим советом.   -  person GC_    schedule 25.10.2010


Ответы (6)


Судя по вашему фрагменту кода, getSameInstance() возвращает SubClass. В этом случае простейшим решением будет

SubClass objectB = getSameInstance();
objectB.functionOne();
objectB.functionOther();

Никаких забросов, не беспокойтесь :-)

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

Дополнительное примечание: вызов одного и того же метода дважды может быть проблематичным, если метод тяжелый (в этом случае вы фактически делаете свой код медленнее, а не быстрее) или имеет побочные эффекты (в этом случае поведение вашей программы может заметно измениться) .

И, кстати, в этой строке вашего фрагмента кода

SuperClass objectA = (SuperClass) getSameInstance();

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

Обновлять

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

public Shape getSomeRandomShape();

это правильно?

Это, кстати, означает, что ваш 2-й исходный фрагмент кода (с вызовом getSameInstance()) неверен - приведение должно быть противоположным. Вам удалось запутать меня этим, так что вы получили запутанный ответ :-)

Также в последних примерах вам не нужно приводить возвращаемое значение к (Shape), поскольку это уже Shape. Приведение просто загромождает ваш код. Точно так же множество приведений во втором примере UPDATE 2 затрудняют чтение кода. Я бы предпочел первую версию с двумя ссылками (не указателей, кстати - в Java указателей нет).

И меня здесь волнует только читабельность кода - как уже отмечали другие, вам действительно не следует тратить время на размышления о стоимости приведений. Просто сосредоточьтесь на том, чтобы сделать свой код как можно более простым и читабельным. Скорее всего, вы никогда не заметите ни малейшей разницы между производительностью двух фрагментов кода, которые вы показываете выше (если, может быть, они не выполняются в тесном цикле миллионы раз, но тогда, я думаю, JIT оптимизирует все равно отбрасывает). Однако вы заметите разницу во времени, чтобы понять один фрагмент кода по сравнению с другим, для кого-то, кто плохо знаком с этим кодом - или для кого-то, кто уже забыл детали, что может быть вами примерно через 6 месяцев.

person Péter Török    schedule 19.10.2010
comment
+1 - на мой взгляд, это лучший ответ, вам нужен только указатель SubClass, обо всем остальном позаботится наследование - person mikera; 19.10.2010
comment
getSameInstance получит подкласс, а не суперкласс. - person GC_; 19.10.2010
comment
@Grae, хорошо, тогда мое предложение выше работает, и вам действительно не нужно никаких бросков. - person Péter Török; 20.10.2010
comment
Однако существует более одного типа подкласса. - person GC_; 20.10.2010
comment
@Grae, теперь это сбивает с толку. Обратите внимание, что статический тип возвращаемого объекта может отличаться от его динамического типа. Например. метод может быть объявлен как Object getSomeObject() и может возвращать String, тогда как статический тип возвращаемого объекта - Object, динамический - String. Я все время имел в виду статический тип - вы говорите о динамическом типе? Или вы имеете в виду, что у SubClass есть дополнительные подклассы? Пожалуйста, поставьте точную подпись getSameInstance() для уточнения. - person Péter Török; 20.10.2010
comment
Я думаю, что вам не хватает одного подкласса. Я собираюсь добавить к вопросу еще кое-что. - person GC_; 21.10.2010

Делайте то, что делает код более понятным, забудьте о микрооптимизациях.

person Michael Borgwardt    schedule 19.10.2010
comment
Просто кажется, что я должен знать, сколько ресурсов использует понижающий преобразователь, прежде чем заменять его выделением другого указателя. Я понимаю, что он маленький, но мне кажется, что я должен знать, что делаю. Для меня это как O-big. Предположим, у меня есть 10 отрицательных результатов, стоит ли это одного лишнего указателя? - person GC_; 19.10.2010
comment
@Grae - Этот совет не о том, что я должен знать, что я делаю, он о том, что кто-то другой должен понимать, что вы сделали - person Ivan; 19.10.2010
comment
@Grae: Что вам следует сделать, так это перестать даже думать об этом! Серьезно, такого рода вещи - пустая трата времени мозга, которое было бы гораздо лучше использовать, чтобы сделать код правильным и легким для понимания. - person Michael Borgwardt; 19.10.2010
comment
Я полностью согласен, но у меня есть твердое мнение о том, что более читабельно! Мне очень не нравится одинаковый код, который повторяется несколько раз. Каждый раз, когда вы смотрите на него, вы должны снова убедиться, что он действительно идентичен. Поэтому я почти всегда использую первый вариант с дополнительной переменной-указателем. Это также устраняет любые сомнения в том, что повторяющиеся выражения, начинающиеся как o.getThis().getThat().getOther()...., действительно работают с одним и тем же объектом, не повторяют дорогостоящие вычисления и не имеют побочных эффектов несколько раз. - person Olaf Seibert; 25.11.2015

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

Просто взглянул на код JVM, два подхода в основном эквивалентны, они реализованы как

CHECKCAST Main$B
INVOKEVIRTUAL Main$B.mySubMethod()V

так что небольшие накладные расходы присутствуют, но они одинаковы в обоих случаях (так как вам все равно нужно сделать снижение).

person Jack    schedule 19.10.2010
comment
Приведение может привести к проверке типа во время выполнения, которая включает некоторые (по общему признанию небольшие) затраты времени выполнения. - person mikera; 19.10.2010
comment
-1, это выполняется во время выполнения (если операция не стерта компилятором). Как вы думаете, вы получите ClassCastException? - person Mark Peters; 19.10.2010
comment
да, но в обоих случаях это делается одинаково! - person Jack; 19.10.2010
comment
Это не меняет того факта, что это происходит во время выполнения. - person Mark Peters; 19.10.2010
comment
Я действительно исправлял свой ответ :) - person Jack; 19.10.2010
comment
В последнее время я слишком привык вводить логический вывод, извините за мою первоначальную ошибку :( - person Jack; 19.10.2010
comment
Думаю, я мог бы избежать необходимости несколько раз выполнять приведение вниз, имея дополнительный указатель. Таким образом, я выполняю понижающее преобразование один раз, а не каждый раз, когда использую функцию подкласса. - person GC_; 19.10.2010

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

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

person Yishai    schedule 19.10.2010
comment
в случае перегрузки метода вы все равно можете бесплатно привести параметр к родительскому типу. Статическая ссылка изменяется, но преобразование отбрасывается, как и любое преобразование, которое статически известно как успешное. - person Mark Peters; 19.10.2010

Просто выберите любое решение, которое легче читать и поддерживать чистоту (вероятно, первое решение). При таком низком уровне оптимизации это действительно того не стоит, за исключением крайних случаев, когда вам действительно нужно дополнительное повышение производительности на 0,01%.

На самом деле вам не нужно приводить подкласс к ссылке на суперкласс (второе решение в списке), только наоборот. При преобразовании суперкласса в подкласс обычно рекомендуется проверять его тип с помощью instanceof перед преобразованием, а не делать предположения.

person Ophidian    schedule 19.10.2010

Допустим, у вас есть 64-битная машина, и она экономит 8 байтов ресурсов. Допустим, вы потратили 10 минут на размышления об этом. Было ли хорошо потрачено ваше время?

1 ГБ увеличивает стоимость сервера примерно на 100 долларов, поэтому 8 байтов стоят около 0,0000008 долларов.

10 минут вашего времени стоят примерно 1 доллар минимальной заработной платы.

Ресурс многоразовый, вашего времени - нет. Что еще более важно, время любого чтения / поддержки вашего кода, вероятно, будет еще дороже, и поэтому простота / ясность намного важнее.

person Peter Lawrey    schedule 19.10.2010
comment
Теперь предположим, что этот код повторяется несколько тысяч раз в моей кодовой базе. А теперь допустим, что он работает на миллиард устройствах по всему миру. Теперь оно того стоит? - person Andrew Rose; 02.01.2014
comment
@AndrewRose Если вы экономите 8 * 3000 * один миллиард, это ~ 24 ТБ, так что да, это будет иметь значение. Возможно, стоит потратить около 240 000 долларов, за исключением того, что вы должны учитывать, что вы будете платить за реконструкцию, тогда как вы не будете платить за память, если это не проблема для ваших конечных пользователей. т.е. перепланировка стоит вам денег, памяти может и нет. - person Peter Lawrey; 02.01.2014