Внутри высокоуровневых языков программирования данные разделяются по типам. Например, строки относятся к типу String, а числа — к типу int. Зачем нужны типы? Для защиты программы от трудноотловимых ошибок. Типы определяют две вещи:
Язык программирования распознает типы. Поэтому Java не позволит нам умножать строку на строку. Но позволит умножать целое число на другое целое число. Наличие типов и таких ограничений в языке защищает программы от случайных ошибок.
"one" * "two"
Error:
bad operand types for binary operator '*'
first type: java.lang.String
second type: java.lang.String
Каким образом Java понимает, что за тип данных перед ним? Достаточно просто. Любое значение где-то инициализируется и, в зависимости от способа инициализации, становится понятно, что перед нами. Например, число это просто число не обернутое в парные символы, например, кавычки. А вот строки всегда ограничены двойными кавычками. Например, такое значение "234"
– строка, несмотря на то, что внутри нее записаны цифры.
// Компилятор понимает что тут число
var age = 33;
По-английски строки в программировании называются "strings", а строчки текстовых файлов называются "lines". Например, в коде выше одна строчка (lines), и нет никаких строк (strings). В русском иногда может быть путаница, поэтому во всех уроках мы будем говорить строка для обозначения типа данных «строка», и строчка для обозначения строчек (lines) в файлах.
Типов данных в Java много, плюс можно создавать свои. Постепенно мы познакомимся со всеми необходимыми и научимся их правильно использовать.
До сих пор при определении переменных мы использовали ключевое слово var
, что может удивить тех, кто имеет какой-то опыт на Java. Обычно определение переменных показывают так:
int x = 3;
String greeting = "Hello Hexlet!";
// Error: incompatible types: java.lang.String cannot be converted to int
int ops = "test";
Пришло время раскрыть карты! Java статически типизированный язык, в таких языках тип переменной фиксируется при ее объявлении. В большинстве языков для этого перед именем переменной указывается ее тип, в примере выше это число (int) и строка (String). Раньше на Java создавали переменные только так, до тех пор пока не появился var
. var
– специальное ключевое слово, которое включает механизм вывода типов. Вывод типов автоматически определяет тип присваиваемого значения и связывает его с переменной. И правда, в примерах выше очевидно, где какой тип, зачем его явно прописывать?
Вывод типов в Java появился в 2018 году, но существуют языки, в которых вывод типов существует не один десяток лет. Первый язык с выводом типов называется ML и появился он аж в 1973 году. С тех пор вывод типов был добавлен в уйму языков среди которых Ocaml, Haskell, C#, F#, Kotlin, Scala и множество других. Вывод типов и предпочтителен в большинстве ситуаций, однако бывает такое, что выводимый тип нас не устраивает. Тогда мы можем указать тип явно.
В этом уроке мы рассмотрим систему типов в Java с высоты птичьего полета, не погружаясь в детали. Но сначала ответим на вопрос, зачем вообще про них знать? В коде программ мы все время оперируем данными. Эти данные имеют разную природу, могут быть по разному организованы, что влияет и на удобство работы с ними и эффективность этой работы. Типы преследуют нас буквально на каждом шагу, поэтому без их изучения программирование на Java возможно только на очень базовом уровне.
С другой стороны не пытайтесь запомнить всю эту информацию про типы наизусть (иначе можно приуныть). Она дается лишь для общего представления. Все что надо знать про типы вы и так выучите в процессе программирования. Глобально, типы данных в Java делятся на две большие группы:
У этих групп есть различия, которые мы разберем позже, когда познакомимся с null и объектно-ориентированным программированием. Пока достаточно знания того, что имена примитивных типов начинаются с нижнего регистра (int), а ссылочных с верхнего (String).
Всего в Java 8 примитивных типов данных: byte, short, int, long, float, double, boolean and char. Первые 4 это целые числа разного размера. Например byte занимает в памяти, как ни трудно догадаться, один байт, а значит может хранить числа от -128 до 127 (здесь мы не погружаемся в основы двоичной системы счисления, просто поверьте на слово -)). short – 2 байта, int – 4 байта и long – 8 байт. Пример:
byte x = 3; // Отработает без проблем
// Error: incompatible types: possible lossy conversion from int to byte
byte y = 270;
Определение переменной y
завершилось с ошибкой, потому что мы указали тип byte, но присвоили переменной значение 270, которое выходит за множество возможных значений.
Возникает закономерный вопрос. Зачем аж 4 типа для хранения чисел? Почему бы не сделать один, в который влезает почти любое большое число? Технически так сделать можно, но мы находимся в мире инженерных решений. Это значит, что у любого решения всегда есть обратная сторона, поэтому невозможно сделать идеально, придется чем-то пожертвовать. В данном случае, объемом занимаемой памяти. Если оставить только long, то программа активно оперирующая числами начнет занимать слишком много места в оперативной памяти, что может быть критично.
Такая же логика использовалась для типов float и double. Они оба отвечают за рациональные числа, разница лишь в том, что double это "двойной" float, то есть в памяти он занимает в два раза больше места. Создатели Java полагаются на разумность программистов. На их способность правильно подобрать нужные типы в зависимости от задачи. Для каких-то экстремальных приложений так и происходит, но в типичной разработке все просто. Программисты выбирают int для целых чисел и double для рациональных.
Тип boolean отвечает за логические значения true
и false
. Впереди им посвящен целый раздел, там мы про него и поговорим.
Особняком стоит тип char (символ). Символ это не строка, у него другой способ определения, через одиночные кавычки:
char ch = 'a';
// Error: incompatible types: java.lang.String cannot be converted to char
char ch2 = "b";
Строка состоящая из одного символа это не символ. С точки зрения здравого смысла кажется нелогично, но с точки зрения, типов всё так и должно быть, со временем вы это прочувствуете.
Извлечение символа из строки извлекает как раз символ, а не строку состоящую из одного символа.
"hexlet".charAt(1); // 'e'
Хорошо, а где String спросите вы? String не является примитивным типом. Внутри она представляет из себя массив символов. Несмотря на это техническое различие, строки используются наравне с примитивными типами без особых отличий.
Примитивные данные всегда имеют значение, даже если они определяются без инициализации:
int a;
System.out.println(a); // => 0
У каждого примитивного типа есть свое значение по умолчанию:
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
char ''
boolean false
Особняком в Java стоит значение null. Здесь оно, в отличии от других языков программирования, не является типом, это просто конкретное значение со специальным смыслом и логикой работы. Начнем с примера:
// Определение переменной без инициализации значением
// С var такое не сработает, так как невозможно вывести тип
String a;
Что находится внутри переменной a
? Если мы ее распечатаем, то увидим null
. null
используется для ссылочных типов, тогда, когда значение не определено. Как такое возможно? Представьте, что мы хотим извлечь из базы данных пользователя, а его там нет. Что, в таком случае, вернет нам запрос в базу? Вот именно для таких ситуаций и нужен null
. Их гораздо больше чем может показаться на первый взгляд и чем дальше мы будем двигаться, тем чаще он начнет встречаться.
var user = // тут делаем запрос в базу
// Если данных нет, то user станет null
// Запись выше равносильна
var user = null;
Из вышесказанного следует важный вывод. Любой ссылочный тип данных может принимать значение null
. То есть, null
, в каком-то смысле, является значением любого ссылочного типа. А вот примитивные типы и null
не совместимы. Примитивное значение всегда должно быть определено.
// Error: incompatible types: <nulltype> cannot be converted to int
int x = null;
В программировании регулярно встречаются задачи, когда один тип данных нужно преобразовать в другой. Простейший пример – работа с формами на сайтах. Данные формы всегда приходят в текстовом виде, даже если значение число. Вот как его можно преобразовать:
// станет int
var number = Integer.parseInt("345");
System.out.println(number); // => 345
Если нужно конвертировать из примитивного типа в примитивный, то все проще. Достаточно перед значением в скобках указать желаемый тип. В результате значение справа преобразуется в значение другого типа, указанного слева:
var result = (int) 5.1;
System.out.println(result); // => 5
Преобразование типов можно использовать внутри составных выражений:
// Дополнительные скобки помогают визуально отделить части выражения друг от друга
var result = 10 + ((int) 5.1);
System.out.println(result); // => 15
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт