«1. Введение

Java является типизированным языком, что означает, что он использует концепцию типов. Существуют две различные группы типов:

  1. primitive data types
  2. abstract data types.

В этой статье мы сосредоточимся на преобразованиях примитивных типов.

2. Обзор примитивов

Первое, что нам нужно знать, это какие значения могут использоваться с примитивными типами. Существует восемь примитивных типов, а именно:

    byte — 8-битный и со знаком Short — 16-битный и со знаком char — 16-битный и беззнаковый, так что он может представлять символы Unicode int — 32-битный и со знаком long — 64 бита и со знаком с плавающей запятой — 32 бита и со знаком double — 64 бита и со знаком boolean — не является числовым, может иметь только истинные или ложные значения

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

3. Расширение примитивных преобразований

Когда нам нужно преобразовать из примитива, который проще или меньше целевого типа, нам не нужно использовать для этого какие-либо специальные обозначения:

int myInt = 127;
long myLong = myInt;

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

float myFloat = myLong;
double myDouble = myLong;

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

4. Сужающее примитивное преобразование

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

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

int myInt = (int) myDouble;
byte myByte = (byte) myInt;

5. Расширение и сужение примитивного преобразования

Эта ситуация происходит в очень специфический случай, когда мы хотим преобразовать байт в символ. Первое преобразование — это расширение byte до int, а затем от int оно сужается до char.

Пример прояснит этот момент:

byte myLargeValueByte = (byte) 130;   //0b10000010 -126

Двоичное представление 130 одинаково для -126, разница заключается в интерпретации бита сигнала. Давайте теперь преобразуем byte в char:

char myLargeValueChar = (char) myLargeValueByte;
  //0b11111111 10000010 unsigned value
int myLargeValueInt = myLargeValueChar; //0b11111111 10000010 65410

Представление char является значением Unicode, но преобразование в int показало нам очень большое значение, в котором младшие 8 бит точно такие же, как -126.

Если мы снова преобразуем его в байты, мы получим:

byte myOtherByte = (byte) myLargeValueInt; //0b10000010 -126

Исходное значение, которое мы использовали. Если бы весь код начинался с char, значения были бы другими:

char myLargeValueChar2 = 130; //This is an int not a byte! 
  //0b 00000000 10000010 unsigned value
        
int myLargeValueInt2 = myLargeValueChar2; //0b00000000 10000010  130
        
byte myOtherByte2 = (byte) myLargeValueInt2; //0b10000010 -126

Хотя байтовое представление такое же, т. е. -126, представление char дает нам два разных символа.

6. Преобразование упаковки/распаковки

В Java у нас есть класс-оболочка для каждого примитивного типа, это умный способ предоставить программистам полезные методы обработки без накладных расходов, связанных с наличием всего в виде тяжеловесной ссылки на объект. Начиная с Java 1.5 была включена возможность автоматического преобразования в/из примитива в объект и обратно, и это достигается простой атрибуцией:

Integer myIntegerReference = myInt;
int myOtherInt = myIntegerReference;

7. Преобразование строк

Все примитивные типы могут быть преобразованы в String через их классы-оболочки, которые переопределяют метод toString():

String myString = myIntegerReference.toString();

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

byte  myNewByte   = Byte.parseByte(myString);
short myNewShort  = Short.parseShort(myString);
int   myNewInt    = Integer.parseInt(myString);
long  myNewLong   = Long.parseLong(myString);

float  myNewFloat  = Float.parseFloat(myString);
double myNewDouble = Double.parseDouble(myString);
boolean myNewBoolean = Boolean.parseBoolean(myString);

~ ~~ Единственным исключением здесь является класс символов, потому что строка в любом случае состоит из символов, таким образом, учитывая, что, вероятно, строка состоит из одного символа, мы можем использовать метод charAt() класса String:

char myNewChar = myString.charAt(0);

8. Числовые преобразования

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

Существует ряд простых применимых правил:

  1. If one of the operands is a double, the other is promoted to double
  2. Otherwise, if one of the operands is a float, the other is promoted to float
  3. Otherwise, if one of the operands is a long, the other is promoted to long
  4. Otherwise, both are considered int

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

byte op1 = 4;
byte op2 = 5;
byte myResultingByte = (byte) (op1 + op2);

Оба операнда были преобразованы в int, и результат должен быть снова преобразован в byte.

9. Заключение

«Преобразование между типами — очень распространенная задача в повседневной деятельности по программированию. Существует набор правил, определяющих способы, которыми статически типизированные языки выполняют эти преобразования. Знание этих правил может сэкономить много времени при попытке выяснить, почему определенный код компилируется или нет.

Код, использованный в этой статье, можно найти на GitHub.