У начинающих программистов, не знакомых с C или C++, типы данных в Java нередко вызывают трудности, потому что виды данных в Java организованы, мягко говоря, не очень удобно: есть примитивы и ссылки, примитивы могут конвертироваться в ссылочные типы и обратно, нужно соблюдать статическую типизацию, легко выстрелить себе в ногу неявным преобразованием и так далее. Ниже мы рассмотрим каждый тип данных и постараемся структурировать информацию про типы данных так, чтобы вам было легко ее запомнить.
В процессе программирования на Java вам всегда нужно указывать тип данных объявляемой переменной – так работает статическая типизация, вы не можете положить в переменную с типом Boolean число «53». И эти типы делятся на 2 больших лагеря: примитивные типы и ссылки.
Примитивные типы – это что-то простое, состоящее из одной сущности. Все числа – это примитивные типы, одиночная буква – тоже примитив, true/false – примитив. Когда программирование только зарождалось, примитивных типов было более чем достаточно, поскольку компьютеры использовались для расчетов. Тогда же и выработался очень простой метод хранения примитивов – всех их стали хранить в стеке оперативной памяти. Стек – это как обойма, вы объявили переменную x – в оперативную память по адресу 0 положили переменную x, после этого вы объявили переменную y – в оперативную память по адресу 1 положили переменную y.
При таком формате хранения переменные очень легко искать в памяти: нужно просто запомнить, на каком расстоянии от нулевой ячейки стека лежит искомая переменная.
Но со временем все чаще начала возникать ситуация, когда нужно объединить несколько примитивных значений одного типа в какую-то одну структуру данных: несколько символов объединить в строку символов; объединить данные о дате рождения 10 человек; … Решение нашли довольно простое:
Так работают массивы в C и C++. При создании массива вы получаете адрес первого элемента массива (ссылку на этот элемент), когда вы обращаетесь, например, ко второму элементу – вам нужно взять ссылку, прибавить к ней длину первого элемента в байтах – и вы получите адрес второго элемента в памяти, потому что элементы расположены один за другим. Но массивы не решают проблему хранения разных типов данных – в них можно засовывать только значения одного примитивного типа.
Проблему разных типов данных решили «в лоб»: в массиве просто указывается примитивный тип данных для разных ячеек. Эта реализация работает хорошо, но теперь для хранения такого массива стек не подходит – это как если бы мы пытались запихнуть в обойму патроны разного калибра. Выходом стала новая область памяти – куча (heap). Название очень хорошо описывает этот тип хранения данных, потому что куча – это буквально свалка данных. Конечно, компилятор и сборщик мусора ее оптимизируют так, чтобы в кучу влезло как можно больше данных, но сути это не меняет – никакой четкой организации в куче нет. Главный плюс кучи – ввиду отсутствия организации в кучу можно положить объект (набор примитивных данных) какой угодно длины и структуры (правда, быстродействие снижается по сравнению со стеком). Для того, чтобы пользоваться объектом, находящимся в куче, вам нужно иметь на него ссылку – то есть ссылку на его физическое расположение в памяти.
Итак, ликбез по основам Computer Science закончен, переходим к практике. В рамках платформы Java 2 типа данных: примитивы (значения в стеке) и ссылки (ссылки на объекты в куче). Примитивы:
По размерам целочисленных типов:
Типы объявляются в момент создания переменных, в случае с long нужно поставить L в конце литерала:
long testLong = 897204352096L
При задании литерала (значения) для char нужно обязательно помещать литерал в одинарные кавычки:
char testChar = ‘a’
В отличие от C, boolean не является надстройкой над byte, а представляет собой полноценный отдельный тип данных. Имеет только 2 значения – true или false. С помощью boolean можно управлять операторами ветвления:
if (5 > 4) { … }
Результат оператора сравнения – true, и блок if исполняется.Что касается ссылочных типов данных, то нужно разделять ссылку и объект. Ссылка на объект String (строку) объявляется так:
String testString;
Как видите, при объявлении нужно указать тип объекта и имя ссылки, как и с примитивами. Но если с примитивами справа от знака «=» мы просто указываем литерал (значение), то ссылке нужно назначить объект, а объект сначала нужно создать в куче: new String();
Полная версия, объявляем ссылку и сразу назначаем ей новый объект из кучи: String testString = new String();
Для продвинутых: объявлять строку таким способом не имеет смысла, потому что строки в Java – неизменяемые, и вы просто создадите пустую строку, которая будет занимать место, пока не будет уничтожена. Лучше сразу создавать строку, которой вы будете пользоваться: String testString = “Hello world!”; System.out.println(testString);
Отдельной болью в C была инициализация переменных по умолчанию – ее просто не было, и если вы забыли проинициализировать int нулем, то в переменной могло оказаться любое число, записанное в этой области памяти ранее (кстати, на этом принципе основана одна очень сложная хакерская атака, Buffer Overflow). В Java все куда проще – переменные, описанные вне метода (то есть поля класса) автоматически инициализируются нулевыми значениями, а переменные методов нужно инициализировать вручную, иначе компилятор Java SE выдаст ошибку. Стандартные значения:
Ссылки всегда инициализируются специальным типом null, который означает отсутствие чего-либо, в данном случае – отсутствие привязанного объекта.
Самый частый в использовании тип – int, и мы рекомендуем использовать именно его, если у вас нет четкой уверенности, что нужно использовать другой тип. Long нужен для действительно больших чисел, byte и short – для оптимизации нагрузки на оперативную память. Если вы не пишете приложение для микроконтроллера – используйте int. При необходимости вы потом сможете заменить его на другой целочисленный тип, если в этом появится нужда.
Числа с плавающей точкой – это float и double. Они хранят числа в виде значащей части, мантиссы и экспоненты. Например, 1 250 000 = 1.25e6, то есть 1.25, умноженное на 10 в степени 6. Значащая часть – 1, мантисса – 25, экспонента – 6. Числа с плавающей точкой могут вмещать в себя очень большие значения (до 2 в степени 63 для double), но страдает точность – мантисса обрезается и округляется, если ее длина превышает определенное значение (25 цифр для double). Поэтому:
if (firstDouble == secondDouble) {}
может вернуть false при одинаковых присвоенных значениях переменных, потому что округление сработает неправильно;Про них особо рассказывать нечего – boolean абсолютно прост и предсказуем, а char умеет хранить 1 символ из Unicode. Если вам любопытно, то на самом деле char – это short (целочисленный тип объемом в 2 байта), который компилятор воспринимает по особенному – тип char сигнализирует о том, что этот short нужно воспринимать как символ из Юникода.
Всегда null. Из-за этого самое распространенное исключение в Java, которое вы будете встречать – NullPointerException, оно показывает, что где-то есть ссылка с null, и с этой ссылкой что-то пытались делать (вызвать метод, например).
Чтобы объяснить Boxing и Unboxing, нужно сначала объяснить приведение типов. Поскольку Java – это язык со статической типизацией, тип переменной должен совпадать с типом данных. И вправду,
boolean testBool = 5;
не имеет смысла, потому что boolean может быть true или false, но никак не 5. А что делать, если типы данных относятся к одной категории? Например: byte b = 5; int i = b;
И byte, и int – целочисленные типы, которые могут хранить 5, при этом типы все же разные.Чтобы решать такие вопросы, в Java есть 2 вида приведения типов: явное и неявное. Явное – это когда вы прямо говорите компилятору, что нужно привести тип к какому-то другому:
int i = 5; byte b = (byte) i;
Здесь мы говорим, что нужно int конвертировать в byte, если мы уберем (byte) – компилятор выдаст ошибку. Неявное приведение – это когда компилятор делает все сам за нас: byte i = 5; int b = i;
Как видите, мы не писали никаких дополнительных указаний, но Java сама сконвертировала byte в int.Неявное приведение типов подчиняется очень простому правилу: оно производится только для расширения типа. То есть если мы пытаемся впихнуть значение в переменную, тип которой равен по объему значению или превышает его по объему, Java все сделает за нас. Цепочка приведения:
byte -> (char <-> short) -> int -> long -> float -> double
Если мы приводим byte к double – все окей, если приводим int к byte – это надо сделать явно. Совет начинающим: всегда используйте int и более объемные типы, избегайте сужающего приведения (из int в byte, к примеру). Сужающие приведения могут генерировать трудновычисляемые баги.Приведение работает не только для примитивных типов, но и для ссылок. Описывать логику – долго, потому что придется захватывать темы наследования и полиморфизма, что выходит далеко за рамки этого материала. Но общая логика такова: ссылочный тип можно неявно приводить к родительскому типу (классу) или явно приводить к дочернему типу (классу).
А теперь – про упаковку и распаковку. В Java есть ряд встроенных библиотек, которые наотрез отказываются работать с примитивными типами, как пример – List. Чтобы обойти эту проблему, разработчики языка для каждого примитивного типа ввели его объектный (ссылочный) аналог. Если тип называется double, то класс называется Double и так далее, исключения: int = Integer, char = Character. Таким образом вы можете передавать в методы объекта List обернутые в объект (ящик) примитивы. Чтобы упростить жизнь разработчикам, создатели языка ввели процедуру упаковки и распаковки – когда вы, например, даете объекту List примитив int, Java сама оборачивает int в Integer (boxing), а при возврате значения распаковывает Integer в int (unboxing).
Простое правило: не используйте методы примитивов, если не уверены, что вам это нужно. Объект класса Byte, например, создается довольно муторно:
Byte x = new Byte((byte) 2);
Как вы можете заметить, приходится приводить литерал к byte, чтобы создать Byte (по умолчанию все литералы в Java – int). Доверьтесь автоматической упаковке и распаковке. Школа |
Нетология |
Стоимость |
98 600 руб |
Цена в рассрочку |
2 883 руб/мес |
Длительность курса |
8 месяцев |
Программа трудоустройства |
Есть |
Формат |
Запись лекций, Онлайн занятия с преподавателем |
Школа |
Skillfactory |
Стоимость |
131 235 руб |
Цена в рассрочку |
4 050 руб/мес |
Длительность курса |
14 месяцев |
Программа трудоустройства |
Есть |
Формат |
Запись лекций, Онлайн занятия с преподавателем |
Школа |
Skillbox |
Стоимость |
96 439 руб |
Цена в рассрочку |
4 384 руб/мес |
Длительность курса |
10 месяцев |
Программа трудоустройства |
Есть |
Формат |
Запись лекций |
Передача по значению означает, что в метод передается копия значения, а не оригинал. Если поменять внутри метода копию – оригинал не изменится. По значению передаются примитивы. Передача по ссылке – это когда в метод передается ссылка на объект. Если метод как-то поменяет объект, он изменится и для вызвавшего метод кода. Все не-примитивы передаются по ссылке.
Технически это возможно, но делать так нельзя. Используйте short или int.
Тезисно: