Опубликован: 11.12.2003 | Уровень: специалист | Доступ: платный
Лекция 7:

Преобразование типов

< Лекция 6 || Лекция 7: 1234 || Лекция 8 >

Применение приведений

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

Такие ситуации могут быть сгруппированы следующим образом.

  • Присвоение значений переменным (assignment). Не все переходы допустимы при таком преобразовании - ограничения выбраны таким образом, чтобы не могла возникнуть ошибочная ситуация.
  • Вызов метода. Это преобразование применяется к аргументам вызываемого метода или конструктора. Допускаются почти те же переходы, что и для присвоения значений. Такое приведение никогда не порождает ошибок. Так же приведение осуществляется при возвращении значения из метода.
  • Явное приведение. В этом случае явно указывается, к какому типу требуется привести исходное значение. Допускаются все виды преобразований, кроме приведений к строке и запрещенных. Может возникать ошибка времени исполнения программы.
  • Оператор конкатенации производит преобразование к строке своих аргументов.
  • Числовое расширение (numeric promotion). Числовые операции могут потребовать изменения типа аргумента(ов). Это преобразование имеет особое название - расширение (promotion), так как выбор целевого типа может зависеть не только от исходного значения, но и от второго аргумента операции.

Рассмотрим все случаи более подробно.

Присвоение значений

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

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

// пример вызовет ошибку компиляции

// примитивное значение нельзя
// присвоить объектной переменной
Parent p = 3;

// приведение к классу-"обертке" 
// также запрещено
Long a=5L;

// универсальное приведение к строке
// возможно только для оператора +
String s=true;

Далее, если сочетание этих двух типов образует расширение (примитивных или ссылочных типов), то оно будет осуществлено автоматически, неявным для разработчика образом:

int i=10;
long a=i;
Child c = new Child();
Parent p=c;

Если же сочетание оказывается сужением, то возникает ошибка компиляции, такой переход не может быть проведен неявно:

// пример вызовет ошибку компиляции
int i=10;
short s=i;   // ошибка! сужение!
Parent p = new Child();
Child c=p;   // ошибка! сужение!

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

int i=10;
short s=(short)i;
Parent p = new Child();
Child c=(Child)p;

Более подробно явное сужение рассматривается ниже.

Здесь может вызвать удивление следующая ситуация, которая не порождает ошибок компиляции:

byte b=1;
short s=2+3;
char c=(byte)5+'a';

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

Однако все эти примеры корректны. Для удобства разработчика компилятор проводит дополнительный анализ при присвоении значений переменным типа byte, short и char. Если таким переменным присваивается величина типа byte, short, char или int, причем ее значение может быть получено уже на момент компиляции, и оказывается, что это значение укладывается в диапазон типа переменной, то явного приведения не требуется. Если бы такой возможности не было, пришлось бы писать так:

byte b=(byte)1;   
   // преобразование необязательно
short s=(short)(2+3);   
   // преобразование необязательно
char c=(char)((byte)5+'a');   
   // преобразование необязательно

// преобразование необходимо, так как
// число 200 не укладывается в тип byte
byte b2=(byte)200;

Вызов метода

Это приведение возникает в случае, когда вызывается метод с объявленными параметрами одних типов, а при вызове передаются аргументы других типов. Объявление методов рассматривается в следующих лекциях курса, однако такой простой пример вполне понятен:

// объявление метода с параметром типа long
void calculate(long l) {
   ...
}

void main() {
   calculate(5);
}

Как видно, при вызове метода передается значение типа int, а не long, как определено в объявлении этого метода.

Здесь компилятор предпринимает те же шаги, что и при приведении в процессе присвоения значений переменным. Если типы образуют запрещенное преобразование, возникнет ошибка.

// пример вызовет ошибку компиляции

void calculate(long a) {
   ...
}

void main() {
   calculate(new Long(5));   
   // здесь будет ошибка
}

Если сужение, то компилятор не сможет осуществить приведение и потребуются явные указания.

void calculate(int a) {
   ...
}

void main() {
   long a=5;
   // calculate(a);   
   // сужение! так будет ошибка.
   calculate((int)a);   // корректный вызов
}

Наконец, в случае расширения, компилятор осуществит приведение сам, как и было показано в примере в начале этого раздела.

Надо отметить, что, в отличие от ситуации присвоения, при вызове методов компилятор не производит преобразований примитивных значений от byte, short, char или int к byte, short или char. Это привело бы к усложнению работы с перегруженными методами. Например:

// пример вызовет ошибку компиляции

// объявляем перегруженные методы
// с аргументами (byte, int) и (short, short)
int m(byte a, int b) { return a+b; }
int m(short a, short b) { return a-b; }

void main() {
   print(m(12, 2));   // ошибка компиляции!
}

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

Аналогичное преобразование потребуется при возвращении значения из метода, если тип результата и заявленный тип возвращаемого значения не совпадают.

long get() {
   return 5;
}

Хотя в выражении return указан целочисленный литерал типа int, во всех местах, где будет вызван этот метод, будет получено значение типа long. Для такого преобразования действуют те же правила, что и для присвоения значения.

В заключение рассмотрим пример, включающий в себя все рассмотренные случаи преобразования:

short get(Parent p) {
   return 5+'A';   
   // приведение при возвращении значения
}

void main() {
   long a =  5L;
   // приведение при присвоении значения
   get(new Child());   
   // приведение при вызове метода
}

Явное приведение

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

(byte)5
(Parent)new Child()
(Flat)getCity().getStreet().getHouse().getFlat()

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

Child c=new Child();
// Child2 c2=(Child2)c;   
// запрещенное преобразование
Parent p=c;   // расширение
Child2 c2=(Child2)p;   // сужение

Такой код будет успешно скомпилирован, однако, разумеется, при исполнении он всегда будет генерировать ошибку в последней строке. "Обманывать" компилятор смысла нет.

Оператор конкатенации строк

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

Это одно из свойств, выделяющих класс String из общего ряда.

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

Небольшой пример:

int i=1;
double d=i/2.;
String s="text";
print("i="+i+", d="+d+", s="+s);

Результатом будет:

i=1, d=0.5, s=text

Числовое расширение

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

Различают унарное и бинарное числовое расширение.

Унарное числовое расширение

Это преобразование расширяет примитивные типы byte, short или char до типов int по правилам расширения примитивных типов.

Унарное числовое расширение может выполняться при следующих операциях:

  • унарные операции + и - ;
  • битовое отрицание ~ ;
  • операции битового сдвига <<, >>, >>>.

Операторы сдвига имеют два аргумента, но они расширяются независимо друг от друга, поэтому данное преобразование является унарным. Таким образом, результат выражения 5<<3L имеет тип int. Вообще, результат операторов сдвига всегда имеет тип int или long.

Примеры работы всех этих операторов с учетом расширения подробно рассматривались в предыдущих лекциях.

Бинарное числовое расширение

Это преобразование расширяет все примитивные числовые типы, кроме double, до типов int, long, float, double по правилам расширения примитивных типов. Бинарное числовое расширение происходит при числовых операторах, имеющих два аргумента, по следующим правилам:

  • если любой из аргументов имеет тип double, то и второй приводится к double ;
  • иначе, если любой из аргументов имеет тип float, то и второй приводится к float ;
  • иначе, если любой из аргументов имеет тип long, то и второй приводится к long ;
  • иначе оба аргумента приводятся к int.

Бинарное числовое расширение может выполняться при следующих операциях:

  • арифметические операции +, -, *, /, % ;
  • операции сравнения <, <=, >, >=, ==, != ;
  • битовые операции &, |, ^ ;
  • в некоторых случаях для операции с условием ?:.

Примеры работы всех этих операторов с учетом расширения подробно рассматривались в предыдущих лекциях.

< Лекция 6 || Лекция 7: 1234 || Лекция 8 >
Вадим Кудаев
Вадим Кудаев

Добрый день! Начал проходить курс "Программирование на Java". Как я понимаю,курс создавался приблизительно в 2015 году. Не потерял ли данный курс свою актуальность? Стоит ли проходить его в 2023 году, или же лучше найти что-то более новое?

Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Данила Некрасов
Данила Некрасов
Россия, Пермь, ПНИПУ
Сергей Федоров
Сергей Федоров
Россия