Кодировки: основы.
Компактные строки - важное обновление в java 9. Чтобы лучше понять проблему и новое решение, разберём в этом посте отличия между кодировками. А в среду обсудим компактные строки.
Символы хранятся в памяти в виде чисел. Кодировки отвечают за формат хранения и правила перевода символов в числа и обратно. Кодировки можно условно поделить на две группы:
1️⃣ ASCII-based
2️⃣ Unicode-based
1️⃣ ASCII
В большинстве ASCII кодировок символ занимает 1 байт и содержит число от 0 до 256. Первые 128 значений транслируются одинаково во всех кодировках:
0-31: управляющие последовательности - перенос строки, конец файла и т.д.
32-127: латинский алфавит, цифры, знаки препинания.
Специфичные символы языков отображаются на значения 128-255. Разные кодировки - разные наборы символов:
🔸Кириллица: ISO-8859-5, Windows-1251.
🔸Греческий алфавит: ISO-8859-7, Windows-1253.
🔸Исландские символы: OEM 861.
200 символ может стать Ш, Θ, È или чем-то ещё. Фраза «Я люблю Java” в другой кодировке отобразится как «Ď ŰîŃŰî Java».
Итого:
✅ Один символ занимает 1 байт — компактно.
❌ Всего 256 значений — нет места для эмодзи.
❌ Неоднозначность трактовки.
❌ Нельзя использовать украинский и норвежский язык в одном тексте.
2️⃣ Unicode
В основе преобразований лежит таблица с большинством символов, которые используются в мире. Но не со всеми, многие азиатские иероглифы записываются в памяти как комбинация 2-3 символов. Или эмоджи, например,👩❤️💋👩 - это сочетание 8 Unicode-кодов.
Изначально Unicode использовал 2 байта для записи символа и кодировку UCS-2. После 256 символов ASCII казалось, что диапазона 0-65536 хватит навсегда. Эта кодировка использовалась в ранних версиях java для типа char.
Со временем в таблицу добавилось больше символов и встал вопрос об эффективном хранении данных. Сегодня, чтобы однозначно представить символ юникода нужно 32 бита — так символы хранятся в UTF-32.
✅ Прямое отображение.
✅ Простота обработки.
❌ Неэффективный расход памяти — если использовать только латиницу с кодами типа 0..045 и 0...077, ¾ памяти будет занято нулями.
На смену UCS-2 с фиксированными 2 байтами пришёл UTF-16 с переменной длиной. Если значение символа превышает 65536, то оно занимает 4 байта. Java перешла на UTF-16.
Кодировка UTF-8 тоже использует переменное количество памяти. Для каждого символа задаётся, сколько он занимает места — 1, 2, 3 или 4 байта.
✅ Экономный расход памяти для латинских символов.
❌ Обработка и поиск происходят чуть медленнее.
❌ Отметка длины находится в первых 2 битах и уменьшает диапазон значений.
Если в структуре 2 или больше байтов, то одни процессоры быстрее считывают их в прямом порядке, а другие — в обратном. Поэтому у UTF-16 и UTF-32 могут быть приставки LE или BE: Little/big endian.
ASCII-кодировки не умеют читать символы больше 255, поэтому когда они встречают юникод-символы, то показывают ❓ и текст «я люблю Java” превращается в “? ????? Java”.
Компактные строки - важное обновление в java 9. Чтобы лучше понять проблему и новое решение, разберём в этом посте отличия между кодировками. А в среду обсудим компактные строки.
Символы хранятся в памяти в виде чисел. Кодировки отвечают за формат хранения и правила перевода символов в числа и обратно. Кодировки можно условно поделить на две группы:
1️⃣ ASCII-based
2️⃣ Unicode-based
1️⃣ ASCII
В большинстве ASCII кодировок символ занимает 1 байт и содержит число от 0 до 256. Первые 128 значений транслируются одинаково во всех кодировках:
0-31: управляющие последовательности - перенос строки, конец файла и т.д.
32-127: латинский алфавит, цифры, знаки препинания.
Специфичные символы языков отображаются на значения 128-255. Разные кодировки - разные наборы символов:
🔸Кириллица: ISO-8859-5, Windows-1251.
🔸Греческий алфавит: ISO-8859-7, Windows-1253.
🔸Исландские символы: OEM 861.
200 символ может стать Ш, Θ, È или чем-то ещё. Фраза «Я люблю Java” в другой кодировке отобразится как «Ď ŰîŃŰî Java».
Итого:
✅ Один символ занимает 1 байт — компактно.
❌ Всего 256 значений — нет места для эмодзи.
❌ Неоднозначность трактовки.
❌ Нельзя использовать украинский и норвежский язык в одном тексте.
2️⃣ Unicode
В основе преобразований лежит таблица с большинством символов, которые используются в мире. Но не со всеми, многие азиатские иероглифы записываются в памяти как комбинация 2-3 символов. Или эмоджи, например,👩❤️💋👩 - это сочетание 8 Unicode-кодов.
Изначально Unicode использовал 2 байта для записи символа и кодировку UCS-2. После 256 символов ASCII казалось, что диапазона 0-65536 хватит навсегда. Эта кодировка использовалась в ранних версиях java для типа char.
Со временем в таблицу добавилось больше символов и встал вопрос об эффективном хранении данных. Сегодня, чтобы однозначно представить символ юникода нужно 32 бита — так символы хранятся в UTF-32.
✅ Прямое отображение.
✅ Простота обработки.
❌ Неэффективный расход памяти — если использовать только латиницу с кодами типа 0..045 и 0...077, ¾ памяти будет занято нулями.
На смену UCS-2 с фиксированными 2 байтами пришёл UTF-16 с переменной длиной. Если значение символа превышает 65536, то оно занимает 4 байта. Java перешла на UTF-16.
Кодировка UTF-8 тоже использует переменное количество памяти. Для каждого символа задаётся, сколько он занимает места — 1, 2, 3 или 4 байта.
✅ Экономный расход памяти для латинских символов.
❌ Обработка и поиск происходят чуть медленнее.
❌ Отметка длины находится в первых 2 битах и уменьшает диапазон значений.
Если в структуре 2 или больше байтов, то одни процессоры быстрее считывают их в прямом порядке, а другие — в обратном. Поэтому у UTF-16 и UTF-32 могут быть приставки LE или BE: Little/big endian.
ASCII-кодировки не умеют читать символы больше 255, поэтому когда они встречают юникод-символы, то показывают ❓ и текст «я люблю Java” превращается в “? ????? Java”.
Java 9: Сompact Strings.
По данным OpenJDK не менее 25% объектов в памяти занимают строки. По той же статистике 95% строк содержат только латинские символы и цифры, числовое значение которых умещается в 1 байт, а 5-15% памяти заняты бесполезными нулями.
Для хранения символов в java 8 используется тип char. Занимает 16 бит и содержит значение в кодировке UTF-16. Строка хранится как массив символов —
В Java 9 вышло обновление Compact Strings, которое меняет структуру хранения строки.
Символы теперь лежат в
1️⃣ Если все символы строки умещаются в 1 байт(латиница и цифры), то записываются в одну ячейку массива. Выставляется кодировка Latin-1.
2️⃣ Если хотя бы один символ требует 16 байт, все элементы занимают 2 ячейки массива. Выставляется кодировка UTF-16.
❓Можно ли было перевести строки на UTF-8, символы с переменной длиной?
Да, расход памяти стал бы ещё меньше, но производительность бы упала. Если символы в массиве одной длины, то по индексу можно быстро найти адрес символа в памяти. Если элементы с переменной длиной - адрес вычисляется на основе предыдущих элементов, а это долго. Все методы класса String работали бы дольше.
Строки в разных кодировках по-разному лежат в памяти, и работать с ними нужно тоже по-разному. Каждый метод в классе String начинается с проверки кодировки и разделяется на две ветки — для Latin-1 и UTF-16. Их код вынесен в отдельные классы StringLatin1 и StringUTF16.
❓Память сэкономили, кода стало в 3 раза больше, не упала ли производительность?
Любая дополнительная проверка снижает скорость обработки, особенно при работе с маленькими строками. Поэтому на уровне JVM и JIT добавлены оптимизации проверки кодировки и сравнения строк, изменён механизм конкатенации и других операций. Именно за счёт внутренних оптимизаций компактные строки работают в среднем на 20% быстрее и создают на 30% меньше промежуточных объектов.
Самое главное - эти изменения никак не отразились на интерфейсе String. I/O классы, StringBuilder, StringBuffer тоже адаптированы без внешних изменений. Нужно просто перейти на java 9 и приложение будет занимать на 5-15% меньше памяти.
По данным OpenJDK не менее 25% объектов в памяти занимают строки. По той же статистике 95% строк содержат только латинские символы и цифры, числовое значение которых умещается в 1 байт, а 5-15% памяти заняты бесполезными нулями.
Для хранения символов в java 8 используется тип char. Занимает 16 бит и содержит значение в кодировке UTF-16. Строка хранится как массив символов —
char[].В Java 9 вышло обновление Compact Strings, которое меняет структуру хранения строки.
Символы теперь лежат в
byte[] и хранятся в одной из двух кодировок. Сама кодировка записана в новом параметре coder. Возможны 2 варианта:1️⃣ Если все символы строки умещаются в 1 байт(латиница и цифры), то записываются в одну ячейку массива. Выставляется кодировка Latin-1.
2️⃣ Если хотя бы один символ требует 16 байт, все элементы занимают 2 ячейки массива. Выставляется кодировка UTF-16.
❓Можно ли было перевести строки на UTF-8, символы с переменной длиной?
Да, расход памяти стал бы ещё меньше, но производительность бы упала. Если символы в массиве одной длины, то по индексу можно быстро найти адрес символа в памяти. Если элементы с переменной длиной - адрес вычисляется на основе предыдущих элементов, а это долго. Все методы класса String работали бы дольше.
Строки в разных кодировках по-разному лежат в памяти, и работать с ними нужно тоже по-разному. Каждый метод в классе String начинается с проверки кодировки и разделяется на две ветки — для Latin-1 и UTF-16. Их код вынесен в отдельные классы StringLatin1 и StringUTF16.
❓Память сэкономили, кода стало в 3 раза больше, не упала ли производительность?
Любая дополнительная проверка снижает скорость обработки, особенно при работе с маленькими строками. Поэтому на уровне JVM и JIT добавлены оптимизации проверки кодировки и сравнения строк, изменён механизм конкатенации и других операций. Именно за счёт внутренних оптимизаций компактные строки работают в среднем на 20% быстрее и создают на 30% меньше промежуточных объектов.
Самое главное - эти изменения никак не отразились на интерфейсе String. I/O классы, StringBuilder, StringBuffer тоже адаптированы без внешних изменений. Нужно просто перейти на java 9 и приложение будет занимать на 5-15% меньше памяти.
Загрузчики классов в Java.
Пост о том, зачем нужны загрузчики классов, и почему по умолчанию их 3.
При компиляции исходный код преобразуется в файл с расширением
Базовые классы JDK, такие как
Любая программа использует много сторонних библиотек. Чтобы не тратить ресурсы на загрузку лишних объектов, классы подгружаются по мере необходимости.
❓Какие события вызывают загрузку класса?
🔸Создание экземпляра:
Схема поиска подходящего
Пост о том, зачем нужны загрузчики классов, и почему по умолчанию их 3.
При компиляции исходный код преобразуется в файл с расширением
.class. Когда в программе встречается имя нового класса, JVM "загружает" его: ищет файл с таким же именем и создаёт экземпляр типа Class. В этом объекте содержится информация о полях, методах и обо всём, что нужно для создания экземпляра. Базовые классы JDK, такие как
Object, String, ArrayList, являются основой для других объектов. Поэтому они загружаются на старте приложения, этим занимается объект JVM под названием Bootstrap ClassLoader.Любая программа использует много сторонних библиотек. Чтобы не тратить ресурсы на загрузку лишних объектов, классы подгружаются по мере необходимости.
❓Какие события вызывают загрузку класса?
🔸Создание экземпляра:
new Example();🔸Ссылка на статические поля/методы этого класса:
Example.getFormat();🔸Явная загрузка класса:
cl.loadClass("Example");
Extension ClassLoader загружает редкие модули JDK: java.sql, jdk.httpserver и тд. Application ClassLoader загружает пользовательские классы.Схема поиска подходящего
.class файла выглядит так:❓Почему так?
✅ Чтобы обеспечить однозначность и приоритет классов JDK. При такой схеме невозможно создать свой класс с именем
❓Почему для классов JDK используется 2 загрузчика?
✅ Bootstrap работает на уровне виртуальной машины и загружает необходимый минимум классов на старте приложения. Extension ClassLoader - java объект, который загружает дополнительные модули JDK по мере необходимости.
❓Почему для отложенной загрузки классов нужны разные ClassLoader?
✅ Для безопасности классов JDK. Cуществует 4 модификатора доступа - private, protected, public и default. Последний ещё называют "доступ по умолчанию". Он даёт доступ к классам и методам того же пакета. Если мы назовём класс java.lang.Smth, он сможет работать с классами из пакета java.lang. Поэтому класс получает доступ к default полям другого класса только если:
🔸У него совпадает название пакета.
🔸Загружен тем же ClassLoader'ом.
JDK классы используют Bootstrap/Extension загрузчик, а наш java.lang.Smth - Application загрузчик, поэтому внутренние классы JDK недоступны классу Smth.
❓Что изменилось в java 9?
✅ Схема взаимодействия загрузчиков осталась той же. Поскольку JDK библиотеки
1️⃣ Изменились внутренние классы и методы загрузчиков. Проекты, которые используют загрузчики классов напрямую, столкнулись с проблемами совместимости.
2️⃣ Extension ClassLoader переименован в Platform ClassLoader. Теперь он загружает классы из модулей JDK и сторонних библиотек, одобренных Java Community: JDBC, JMS, JAX-RS и т.д.
✅ Чтобы обеспечить однозначность и приоритет классов JDK. При такой схеме невозможно создать свой класс с именем
java.lang.String. ❓Почему для классов JDK используется 2 загрузчика?
✅ Bootstrap работает на уровне виртуальной машины и загружает необходимый минимум классов на старте приложения. Extension ClassLoader - java объект, который загружает дополнительные модули JDK по мере необходимости.
❓Почему для отложенной загрузки классов нужны разные ClassLoader?
✅ Для безопасности классов JDK. Cуществует 4 модификатора доступа - private, protected, public и default. Последний ещё называют "доступ по умолчанию". Он даёт доступ к классам и методам того же пакета. Если мы назовём класс java.lang.Smth, он сможет работать с классами из пакета java.lang. Поэтому класс получает доступ к default полям другого класса только если:
🔸У него совпадает название пакета.
🔸Загружен тем же ClassLoader'ом.
JDK классы используют Bootstrap/Extension загрузчик, а наш java.lang.Smth - Application загрузчик, поэтому внутренние классы JDK недоступны классу Smth.
❓Что изменилось в java 9?
✅ Схема взаимодействия загрузчиков осталась той же. Поскольку JDK библиотеки
(rt.jar, tools.jar) теперь разбиты на модули, работа с ними поменялась:1️⃣ Изменились внутренние классы и методы загрузчиков. Проекты, которые используют загрузчики классов напрямую, столкнулись с проблемами совместимости.
2️⃣ Extension ClassLoader переименован в Platform ClassLoader. Теперь он загружает классы из модулей JDK и сторонних библиотек, одобренных Java Community: JDBC, JMS, JAX-RS и т.д.
У Вас есть домашний проект?
Anonymous Poll
11%
Да, активно развиваю
29%
Да, но редко им занимаюсь
51%
Нет, но хочу начать
8%
Нет и не планирую
Домашний проект: от выбора темы до пункта в резюме.
Написать работающую систему - важный шаг в обучении программированию. Закрепить теорию на практике, закрыть пробелы и попробовать новые технологии можно с помощью домашнего проекта. Давайте разберём популярные вопросы по этой теме:
❓Что писать?
Необязательно придумывать стартап, который изменит мир. Но если идей совсем нет:
1️⃣ Реализуйте с нуля текущий рабочий проект. Это очень удобно:
✅ Знакомая предметная область
✅ Понятные задачи
✅ Можно обсудить с коллегами непонятные места и текущие решения
2️⃣ Если рабочий проект надоел или не нравится, напишите свою версию популярной системы: Tinder, Яндекс.Маркет, «Кто хочет стать миллионером?» и тд. Помните, Вы пишете не "очередной велосипед", а тренируетесь и изучаете лучшие практики.
✅ Понятный функционал
✅ Современные подходы и технологии
❌ Детали реализации оригинала не всегда доступны, поэтому не с чем сравнить результат
❓Как работать над проектом?
Здесь Вы сами себе проджект-менеджер:
▫️Опишите функции до начала работы. Ориентируйтесь на текущие навыки и добавьте небольшой челлендж
▫️Декомпозируйте и отсортируйте задачи
▫️Составьте план
▫️Поставьте нестрогие дедлайны
▫️Двигайтесь от простого к сложному. Напишите основу на чистой java, добавьте maven/gradle, затем Spring, Spring Boot, базу данных и другие технологии
▫️Пишите тесты, это поможет найти узкие места
▫️Напишите эмулятор запросов, нагрузите приложение и изучите профайлеры
❓Нужно ли добавлять проект в резюме?
Как хотите🙂 Это большой бонус, если:
🔸Вы только начинаете карьеру разработчика.
🔸Домашний проект больше соответствует вакансии, чем текущее место работы.
🔸Вы хотите показать область своих интересов. Это полезно при собеседовании в компанию с большим количеством проектов.
❓Как подготовить проект к показу?
Прежде чем добавлять ссылку на проект в резюме, сделайте 2 несложных шага:
1️⃣ Добавьте файл
▪️О чём проект
▪️Основные функции
▪️Архитектуру (нарисуйте схему)
▪️Используемые технологии
2️⃣ Проработайте структуру проекта - дайте модулям и классам понятные имена. Функции, указанные в
Эти простые действия помогут сориентироваться в проекте и лучше оценить Вашу работу.
Написать работающую систему - важный шаг в обучении программированию. Закрепить теорию на практике, закрыть пробелы и попробовать новые технологии можно с помощью домашнего проекта. Давайте разберём популярные вопросы по этой теме:
❓Что писать?
Необязательно придумывать стартап, который изменит мир. Но если идей совсем нет:
1️⃣ Реализуйте с нуля текущий рабочий проект. Это очень удобно:
✅ Знакомая предметная область
✅ Понятные задачи
✅ Можно обсудить с коллегами непонятные места и текущие решения
2️⃣ Если рабочий проект надоел или не нравится, напишите свою версию популярной системы: Tinder, Яндекс.Маркет, «Кто хочет стать миллионером?» и тд. Помните, Вы пишете не "очередной велосипед", а тренируетесь и изучаете лучшие практики.
✅ Понятный функционал
✅ Современные подходы и технологии
❌ Детали реализации оригинала не всегда доступны, поэтому не с чем сравнить результат
❓Как работать над проектом?
Здесь Вы сами себе проджект-менеджер:
▫️Опишите функции до начала работы. Ориентируйтесь на текущие навыки и добавьте небольшой челлендж
▫️Декомпозируйте и отсортируйте задачи
▫️Составьте план
▫️Поставьте нестрогие дедлайны
▫️Двигайтесь от простого к сложному. Напишите основу на чистой java, добавьте maven/gradle, затем Spring, Spring Boot, базу данных и другие технологии
▫️Пишите тесты, это поможет найти узкие места
▫️Напишите эмулятор запросов, нагрузите приложение и изучите профайлеры
❓Нужно ли добавлять проект в резюме?
Как хотите🙂 Это большой бонус, если:
🔸Вы только начинаете карьеру разработчика.
🔸Домашний проект больше соответствует вакансии, чем текущее место работы.
🔸Вы хотите показать область своих интересов. Это полезно при собеседовании в компанию с большим количеством проектов.
❓Как подготовить проект к показу?
Прежде чем добавлять ссылку на проект в резюме, сделайте 2 несложных шага:
1️⃣ Добавьте файл
README.md в корневую директорию. Иногда это единственное, на что посмотрит посторонний человек. Не пожалейте времени, тщательно опишите:▪️О чём проект
▪️Основные функции
▪️Архитектуру (нарисуйте схему)
▪️Используемые технологии
2️⃣ Проработайте структуру проекта - дайте модулям и классам понятные имена. Функции, указанные в
README, должно быть легко найти в коде. Эти простые действия помогут сориентироваться в проекте и лучше оценить Вашу работу.
Forwarded from Java Developer
Java 25 лет!
Java, выпущенная в 1995 году под руководством Джеймса Гослинга, используется на 3 миллиардах устройств по всей планете, от мобильного телефона и PC, до холодильника и компьютера в автомобиле.
Более 10 миллионов разработчиков пишут на Java и других мощных языках, которые используют JVM: Kotlin, Scala, Groovy, Jython, JRuby, Clojure.
Поздравляю всех причастных: разработчиков, тестировщиков, менеджеров, аналитиков! Java — это сила!
Мы не знаем точно, что будет дальше в мире технологий, но Java явно войдет в историю. Быть причастным к этому и кодить на одном языке с массой крутых разработчиков очень приятно! С 25-летием, родная
@java_developer
Java, выпущенная в 1995 году под руководством Джеймса Гослинга, используется на 3 миллиардах устройств по всей планете, от мобильного телефона и PC, до холодильника и компьютера в автомобиле.
Более 10 миллионов разработчиков пишут на Java и других мощных языках, которые используют JVM: Kotlin, Scala, Groovy, Jython, JRuby, Clojure.
Поздравляю всех причастных: разработчиков, тестировщиков, менеджеров, аналитиков! Java — это сила!
Мы не знаем точно, что будет дальше в мире технологий, но Java явно войдет в историю. Быть причастным к этому и кодить на одном языке с массой крутых разработчиков очень приятно! С 25-летием, родная
@java_developer
Best practices: как проверять входные данные.
Запросы пользователей и ответы сторонних систем иногда приходят некорретными или в неправильном формате. Проверить данные можно по-разному, посмотрим разные варианты на простом примере:
1️⃣ Оставить как есть: тогда JVM выбросит
2️⃣ Добавить проверку и самим бросить исключение:
Начнём с производительности. Запустим каждый вариант миллион раз:
▪️14 мс - JVM исключение
▪️2 мс - явный выброс исключения
▪️0,03 мс - null
▪️0,03 мс - Optional.empty()
Что это значит?
1️⃣ Создание исключения гораздо дольше, чем выход из метода. Время уходит на создание стек-трейса - чем он глубже, тем хуже производительность. Стек-трейс из сервиса на Spring Boot собирается в 50-100 раз дольше, чем стек-трейс из метода main.
2️⃣ Если исключение явно создаётся в коде, JVM переиспользует ранее собранный стэк-трейс. Так получается выигрыш в 5-10 раз, но разница с вариантом без исключения всё ещё внушительна.
По этим цифрам легко решить, что исключения - удар по производительности. 10 лет назад была тенденция избегать исключений любой ценой. Например, возвращать из метода пару (результат, код_ошибки).
В 2020 это уже не актуально. Если взаимодействие в системе не построено на исключениях, то это капля в море относительно других операций. В примере сверху исключение создаётся миллион раз подряд - такого в продакшене обычно нет.
Исключения делают код чище:
🔹Понятное возвращаемое значение
🔹В части throws явно обозначены непредвиденные ситуации
🔹Логика метода и обработка ошибок разделены
Теперь сравним создание исключения и возврат null с точки зрения дизайна.
Исключение подразумевает обработку ошибки, например:
🔸Показать пользователю сообщение.
🔸Прекратить работу с файлом.
🔸Поменять формат данных.
Optional.empty(), null или объект по умолчанию возвращается, когда реагировать на ситуацию необязательно. Например, когда данные быстро устаревают. Возвращаемый тип Optional удобнее использовать: не нужно смотреть в документации, возвращает ли функция пустой результат, и как он обозначается: null, -1 или 0.
Запросы пользователей и ответы сторонних систем иногда приходят некорретными или в неправильном формате. Проверить данные можно по-разному, посмотрим разные варианты на простом примере:
String info(Request req) {
return req.toString(); }
Для нормальной работы параметр req не должен быть пустым. Мы можем:1️⃣ Оставить как есть: тогда JVM выбросит
NullPointerException(NPE)2️⃣ Добавить проверку и самим бросить исключение:
if (req==null) throw new NPE();3️⃣ Добавить проверку и вернуть null:
if (req==null) return null;4️⃣ Добавить проверку и вернуть Optional.empty():
if (req==null) return Optional.empty();Оценим их с точки зрения производительности и дизайна.
Начнём с производительности. Запустим каждый вариант миллион раз:
▪️14 мс - JVM исключение
▪️2 мс - явный выброс исключения
▪️0,03 мс - null
▪️0,03 мс - Optional.empty()
Что это значит?
1️⃣ Создание исключения гораздо дольше, чем выход из метода. Время уходит на создание стек-трейса - чем он глубже, тем хуже производительность. Стек-трейс из сервиса на Spring Boot собирается в 50-100 раз дольше, чем стек-трейс из метода main.
2️⃣ Если исключение явно создаётся в коде, JVM переиспользует ранее собранный стэк-трейс. Так получается выигрыш в 5-10 раз, но разница с вариантом без исключения всё ещё внушительна.
По этим цифрам легко решить, что исключения - удар по производительности. 10 лет назад была тенденция избегать исключений любой ценой. Например, возвращать из метода пару (результат, код_ошибки).
В 2020 это уже не актуально. Если взаимодействие в системе не построено на исключениях, то это капля в море относительно других операций. В примере сверху исключение создаётся миллион раз подряд - такого в продакшене обычно нет.
Исключения делают код чище:
🔹Понятное возвращаемое значение
🔹В части throws явно обозначены непредвиденные ситуации
🔹Логика метода и обработка ошибок разделены
Теперь сравним создание исключения и возврат null с точки зрения дизайна.
Исключение подразумевает обработку ошибки, например:
🔸Показать пользователю сообщение.
🔸Прекратить работу с файлом.
🔸Поменять формат данных.
Optional.empty(), null или объект по умолчанию возвращается, когда реагировать на ситуацию необязательно. Например, когда данные быстро устаревают. Возвращаемый тип Optional удобнее использовать: не нужно смотреть в документации, возвращает ли функция пустой результат, и как он обозначается: null, -1 или 0.
Что вернётся из try-catch-finally?
Вопросы такого типа популярны на собеседованиях. Поведение
Чтобы решать такие задачки, воспользуйтесь следующей моделью:
Результатом блока
🔸Ничего
🔸Возврат значения
🔸Исключение
Представим, что результат записывается в переменную result. В начале работы там "ничего".
Блоки обрабатываются в строгом порядке:
1️⃣ try
2️⃣ catch
3️⃣ finally
На каждом шаге переменная result может быть перезаписана. Итоговое значение result и будет результатом выполнения. Особый случай — при вызове
Разберём пример из опроса:
▫️try установит результат на "вернуть try".
▫️Блока catch нет.
▫️В finally результат меняется на "вернуть finally".
Итог: вернётся "finally"
Другой пример:
▫️Блок catch пропускается, т.к нет подходящего типа исключения.
▫️В блоке finally результат не переопределяется.
Итог: выброс NPE.
Этот пример важен, потому что такие ошибки часто встречаются на практике. Блок finally - не оберег от исключений. Если результат "исключение" дальше не переопределён, он пробрасывается в вышестоящий метод.
Вопросы такого типа популярны на собеседованиях. Поведение
try-catch-finally в спецификации описывается через 24 предложения "если". Логика запутанная, и легко допустить ошибку.Чтобы решать такие задачки, воспользуйтесь следующей моделью:
Результатом блока
try-catch-finally могут быть 3 варианта:🔸Ничего
🔸Возврат значения
🔸Исключение
Представим, что результат записывается в переменную result. В начале работы там "ничего".
Блоки обрабатываются в строгом порядке:
1️⃣ try
2️⃣ catch
3️⃣ finally
На каждом шаге переменная result может быть перезаписана. Итоговое значение result и будет результатом выполнения. Особый случай — при вызове
System.exit(0) выполнение прекращается сразу же .Разберём пример из опроса:
▫️try установит результат на "вернуть try".
▫️Блока catch нет.
▫️В finally результат меняется на "вернуть finally".
Итог: вернётся "finally"
Другой пример:
try { throw new NPE(); }
catch (SecurityEx e)
{ return "ex"; }
finally {}
▫️В блоке try результатом станет "NPE".▫️Блок catch пропускается, т.к нет подходящего типа исключения.
▫️В блоке finally результат не переопределяется.
Итог: выброс NPE.
Этот пример важен, потому что такие ошибки часто встречаются на практике. Блок finally - не оберег от исключений. Если результат "исключение" дальше не переопределён, он пробрасывается в вышестоящий метод.
Что вернёт метод?
Anonymous Quiz
7%
NullPointerException
20%
exception
67%
finally
6%
Ошибка компиляции
Stream API: ускоряемся🚀
Stream API помогает писать выразительный код, который быстро работает. Несложными действиями можно повысить скорость ещё больше:
1️⃣ Добавить parallel().
Для поддержки параллельности нужны дополнительные ресурсы, и прирост скорости заметен только если в коллекции много элементов. Если:
N – количество элементов,
Q – количество операций над каждым элементом,
то при N*Q>10000 можно смело добавлять parallel()
2️⃣ Добавить или удалить sorted(), unordered().
🔸sorted(): отсортировать стрим и добавить свойство «отсортирован».
🔸unordered(): выставить свойство «порядок не важен».
Источник данных и методы sorted, unordered, dictinct,... определяют свойства стрима: конечный размер, уникальность, отсортированные значения и другие. Благодаря этому некоторые методы оптимизируют работу. Если порядок не важен, параллельная обработка будет быстрее. Если список уже отсортирован, удаление повторяющихся элементов distinct() займёт меньше времени. Иногда наоборот — дополнительные ограничения замедляют работу.
Другие специфичные примеры.
3️⃣ Пользоваться специальными методами.
Чем меньше операций, тем быстрее всё работает. Проверить, что ни один элемент не удовлетворяет условию можно так:
none
Больше примеров
4️⃣ Объединить однотипные операции.
Чаще это касается сложной фильтрации:
Stream API помогает писать выразительный код, который быстро работает. Несложными действиями можно повысить скорость ещё больше:
1️⃣ Добавить parallel().
Для поддержки параллельности нужны дополнительные ресурсы, и прирост скорости заметен только если в коллекции много элементов. Если:
N – количество элементов,
Q – количество операций над каждым элементом,
то при N*Q>10000 можно смело добавлять parallel()
2️⃣ Добавить или удалить sorted(), unordered().
🔸sorted(): отсортировать стрим и добавить свойство «отсортирован».
🔸unordered(): выставить свойство «порядок не важен».
Источник данных и методы sorted, unordered, dictinct,... определяют свойства стрима: конечный размер, уникальность, отсортированные значения и другие. Благодаря этому некоторые методы оптимизируют работу. Если порядок не важен, параллельная обработка будет быстрее. Если список уже отсортирован, удаление повторяющихся элементов distinct() займёт меньше времени. Иногда наоборот — дополнительные ограничения замедляют работу.
Другие специфичные примеры.
3️⃣ Пользоваться специальными методами.
Чем меньше операций, тем быстрее всё работает. Проверить, что ни один элемент не удовлетворяет условию можно так:
filter(...).findFirst().isPresent()
а можно короче, понятнее и быстрее:none
Match(...)
Иногда стримы даже не нужны, много полезных методов есть в интерфейсе Collections. Например, максимальный элемент в списке быстрее найти через Collections.max(...), чем с использованием стрима.Больше примеров
4️⃣ Объединить однотипные операции.
Чаще это касается сложной фильтрации:
filter(1).filter(2).filter(3)
можно ускорить в 2-4 раза, если объединить условия:filter(1 & 2 & 3)❤3
IntelliJ IDEA: быстрый поиск.
Двойное нажатие Shift - универсальный шорткат для поиска. Он поможет:
✅ Найти класс, метод или файл.
✅ Открыть настройки: Tab Size, Code style, ...
✅ Выполнить команду:
Optimize imports,
Push (в репозиторий),
Show history,
Rename,
Presentation Mode
...и многие другие. Просто нажмите Shift-Shift, начните набирать слово и выберите подходящий вариант из списка.
Двойное нажатие Shift - универсальный шорткат для поиска. Он поможет:
✅ Найти класс, метод или файл.
✅ Открыть настройки: Tab Size, Code style, ...
✅ Выполнить команду:
Optimize imports,
Push (в репозиторий),
Show history,
Rename,
Presentation Mode
...и многие другие. Просто нажмите Shift-Shift, начните набирать слово и выберите подходящий вариант из списка.
👍2❤1
Скомпилируется ли код выше?
Anonymous Poll
63%
Да
18%
Нет, в метод нужно передать (Long) intValue
19%
Нет, в метод нужно передать (long) intValue
Система типов и проверка аргументов.
Язык программирования строится на многих формальных теориях. Одна из них - система типов. От неё зависит реализация языка, скорость компиляции и работы программы. Тема этого поста: как проверяются аргументы метода. Кажется, что ничего сложного здесь нет, но каждый язык делает это по-своему.
Первый вопрос: когда происходит проверка? В зависимости от ответа типизация может быть:
🔸Cтатической
🔸Динамической
При статической тип указателя навсегда закрепляется при создании:
✅ Отлавливается много ошибок.
✅ Быстро работает в рантайме.
✅ Предсказуемое поведение.
✅ Легко тестировать.
При динамической типизации указатель не имеет типа. Во время работы программы в переменную можно записать любые значения:
Java является языком со статической типизацией.
Второй вопрос, на который отвечает система типов: что делать, если метод принимает тип А, но приходит объект типа Б? Можно:
▪️Бросить ошибку.
▪️Преобразовать Б в тип А.
У языка сильная типизация, если допустимо мало преобразований. Если много, то типизация слабая. Речь идёт о неявных преобразованиях, которые делает компилятор или исполняющая среда.
Пример:
Компилятор java может перевести:
- подкласс в суперкласс
- long в int и наоборот
- int в Integer и наоборот
и так далее. Некоторые правила работают в одних условиях, некоторые в других. В целом java считается языком с сильной типизацией.
Вернёмся к вопросу перед постом. Метод
int → long
long → Long
Компилятор может выполнить только один шаг, второе преобразование делает программист. Поэтому подходящий ответ:
Язык программирования строится на многих формальных теориях. Одна из них - система типов. От неё зависит реализация языка, скорость компиляции и работы программы. Тема этого поста: как проверяются аргументы метода. Кажется, что ничего сложного здесь нет, но каждый язык делает это по-своему.
Первый вопрос: когда происходит проверка? В зависимости от ответа типизация может быть:
🔸Cтатической
🔸Динамической
При статической тип указателя навсегда закрепляется при создании:
String str;Ассоциированный объект имеет тот же тип или производный. Т.к типы известны заранее, проверка аргументов происходит во время компиляции.
✅ Отлавливается много ошибок.
✅ Быстро работает в рантайме.
✅ Предсказуемое поведение.
✅ Легко тестировать.
При динамической типизации указатель не имеет типа. Во время работы программы в переменную можно записать любые значения:
value = "а"Аргументы проверяются во время вызова метода. Такие программы в разы короче и легко адаптируются к изменениям.
value = 4
Java является языком со статической типизацией.
Второй вопрос, на который отвечает система типов: что делать, если метод принимает тип А, но приходит объект типа Б? Можно:
▪️Бросить ошибку.
▪️Преобразовать Б в тип А.
У языка сильная типизация, если допустимо мало преобразований. Если много, то типизация слабая. Речь идёт о неявных преобразованиях, которые делает компилятор или исполняющая среда.
Пример:
5L + 1
Язык Ruby не разрешает складывать числа разных типов, а java приведёт единицу к типу long и выполнит сложение. Выражение 5 + trueв java вызовет ошибку компиляции. JavaScript спокойно переведёт true в единицу.
Компилятор java может перевести:
- подкласс в суперкласс
- long в int и наоборот
- int в Integer и наоборот
и так далее. Некоторые правила работают в одних условиях, некоторые в других. В целом java считается языком с сильной типизацией.
Вернёмся к вопросу перед постом. Метод
id ожидает аргумент типа Long, а передаётся примитивный int. В правилах преобразований нет перехода int → Long, поэтому будет ошибка компиляции. Достичь цели можно в 2 шага:int → long
long → Long
Компилятор может выполнить только один шаг, второе преобразование делает программист. Поэтому подходящий ответ:
(long) intValueJava 8-11: новые методы String.
Чтобы не писать велосипеды и не добавлять в проект лишние библиотеки, посмотрите на новые методы в классе String:
🔸Соединить строки через разделитель:
▪️с начала строки:
🔸Проверить, что строка пуста:
🔸Создать стрим из строки. Можно разделить текст
▪️по линиям:
Чтобы не писать велосипеды и не добавлять в проект лишние библиотеки, посмотрите на новые методы в классе String:
🔸Соединить строки через разделитель:
String.join("-","1","2");
// 1-2
Можно использовать со списком строк:List<String> list = ...;🔸Убрать пробелы и служебные символы
String.join("-", list);
▪️с начала строки:
str.stripLeading();
▪️в конце строки: str.stripTrailing();
▪️с обеих сторон:str.strip();
В классе уже присутствует метод trim(), который тоже стирает неподходящие символы. strip() корректнее определяет недопустимые символы, в том числе экзотические виды пробелов ('\u00A0', '\u2007', '\u202F').🔸Проверить, что строка пуста:
str.isBlank()На замену StringUtils.isBlank() из библиотеки Apache Commons.
🔸Создать стрим из строки. Можно разделить текст
▪️по линиям:
str.lines()
▪️по символам: str.chars()
🔸Продублировать строку:"<td>".repeat(3);
// <td><td><td>