Приведение типов может быть явное и автоматическое.
При явном приведении типов сама операция приведения задается явным образом.
При автоматическом приведении типов нужно, чтобы выполнялись два условия:
Явное приведение типов позволяет осуществлять присвоение несовместимых типов. Общая форма явного приведения типов имеет вид:
(целевой_тип) значениецелевой_тип – это тип, в который нужно привести указанное значение .
Примеры явного приведения типов.
// явное приведение типов в выражениях byte b; int a; double d; float f; d = -39.9203; a = (int )d; // a = -39 f = (float )d; // f = -39.9203 b = (byte )d; // b = -39 d = 302930932; b = (byte )d; // b = -12 - урезание значения a = -27; b = (byte )a; // b = -27Пример 1 . Автоматическое приведение целочисленных типов.
// автоматическое приведение целочисленных типов int a; byte b; short sh; b = -23; a = b; // a = -23 - автоматическое приведение типов sh = -150; a = sh; // a = -150 long l = 200; // Ошибка: "Type mismatch: cannot convert from long to int" // a = l; l = b; // l = -23 l = sh; // l = -150 char c = "Z" ; a = c; // a = 90 - код символа "Z" boolean b1 = false ; //a = b1; - ошибка, типы несовместимыеПример 2 . Автоматическое приведение типов с плавающей запятой.
// автоматическое приведение типов с плавающей запятой float f; double d; f = 3.409033f; d = f; // d = 3.409033Пример 3 . Автоматическое приведение смешанных типов. Такой случай возможен, если переменной типа с плавающей запятой присваивается значение переменной целочисленного типа.
// автоматическое приведение смешанных типов float f; double d; a = 28; d = a; // d = 28.0 f = a; // f = 28.0 // Ошибка: Type mismatch: cannot convert from float to int // a = f;Автоматическое продвижение типов происходит в выражениях. При этом, значение, которое фигурируют в выражениях, автоматически продвигаются к типам с большими диапазонами значений.
При автоматическом продвижении типов в выражениях:
Вышеприведенный пример работает корректно, так как:
В вышеприведенном примере значения 1000 превышает диапазон значений типа byte. Сначала число 1000 приводится к типу int. Но результат
1000 / 20 = 50приводится к типу byte и может корректно поместиться в переменной b .
Если написать так:
byte b; b = 100000 / 20; // ошибка, так как результат не помещается в тип byteто выйдет ошибка компиляции с выводом сообщения:
В этом случае результат не помещается в тип byte:
100000 / 20 = 5000Тогда это число (5000) автоматически становится типом int и компилятор выдаст сообщение об ошибке.
Если сделать явное приведение типов:
byte b; b = (byte ) (100000 / 20); // b = -120то в этом случае результат 5000 типа int превращается в тип byte. Как известно, переменная типа int занимает 32 бита, а переменная типа byte занимает 8 бит. Значение переменной типа int урезается. И имеем то, что имеем (b = -120 ).
Вышеприведенные примеры относятся и к переменным типов short и char.
В вышеприведенном примере в выражении используется переменная d типа int . Поэтому компилятор выдаст сообщение об ошибке:
Type mismatch: cannot convert from int to byteЭто означает, что результат есть типа int (а не byte ) даже если значение помещается в диапазон значений типа byte. Поскольку в выражении используется переменная-операнд d типа int.
Если осуществить явное приведение типов, то результат будет корректным:
// продвижение типов в выражениях // byte -> int byte b; int d; d = 20; b = (byte )(1000 / d); // b = 50 - работает корректноПример продвижения типов из int в long. Если один из операндов есть типа long, то все выражение продвигается к типу long.
int d; long l; d = 10000 * 200; // работает, d = 2000000 // Ошибка! Type mismatch: cannot convert from long to int // d = 1L * 2L; - операнды 1L и 2L есть типа long l = 100; // ошибка, один из операндов есть типа long // d = l * 2;Как видно из примера, если один из операндов есть типа long , то все выражение становится типа long .
Предыдущий оратор достаточно полно описал нисходящее преобразование, но восходящее (на мой взгляд) требует дополнительных пояснений, так как вопрос очень популярен и интересен.
Каким образом работает явное приведение типов
В вашем примере показано восходящее преобразование (Upcasting ):
List coll = new ArrayList();
На русский язык переводится так: создай ворону, типа птицы. Создай динамический массив, типа лист.
В большинстве ситуаций восходящее преобразование совершенно не нужно .
Однако, приведение типов работает на собеседованиях, когда вам дают вопросы на наследование. К примеру, сайт quizful.net вообще содержит в себе множество вопросов на приведение типов. Поэтому разъясню особенности, которые знаю.
Итак, в вышеприведенном примере мы создали объект типа ArrayList , а ссылка типа List . Запомните аксиомы для этого способа:
1. Ссылку можно указать на любого родителя. Даже очень давнего. То есть, можно привести ссылку coll даже к типу Object . Компилятор пропустит любую ссылку на класс родителя, или родителя-родителя, или родителя-родителя...родителя
2. Обращение к полю - всегда идёт возврат поля ссылки, не поля объекта. Если такого поля нет в классе-ссылке будет ошибка компиляции.
Class A{ int x = 2; //Поле родителя } Class B extends A { int x = 3; //Поле которое должно перекрыть родительское int y = 5; //Поле, которого нет в родительском классе. } Class Test{ public static void main(String args) { A ab = new B(); //Восходящее преобразование System.out.println("Int x = " + ab.x); } }
Вернет Int x = 2 . Если вы попробуете обратиться к полю объекта:
System.out.println("Int y = " + ab.y); //Ошибка компилляции
Ваш компилятор скажет, что вы не правы, так как он по ссылке (A ab) не видит такого поля. Всё вышесказанное сохраняет силу, даже если ваши поля пометить модификаторами static.
3. Обращение к нестатическому методу: в этом случае вернёт метод объекта. Но при обращении к статическому методу - возвращает метод ссылки.
Class D{ public void doSome(){ //Нестатический метод System.out.println("Nonstatic doSome from D"); } public static void Action(){ //Статический метод System.out.println("static Action from D"); } } public class Okey extends D{ public void doSome(){ System.out.println("doSome from Okey"); } public static void Action(){ System.out.println("static Action from Okey"); } public static void main(String args) { D o=new Okey(); o.doSome(); //Из класса Okey o.Action(); //Из класса D } }
Nonstatic doSome from Okey
static Action from D
Разгадка проста, нестатический метод - это метод объекта, статический - метод класса. Когда мы вызываем не статический метод - компилятор понимает так: летай как ворона. Когда мы вызываем статический - буквально, летай как птица.
4. Если идёт вызов метода, который описан в классе объекта, но не описан в классе ссылки - пойдёт ошибка компилляции. Потому что, вызов метода происходит по ссылке:
Class A {} Class B extends A { void someMethod(){}; public static void main(String args) { A ab = new B(); ab.someMethod(); //Ошибка компилляции. } }
5. Конструктор объекта (при создании командой new) работает также, как если давать ссылку на свой класс.
Каждое выражение в Java имеет тип, который определяется структурой выражения и типами составляющих его операндов (констант, переменных и методов). Однако, иногда нам может потребоваться явное преобразование выражения в другой тип. Кроме того, в некоторых ситуациях исполняющая система Java сама неявно проводит такие преобразования.
Преобразование типа Т 1 в тип T 2 позволяет выражению типа T 1 трактоваться в период компиляции как выражение типа T 2 . В одних случаях это чисто синтаксическая конструкция, не влияющая на генерируемый код, в других преобразование типа требует дополнительных действий в период выполнения по изменению значения выражения или дополнительных проверок правильности применяемого преобразования. Примеры:
Расширяющие преобразования чисел это преобразования числового типа в "больший" числовой тип, которые считаются безопасными, т. к. не приводят к потере величины преобразуемого значения. Такими преобразованиями в Java являются:
В действительности, преобразование целого значения в плавающее может привести к потере точности, т. е. к потере значащих цифр. Так, следующий пример
Class Test { public static void main(String args) { int bigNumber = 1234567890; float approximate = bigNumber; System.out.println(approximate); } }
выведет на экран строку 1234567936 . Это связано с тем, что при преобразовании int в float результирующее значение равно 1.2345679E9 из-за того, что мантисса чисел типа float вмещает только 8 десятичных цифр (здесь для правильной работы следует использовать преобразование к типу double ). Тем не менее, исполняющая система никогда не генерирует ошибок при выполнении перечисленных преобразований.
Сужающие преобразования чисел это преобразования числового типа в "меньший" числовой тип, которые могут привести как к потере величины, так и к потере точности. Такими преобразованиями в Java являются:
Мы не будем здесь подробно рассматривать правила, по которым происходят эти преобразования, поскольку интуитивно они понятны, а формально достаточно громоздки. При их применении важно помнить, что Java, в отличие от других языков, не генерирует ошибок при переполнении (overflow) или потере значения (underflow), поэтому контроль за корректностью преобразований полностью ложится на программиста.
Расширяющие преобразования ссылок это преобразования производных ссылочных типов в типы их предков, которые не требуют никаких действий на этапе исполнения и никогда не генерируют ошибок. Такими преобразованиями в Java являются:
Сужающие преобразования ссылок это преобразования производных ссылочных типов в типы их потомков. Эти преобразования требуют проверки своей легитимности на этапе исполнения и могут генерировать исключение ClassCastException . Такими преобразованиями в Java являются:
Любое выражение в Java, включая null , может быть преобразовано в тип String .
Следующие преобразования типов в Java запрещены:
Преобразование при присваивании происходит, когда значение выражения присваивается переменной. При этом тип выражения преобразуется к типу переменной. При присваивании всегда возможны расширяющие преобразования типов (как числовых, так и ссылочных). Сужающее преобразование возможно только при соблюдении следующих условий:
Например, оператор byte x = 123; допустим, поскольку константа 123 (имеющая тип int ) лежит в диапазоне допустимых значений типа byte .
Если тип выражения не может быть преобразован к типу переменной, то компилятор генерирует ошибку. В остальных случаях мы говорим, что тип выражения совместим по присваиванию с типом переменной. Так, следующий фрагмент
Short s = 123; char c = s; // генерирует ошибку компиляции
приведет к генерации ошибки, поскольку типы char и short несовместимы по присваиванию согласно данных выше определениям (первый реализован 16-битовыми словами без знака, а второй со знаком).
Преобразование аргументов метода происходит, когда фактические значения аргументов преобразуется к типу параметров метода или конструктора при его вызове. При этом всегда возможны расширяющие преобразования типов (как числовых, так и ссылочных) и недопустимы сужающие преобразования. Причины последнего запрета можно пояснить следующим примером:
Class Test { static int m(byte a, int b) { return a + b; } static int m(short a, short b) { return a - b; } public static void main(String args) { System.out.println(m(1, 2) ); // генерирует ошибку компиляции } }
Здесь класс Test содержит два одноименных метода, которые различаются только типами параметров. Если бы сужающие преобразования аргументов были в Java разрешены, то исполняющей системе пришлось бы определять, к какому из этих методов относится вызов m(1, 2) . Чтобы избежать подобных двусмысленностей, разработчики языка решили проблему радикально: они запретили подобные вызовы методов. В данной ситуации для вызова, к примеру, первого метода мы должны явно указать тип первого операнда (второй по умолчанию уже имеет тип int ), а именно m((byte)1, 2) .
Преобразование в строку происходит только в одном случае: когда бинарная операция + применяется к двум операндам, один из которых имеет тип String . В этой ситуации второй операнд также преобразуется к типу String , и результатом операции является конкатенация полученных строк. Подробнее этот процесс описан в гл. 5.14 .
Явное преобразование типа происходит, когда к операнду явно применяется операция приведения типа (type cast). В этой ситуации могут применяться все описанные выше виды преобразований типов, кроме преобразования в строку. Попытка явного преобразования к типу, отмеченная выше, как запрещенная, вызовет ошибку компиляции. Кроме того, на этапе выполнения возможна генерация исключения ClassCastException, если заданное преобразование недопустимо.
Преобразование типов в процессе вычисления числовых выражений имеет ряд особенностей. Они сводятся к двум случаям: преобразования операндов в унарных операциях и в бинарных операциях.
Перед выполнением унарной операции:
Перед выполнением бинарной операции:
Иногда возникают ситуации, когда у вас есть величина какого-то определенного типа, а вам нужно ее присвоить переменной другого типа. Для некоторых типов это можно проделать и без приведения типа, в таких случаях говорят об автоматическом преобразовании типов. В Java автоматическое преобразование возможно только в том случае, когда точности представления чисел переменной-приемника достаточно для хранения исходного значения. Такое преобразование происходит, например, при занесении литеральной константы или значения переменной типа byte или short в переменную типа int. Это называется расширением (widening ) или повышением (promotion ), поскольку тип меньшей разрядности расширяется (повышается) до большего совместимого типа. Размера типа int всегда достаточно для хранения чисел из диапазона, допустимого для типа byte, поэтому в подобных ситуациях оператора явного приведения типа не требуется. Обратное в большинстве случаев неверно, поэтому для занесения значения типа int в переменную типа byte необходимо использовать оператор приведения типа. Эту процедуру иногда называют сужением (narrowing ), поскольку вы явно сообщаете транслятору, что величину необходимо преобразовать, чтобы она уместилась в переменную нужного вам типа. Для приведения величины к определенному типу перед ней нужно указать этот тип, заключенный в круглые скобки. В приведенном ниже фрагменте кода демонстрируется приведение типа источника (переменной типа int) к типу приемника (переменной типа byte). Если бы при такой операции целое значение выходило за границы допустимого для типа byte диапазона, оно было бы уменьшено путем деления по модулю на допустимый для byte диапазон (результат деления по модулю на число - это остаток от деления на это число),
int а = 100;
byte
b = (byte) а;
2.2.1. Автоматическое преобразование типов в выражениях
При вычислениях значения выражения точность, требуемая для хранения промежуточных результатов, зачастую должна быть выше, чем требуется для представления окончательного результата,
byte а
= 40;
byte b =
50;
byte с
= 100;
int d = a*
b / с
;
Результат промежуточного выражения (а*b) вполне может выйти за диапазон допустимых для типа byte значений. Именно поэтому Java автоматически повышает тип каждой части выражения до типа int, так что для промежуточного результата (а* b) хватает места.
Автоматическое преобразование типа иногда может оказаться причиной неожиданных сообщений транслятора об ошибках. Например, показанный ниже код, хотя и выглядит вполне корректным, приводит к сообщению об ошибке на фазе трансляции. В нем мы пытаемся записать значение 50*2, которое должно прекрасно уместиться в тип byte, в байтовую переменную. Но из-за автоматического преобразования типа результата в int мы получаем сообщение об ошибке от транслятора - ведь при занесении int в byte может произойти потеря точности.
byte b =
50;
b = b* 2:
^
Incompatible type for =. Explicit cast
needed to convert int to byte.
(Несовместимый тип для
=. Необходимо явное преобразование
int в
byte)
Исправленный текст:
byte
b = 50;
b = (byte) (b* 2);
что приводит к занесению в b правильного значения 100.
Если в выражении используются переменные типов byte, short и int, то во избежание переполнения тип всего выражения автоматически повышается до int. Если же в выражении тип хотя бы одной переменной - long, то и тип всего выражения тоже повышается до long. Не забывайте, что все целые литералы, в конце которых не стоит символ L (или 1), имеют тип int.
Если выражение содержит операнды типа float, то и тип всего выражения автоматически повышается до float. Если же хотя бы один из операндов имеет тип double, то тип всего выражения повышается до double. По умолчанию Java рассматривает все литералы с плавающей точкой как имеющие тип double. Приведенная ниже про1рамма показывает, как повышается тип каждой величины в выражении для достижения соответствия со вторым операндом каждого бинарного оператора.
class
Promote {
public
static void main (String args ) {
byte b= 42;
char с
= "a’;
shorts =
1024;
int i =
50000;
float f = 5.67f;
doubled =.1234;
double
result = (f*b) + (i/ c) - (d* s);
System,
out. println ((f* b)+ "+ "+ (i / c)+ " -" + (d* s));
System,
out. println ("result = "+ result); }
}
Подвыражение f*b - это число типа float, умноженное на число типа byte, поэтому его тип автоматически повышается до float. Тип следующего подвыражения i / с (int, деленный на char) повышается до int. Аналогично этому тип подвыражения d*s (double, умноженный на short) повышается до double. На следующем шаге вычислений мы имеем дело с тремя промежуточными результатами типов float, int и double. Сначала при сложении первых двух тип int повышается до float и получается результат типа float. При вычитании из него значения типа double тип результата повышается до double. Окончательный результат всего выражения - значение типа double.
Теперь, когда мы познакомились со всеми простыми типами, включая целые и вещественные числа, символы и логические переменные, давайте попробуем собрать всю информацию вместе. В приведенном ниже примере создаются переменные каждого из простых типов и выводятся значения этих переменных.
class
SimpleTypes {
public
static void main(String args ) {
byte b = 0x55;
short s = 0x55ff;
int i =
1000000;
long l = 0xffffffffL;
char с
= ’a’;
float f= .25f;
double d =
.00001234;
boolean
bool = true;
System.out.println("byte
b = " + b);
System.out.println("short
s = " +s);
System.out.println("int
i =” + i);
System.out.println("long
1 = " + l);
System.out.println("char
с
=” + с
);
System.out.println("float
f = " + f);
System.out.println("double
d = " + d);
System.out.println("boolean
bool =” + bool); }
}
Запустив эту программу, вы должны получить результат, показанный ниже:
byte b = 85
shorts =
22015
int i =
1000000
long 1 =
4294967295
char с
= a
float f =
0.25
double
d=1.234e-005
boolean
bool = true
Обратите внимание на то, что целые числа печатаются в десятичном представлении, хотя мы задавали значения некоторых из них в шестнадцатиричном формате.