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

Данная статья:

  • написана командой . Надеемся, что она Вам будет полезна. Приятного прочтения!
  • это одна из статей из нашего

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

В Java существует 2 типа преобразований - картинка Вам в помощь:

Напомним, что вся "Вселенная Java" состоит из:

  • примитивных типов (byte, short, int, long, char, float, double, boolean)
  • объектов

В данной статье мы:

  • рассмотрим преобразование типов для примитивных типов переменных
  • преобразование объектов (String, Scanner и др.) в этой статье не рассматривается, поскольку с объектами происходит отдельная «магия» - это тема для отдельной статьи.
Автоматическое преобразование

Ну, что ж, давайте попробуем разобраться что такое "автоматическое преобразование".

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

Так вот, к чему мы, собственно говоря, клоним. К тому, что совсем не просто так Вам давались диапазоны допустимых значений и объем занимаемой памяти 🙂

Давайте, сравним, например:

1. byte и short. byte имеет меньший диапазон допустимых значений, чем short. То есть byte это как бы коробочка поменьше, а short - это коробочка побольше. И значит, мы можем byte вложить в short.

2. byte и int . byte имеет меньший диапазон допустимых значений, чем int. То есть byte это как бы коробочка поменьше, а int - это коробочка побольше. И значит, мы можем byte вложить в int.

3. int и long. int имеет меньший диапазон допустимых значений, чем long. То есть int это как бы коробочка поменьше, а long - это коробочка побольше. И значит, мы можем int вложить в long.

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

Давайте рассмотрим как это работает на практике.

Пример №1

Код №1 - если Вы запустите это код на своем компьютере,

class Test { public static void main(String args) { byte a = 15; byte b = a; System.out.println(b); } }

class Test {

byte a = 15 ;

byte b = a ;

Код №2 - если Вы запустите это код на своем компьютере, в консоли будет выведено число 15

class Test { public static void main(String args) { byte a = 15; int b = a; System.out.println(b); } }

class Test {

public static void main (String args ) {

byte a = 15 ;

int b = a ;

System . out . println (b ) ;

И-и-и? Вы думаете, что раз в консоль было выведено одно и то же число, и код №1 отличается от кода №2 всего лишь типом переменной b, то между ними нет никакой разницы? Э то не так.

В коде №2 присутствует автоматическое преобразование типов , а в коде №1 - нет:

Хотя число, в принципе, одно и то же, но теперь оно находится в бо льшем контейнере, который занимает больше места на диске. При этом, JVM выполняет автоматические преобразования за Вас. Она знает, что int больше чем byte .

Приведение типов

Другое дело если вы пытаетесь переложить что-то из большего контейнера в более маленький.

Вы можете знать, что в большем контейнере лежит то, что поместиться и в маленьком – но об этом не знает JVM, и пытается предохранить вас от ошибок.

Поэтому, вы должны «прямо сказать», что ситуация под контролем:

class Test { public static void main(String args) { int a=0; long b=15; a = (int) b; } }

class Test {

public static void main (String args ) {

int a = 0 ;

long b = 15 ;

a = (int ) b ;

Тут мы дописали (int) перед b . Если бы переменная a была, к примеру, типа byte , в скобках бы стояло (byte) . Общая формула выглядит так:

Она говорит "сделай из (большего) значения b переменную нужного мне (целевого) типа int ".

Если что-то пошло не так.

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

Оказывается, в контейнере останется лишь то, что туда «влезло». К примеру, у чисел с плавающей точкой будет «отсекаться» дробная часть:

//пример 1 class Test { public static void main(String args) { double a=11.2345; int b=(int)a; System.out.println(b); // в консоли получится число 11 } }

//пример 1

class Test {

public static void main (String args ) {

double a = 11.2345 ;

int b = (int ) a ;

System . out . println (b ) ; // в консоли получится число 11

Надо помнить, что дробная часть не округляется , а отбрасывается .

А что будет, если мы попытаемся поместить число, которое выходит за допустимые границы? Например, если в byte (диапазон byte от -128 до 127) положить число 128? Думаете, мы получим 1? Нет. Мы получим -128:

class Test { public static void main(String args) { double a=128; byte b=(byte)a; System.out.println(b); //в консоли увидим -128 } }

Значение переменной при таком преобразовании можно рассчитать, но цель программиста – не допускать ситуации, когда значение выходит за допустимые границы, поскольку это может привести к неправильной работе программы.

Задания:
  1. Последовательно пропишите в компиляторе преобразования всех примитивных типов друг к другу, включая типы char и Составьте таблицу такого вида:
byte short char int long float double boolean
byte
short
char
int
Long
Float
double
boolean

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

* приведение типа к самому себе называется тождественным – его прописывать не обязательно

  1. Посмотрите еще раз, какой размер имеет каждый примитивный тип. Попытайтесь составить блок-схему, показывающую, куда помещаются какие типы. Проведите стрелочки с надписью «расширяющее преобразование» и «сужающее преобразование».
Вопросы

На собеседовании на должность Junior Java Developer Вас могут спросить:

Что Вы знаете о преобразовании примитивных типов данных, есть ли потеря данных, можно ли преобразовать логический тип?

Попробуйте ответить на вопрос.

Подытожим:
  • Если Вы "кладёте" в больший контейнер содержимое меньшего контейнера», преобразование происходит автоматически, и ошибок возникать не должно.
  • Если есть необходимость положить «значение из большего контейнера в меньший», нужно быть осторожным, и пользоваться явным приведением типов.
  • При приведении float или double к целочисленным типам, дробная часть не округляется, а просто отбрасывается.
  • Тип boolean не приводится ни к одному из типов.
  • Тип char приводится к числовым типам, как код символа в системе UNICODE.
  • Если число больше своего контейнера, результат будет непредсказуемым.

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

Надеемся, что наша статья была Вам полезна. Также есть возможность записаться на наши курсы по Java в Киеве. Обучаем с нуля. Детальную информацию Вы можете найти у нас на .


Иногда возникают ситуации, когда у вас есть величина какого-то определенного типа, а вам нужно ее присвоить переменной другого типа. Для некоторых типов это можно проделать и без приведения типа, в таких случаях говорят об автоматическом преобразовании типов. В 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

Обратите внимание на то, что целые числа печатаются в десятичном представлении, хотя мы задавали значения некоторых из них в шестнадцатиричном формате.

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

Приведение типов в арифметических выражениях выполняется автоматически.

byte->short->int->long->float->double

Если операнды a и b комбинируются бинарным оператором (ниже мы это обсудим), перед его исполнением оба операнда преобразуются в данные одного типа следующим образом:

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

Разрешенные преобразования типов

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

Для сужения каст необходимо сделать явным. Например: byte b = (byte)128; прикастили инт к байт типу.

Предлагаю сделать несколько примеров.

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

  • Имена переменных не могут начинаться с цифры, в именах не могут использоваться как символы арифметических и логических операторов, а также символ ‘#’.
  • Применение символов ‘$’ или ‘_’ приемлемо, включая первую позицию и имя.
  • Переменная примитивного типа, объявленная как член класса (глобальная переменная), по умолчанию задается нулем.
  • Если переменная объявлена как локальная переменная в методе, перед использованием она должна обязательно быть проинициализирована. Так как локальные переменные не изициализируются по умолчанию. Это значит, что Вы не можете объявить локальную переменную и оставить ее без инициализации. То есть вот так: int i;. Если Вы так сделаете в методе, компилятор попросит Вас задать значение по умолчанию в то время, как создав такую переменную как член класса (глобальную) компилятор сам задаст ей значение 0.
  • Область действия и время жизни переменной ограничено блоком {}, в котором она объявлена. Если Вы создали переменную внутри метода (как мы это делали в примерах), то Вы не сможете использовать ее вне метода, так как метод ограничен скобками {}. Глобальную переменную видно во всех блоках.
  • Также запрещено использовать зарезервированные слова java. Весь перечень ключевых слов можно увидеть на картинке ниже.

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

Java имеет несколько типов операторов: простое присваивание, арифметическое, унарное, равноправное и реляционное, условное, сравнение типов, побитовое и битовое смещение.

Много умных слов, но очень просто все объяснит вот эта картинка:

На первых порах мы будем пользоваться побитовым сравнением, присваиванием, и постфиксными операторами. Другие операторы встречаются не так часто, поэтому мы рассмотрим только те, которыми будем пользоваться.

    public class OperatorsInJava {

    int a = 5 ;

    int b = 6 ;

    int sum = a + b;

    int difference = a - b;

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

  • Тождественное (identity);

  • Расширение примитивного типа (widening primitive);

  • Сужение примитивного типа (narrowing primitive);

  • Расширение объектного типа (widening reference);

  • Сужение объектного типа (narrowing reference);

  • Преобразование к строке (String);

  • Запрещенные преобразования (forbidden);
Рассмотрим их по отдельности.
Тождественное преобразование
Самым простым является тождественное преобразование. В Java преобразование выражения любого типа к точно такому же типу всегда допустимо и успешно выполняется.
Это важно для возможности утверждать с теоретической точки зрения, что любой тип в Java может участвовать в преобразовании, хотя бы в тождественном.
Преобразование примитивных типов (расширение и сужение)
Для простых типов расширение означает, что осуществляется переход от менее емкого типа к более ёмкому. Например, от типа byte (длина 1 байт) к типу int (длина 4 байта). Такие преобразование безопасны в том смысле, что новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом типе, и таким образом не происходит потери данных. Именно поэтому компилятор осуществляет его сам, незаметно для разработчика:

byte b=3;
int a=b;

Следующие 19 преобразований являются расширяющими:

  • От byte к short, int, long, float, double

  • От short к int, long, float, double

  • От char к int, long, float, double

  • От int к long, float, double

  • От long к float, double

  • От float к double
Обратите внимание, что нельзя провести преобразование к типу char от типов меньшей или равной длины (byte, short) или, наоборот, к short от char без потери данных. Это связано с тем, что char, в отличие от остальных целочисленных типов, является знаковым.
Тем не менее, следует помнить, что даже при расширении данные все таки могут быть искажены. Это приведение значений int к типу float и приведение значений типа long к типу float или double. Хотя эти дробные типы вмещают гораздо большие числа, чем соответствующие целые, но у них меньше значащих разрядов.
Например:

long a = 111111111111L;
float f=a;
a=(long)f; // () это как раз и есть операция преобразования типа
System.out.println(a); //результат 111111110656

Обратите внимание – сужение – означает, что переход осуществляется от боле емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, т.е. программист в коде должен явно указать, то он намеревается осуществить такое преобразование и готов потерять данные.
Следующие 23 преобразования являются сужающими:

  • От byte к char

  • От short к byte, char

  • От char к byte, short

  • От int к byte, short, char

  • От long к byte, short, char, int

  • От float к byte, short, char, int, long

  • От double к byte, short, char, int, long, float
При сужении целочисленного типа к более узкому целочисленному все старшие биты, не попадающие в новый тип,просто отбрасывается. Не производится никакого округления или других действий для получения более корректного результата:

System.out.println((byte)383);
System.out.println((byte)384);
System.out.println((byte)-384);

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

127
-128
-128
Видно, что знаковый бит при сужении не оказал никакого влияния, так как был просто отброшен – результат приведения обратных чисел (384, -384) оказался одинаковым. Следовательно, может быть потеряно не только точное абсолютное значение, но и знак величины.
Это верно и для char:

char c=4000;
System.out.println((short)c);

Результат:

-25536
Преобразование ссылочных типов (расширение и сужение)
Преобразование объектных типов лучше всего иллюстрируется с помощью дерева наследования. Рассмотрим небольшой пример наследования:

class Parent {
int x;
}

class ChildY extends Parent {
int y;
}

class ChildZ extends Parent {
int z;
}

В каждом классе объявлено поле с уникальным именем. Будем рассматривать это поле как пример набора уникальных свойств, присущи некоторому объектному типу.
Объекты класса Parent обладают только одним полем x, а значит, только ссылки типа Parent могут ссылаться на такие объекты. Объекты класса ChildY обладают полем y и полем x, полученным по наследству от класса Parent. Стало быть, на такие объекты могут указывать ссылки типа ChildY или Parent. Пример:

Parent p = new ChildY();

Обратите внимание, что с помощью такой ссылки p можно обращаться лишь к полю x созданного объекта. Поле y недоступно, так как компилятор, проверяя корректность выражения p.y, не может предугадать, что ссылка p будет указывать на объект типа ChildY во время исполнения программы. Он анализирует лишь тип самой переменной, а она объявлена как Parent, но в этом классе нет поля y, что и вызовет ошибку компиляции.
Аналогично, объекты класса ChildZ обладают полем z и полем x, полученным по наследству от класса Parent. Значит, на такие объекты могут указывать ссылки типа ChildZ и Parent.
Таким образом, ссылки типа Parent могут указать на объект любого из трех рассматриваемых типов, а ссылки типа ChildY и ChildZ – только на объекты точно такого же типа. Теперь можно перейти к преобразования ссылочных типов на основе такого дерева наследования.
Расширение означает переход от более конкретного типа к менее конкретному, т.е. переход от детей к родителям. Подобно случаю с примитивными типами, этот переход производиться самой JVM при необходимости и «незаметен» для разработчика, то есть не требует никаких специальных преобразования.

Parent p1=new ChildY();
Parent p2=new ChildZ();

В обеих строках переменным типа Parent присваивается значение другого типа, а значит, происходит преобразование. Поскольку это расширение, оно производиться автоматически и всегда успешно.
Нужно заметить, что при подобном преобразовании с самим объектом ничего не происходит. Несмотря на то что, например, поле y класса ChildY теперь недоступно, это не значит, что оно исчезло. Такое существенно изменение объекта не возможно. Он был порожден от класса ChildY и сохраняет все его свойства. Изменился лишь тип ссылки, через которую идет обращение к объекту.
Обратный переход, то есть движение по дереву наследования вниз, к наследникам, является сужением. Например, для рассматриваемого случая, переход от ссылки типа Parent , которая может ссылаться на объекты трех классов, к ссылке типа ChildY, которая может ссылаться только на один класс из трех, очевидно, является сужением. Такой переход может оказаться невозможным. Если ссылка типа Parent ссылается на объект типа Parent или ChildZ, то переход к ChildY невозможен, так как в обоих случаях объект не обладает полем y, которое объявлено в классе ChildY. Поэтому при сужении разработчику необходимо явным образом указывать на то, что необходимо попытаться провести такое преобразование. JVM во время исполнения проверит корректность перехода. Если он возможен, преобразование будет проведено. Если же нет – возникнет ошибка (обычно ClassCastException).

Parent p=new ChildY();
ChildY cy = (ChildY)p; //верно
Parent p2=new ChildZ();
ChildY cy2 = (ChildY)p2; //ошибка

Чтобы проверить, возможен ли желаемый переход, можно воспользоваться оператором instanceof:

Parent p=new ChildY();
if (p instanceof ChildY) {
ChildY cy = (ChildY)p;
}

Parent p2=new ChildZ();
if (p2 instanceof ChildY) {
ChildY cy = (ChildY)p2;
}

Parent p3=new Parent();
if (p3 instanceof ChildY) {
ChildY cy = (ChildY)p3;
}

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

  • Числовые типы записываются в текстовом виде без потери точности представления. Сначала на основе примитивного значения порождается экземпляр соответствующего класса-«обертки», затем у него вызывается метод toString(). Но поскольку эти действия снаружи незаметны, JVM оптимизирует их и преобразует примитивные значения в текст напрямую.

  • Булевские величины приводятся к строке «true» или «false» в зависимости от значения.

  • Для объектных величин вызывается метод toString(). Если метод возвращает null, то результатом будет строка “null”.

  • Для null-значения генерируется строка “null”.
Запрещенные преобразования
Не все переходы между произвольными типами допустимы. Например, к запрещенным преобразованиям относятся: переходы от любого ссылочного типа к примитивному и наоборот (кроме преобразования к строке), boolean можно привести только к этому типу или же к строке. Кроме того невозможно привести друг к другу, классы находящиеся на соседних ветвях дерева наследования. В примере, который рассматривался для иллюстрации ссылочных типов, переход от ChildY к ChildZ запрещен.
Этим список запрещенных преобразований не исчерпывается. Он довольно широк и в тоже время все варианты достаточно очевидны, поэтому подробно рассматриваться не будут. Желающие могут получить полную информацию из спецификации.
Разумеется, попытка осуществить запрещенное преобразование вызовет ошибку.

Применение приведений
Ситуации применения преобразования типов могут быть сгруппированы следующим образом:

  • Присвоение значений переменным (assignment). Не все переходы допустимы при таком преобразовании – ограничения выбраны таким образом, чтобы не могла возникнуть исключительная ситуация.

  • Вызов метода. Это преобразование применяется к аргументам вызываемого метода или конструктора. Такое приведение никогда не порождает ошибок. Так же приведение осуществляется при возвращении значения метода.

  • Явное приведение. В этом случае явно указывается, к какому типу требуется привести исходное значение.

  • Оператор конкатенации производит преобразование к строке своих аргументов.

  • Числовое расширение. Числовые операции могут потребовать изменения типа аргумента(ов). Это преобразование имеет особое название – расширенное, так как выбор целевого типа может зависеть не только от исходного значения, но и от второго аргумента операции.
Задание #8
Добавить в проект использование приведения для иерархии ваших классов.

Это достаточно большая тема, но мы постараемся рассмотреть ее как можно более полно и вместе с тем компактно. Частично мы уже касались этой темы когда рассматривали примитивные типы Java.

В Java возможны преобразования между целыми значениями и значениями с плавающей точкой. Кроме того, можно преобразовывать значения целых типов и типов с плавающей точкой в значения типа char и наоборот, поскольку каждый символ соответствует цифре в кодировке Unicode. Фактически тип boolean является единственным примитивным типом в Java, который нельзя преобразовать в другой примитивный тип. Кроме того, любой другой примитивный тип нельзя преобразовать в boolean.

Преобразование типов в Java бывает двух видов: неявное и явное .

Неявное преобразование типов выполняется в случае если выполняются условия:

  1. Оба типа совместимы
  2. Длина целевого типа больше или равна длине исходного типа

Во всех остальных случаях должно использоваться явное преобразование типов .

Так же существуют два типа преобразований:

  1. Расширяющее преобразование (widening conversion)
  2. Сужающее преобразование (narrowing conversion)

Расширяющее преобразование (widening conversion ) происходит, если значение одного типа преобразовывается в более широкий тип, с большим диапазоном допустимых значений. Java выполняет расширяющие преобразования автоматически, например, если вы присвоили литерал типа int переменной типа double или значение пепременной типа char переменной типа int. Неявное преобразование всегда имеет расширяющий тип .

Но у тут могут быть свои небольшие грабельки. Например если преобразуется значение int в значение типа float. И у значения int в двоичном представлении больше чем 23 значащих бита, то возможна потеря точности, так как у типа float под целую часть отведено 23 бита. Все младшие биты значения int, которые не поместятся в 23 бита мантиссы float, будут отброшены, поэтому хотя порядок числа сохраниться, но точность будет утеряна. То же самое справедливо для преобразования типа long в тип double.

Расширяющее преобразование типов Java можно изобразить еще так:

Сплошные линии обозначают преобразования, выполняемые без потери данных. Штриховые линии говорят о том, что при преобразовании может произойти потеря точности.

Стоит немного пояснить почему, к примеру тип byte не преобразуется автоматически (не явно) в тип char, хотя тип byte имеет ширину 8 бит, а char 16, тоже самое касается и преобразования типа short в char. Это происходит потому, что byte и short знаковые типы данных, а char без знаковый. Поэтому в данном случае требуется использовать явное приведение типов, поскольку компилятору надо явно указать что вы знаете чего хотите и как будет обрабатываться знаковый бит типов byte и short при преобразовании к типу char.

Поведение величины типа char в большинстве случаев совпадает с поведением величины целого типа, следовательно, значение типа char можно использовать везде, где требуются значения int или long. Однако напомним, что тип char не имеет знака, поэтому он ведет себя отлично от типа short, несмотря на то что диапазон обоих типов равен 16 бит.

short s = ( short ) 0xffff ; // Данные биты представляют число –1
char c = "\uffff" ; // Те же биты представляют символ юникода
int i1 = s ; // Преобразование типа short в int дает –1
int i2 = c ; // Преобразование char в int дает 65535

Сужающее преобразование (narrowing conversion ) происходит, если значение преобразуется в значение типа, диапазон которого не шире изначального. Сужающие преобразования не всегда безопасны: например, преобразование целого значения 13 в byte имеет смысл, а преобразование 13000 в byte неразумно, поскольку byte может хранить только числа от −128 до 127. Поскольку во время сужающего преобразования могут быть потеряны данные, Java компилятор возражает против любого такого преобразования, даже если преобразуемое значение укладывается в более узкий диапазон указанного типа:

int i = 13 ;
byte b = i ; // Компилятор не разрешит это выражение

Единственное исключение из правила – присвоение целого литерала (значения типа int) переменной byte или short, если литерал соответствует диапазону переменной.

Сужающее преобразование это всегда явное преобразование типов .

Явное преобразование примитивных типов

Оператором явного преобразования типов или точнее говоря приведения типов являются круглые скобки, внутри которых указан тип, к которому происходит преобразование – (type) . Например:

int i = 13 ;
byte b = ( byte ) i ; // Принудительное преобразование int в byte
i = ( int ) 13.456 ; // Принудительное преобразование литерала типа double в int 13

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

При приведении более емкого целого типа к менее емкому старшие биты просто отбрасываются . По существу это равнозначно операции деления по модулю приводимого значения на диапазон целевого типа (например для типа byte это 256).

Слишком большое дробное число при приведении к целому превращается в MAX_VALUE или MIN_VALUE .

Слишком большой double при приведении к float превращается в Float.POSITIVE_INFINITY или Float.NEGATIVE_INFINITY .

Таблица представленная ниже представляет собой сетку, где для каждого примитивного типа указаны типы, в которые их можно преобразовать, и способ преобразования. Буква N в таблице означает невозможность преобразования. Буква Y означает расширяющее преобразование, которое выполняется автоматически. Буква С означает сужающее преобразование, требующее явного приведения. Наконец, Y* означает автоматическое расширяющее преобразование, в процессе которого значение может потерять некоторые из наименее значимых разрядов. Это может произойти при преобразовании int или long во float или double. Типы с плавающей точкой имеют больший диапазон, чем целые типы, поэтому int или long можно представить посредством float или double. Однако типы с плавающей точкой являются приближенными числами и не всегда могут содержать так много значащих разрядов в мантиссе, как целые типы.

Автоматическое расширение типов в выражениях

Так же стоит еще раз упомянуть об автоматическом повышении (расширении) типов в выражениях. Мы с этим уже сталкивались когда рассматривали целочисленные типы данных и операции над ними, но все же стоит и тут напомнить, чтобы усвоилось еще лучше и к тому же это имеет непосредственное отношение к данной теме. В примере ниже знак @ + , , * , / и т.п.

То есть, все целочисленные литералы в выражениях, а так же типы byte , short и char расширяются до int . Если, как описано выше, в выражении не присутствуют другие, более большие типы данных (long , float или double ). Поэтому приведенный выше пример вызовет ошибку компиляции, так как переменная c имеет тип byte , а выражение b+1, в результате автоматического повышения имеет тип int .

Неявное приведение типов в выражениях совмещенного присваивания

Хоть данный раздел и относится к неявному преобразованию (приведению) типов, его объяснение мы привели тут, поскольку в данном случае так же работает и автоматическое расширение типов в выражениях, а затем уже неявное приведение типов. Вот такой кордебалет. Пример ниже я думаю все разъяснит. Так же как и в предыдущем объяснении знак @ означает любой допустимый оператор, например + , , * , / и т.п.

Это стоит пояснить на простом примере:

byte b2 = 50 ;
b2 = b2 * 2 ; // не скомпилируется
b2 *= 2 ; //скомпилируется, хотя и равнозначна b2 = b2 * 2

Вторя строка, приведенная в примере не скомпилируется из-за автоматического расширения типов в выражениях, так как выражение b2*2 имеет тип int, так как происходит автоматическое расширение типа (целочисленные литералы в выражении всегда int). Третья же строка спокойно скомпилируется, так как в ней сработает неявное приведение типов в совмещенном выражении присваивания.

Boxing/unboxing – преобразование примитивных типов в объекты обертки

Boxing и unboxin – это тоже достаточно большая тема, но она достаточно простая.

По существу boxing и unboxing это преобразование примитивных типов в объекты обертки и обратно .

Для объектов оберток примитивных типов применимо все что было сказано выше.

Об классах обертках упоминалось в таблицах, при разборе каждого из примитивных типов. Но тогда это было лишь упоминание в таблице.

Так вот, для каждого примитивного типа есть его старший брат, и он совсем не примитивный, а является настоящим классом, с полями и методами. И для каждой такой парочки возможно автоматическое преобразование.

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

Приведу простой пример:

int i3 ;
byte b2 = 3 ;
Byte myB ;
myB = b2 ;
myB ++;
b2 = myB ;
i3 = myB ;

Если пока не понятно зачем это нужно, то это не страшно, просто завяжите узелок на память.