Секреты Java
Открыть в Telegram
Обсуждаем Java, архитектуру, фреймворки и всё, о чем должны говорить разработчики. Владелец: @Alexey_Morl Реклама на бирже: https://telega.in/c/java_secrets
Больше7 991
Подписчики
Нет данных24 часа
Нет данных7 дней
Нет данных30 день
Архив постов
7 991
Какое худшее время работы метода get(key) для ключа, который есть в HashMap?
O(N). Худший случай - это поиск ключа в
HashMap, вырожденного в список по причине совпадения ключей по hashCode() и для выяснения хранится ли элемент с определённым ключом может потребоваться перебор всего списка.
Но начиная с Java 8, после определенного числа элементов в списке, связный список преобразовывается в красно-черное дерево и сложность выборки, даже в случае плохой хеш-функции, не хуже логарифмической O(log(N)).7 991
Метод before()
Метод before() класса Date проверяет, была ли наша дата раньше той, которую мы передаем в качестве аргумента.
7 991
✅ Как прочитать файл из classpath?
Часто в коде бывает необходимо прочесть файл из classpath, например с конфигами или какими-либо ресурсами.
Предположим у нас есть файл
demo.txt, который лежит в
src/main/resources/config.
Рассмотрим варианты, как можно прочитать этот файл.
▫️Загрузка через ClassLoader:
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("config/demo.txt");
System.out.println(new String(in.readAllBytes()));
В ClassLoader все пути абсолютные, поэтому вам не нужен слеш в начале.
▫️Загрузка через класс:
InputStream in = this.getClass()
.getResourceAsStream("/config/demo.txt");
System.out.println(new String(in.readAllBytes()));
А вот через класс путь будет относительным, поэтому потребуется слеш в начале для пути к файлу, лежащем в другой директории.
▫️Еще вариант - получить ClassLoader из текущего потока:
ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
InputStream in = loader
.getResourceAsStream("config/demo.txt");
System.out.println(new String(in.readAllBytes()));
Так же путь абсолютный.7 991
Как конвертировать ArrayList в массив на Java?
Рассмотрим пример простой программы, которая преобразовывает ArrayList в массив в Java.
Мы используем реализацию метода toArray(T[] a) класса ArrayList, чтобы конвертировать ArrayList в массив на Java. «Под капотом» этого метода используется метод Arrays.copyOf(), чтобы конвертировать ArrayList в Array.
7 991
Аннотация @Value в Spring
Аннотация
@Value в Spring Framework позволяет внедрять значения свойств прямо в поля, конструкторы или параметры методов Spring-компонентов. Это удобный способ управлять настройками приложения из внешних файлов конфигурации или переменных окружения.
Примеры использования
Допустим, у нас есть файл application.properties:
app.server.value=My value
И поле в нужном нам классе:
@Value("${app.server.value}")
private String value;
По умолчанию Spring будет искать это свойство в application.properties(yml), но можно указать другой файл с помощью аннотации @PropertySource:
@Configuration
@PropertySource("classpath:another.properties")
public class Config {
@Value("${app.server.value}")
private String value;
...
}
Причем интересно, что Spring сначала все таки попробует найти значение в application.properties и если не найдет, пойдет искать в указанный файл.
И это только начало!
Внедряйте значение прямо из аннотации:
@Value("My valyue")
private String value;
Или установите дефолтное значение:
@Value("${app.server.value:some default value}")
private String value;
@Value может быть использована для внедрения значений переменных окружения или системных свойств. Если вы зададите системное свойство, то у него будет приоритет перед файлами свойств.
Можно внедрять и массив значений:
app.server.value=first, second, third
Свойство выше будет установлено в массив:
@Value("${app.server.values}")
private String[] values;
Значения, внедряемые с помощью @Value, могут содержать выражения SpEL (Spring Expression Language), что позволяет проводить сложные преобразования и комбинировать значения в различных контекстах, но это тема отдельного поста.
Напомню еще раз - все вышеуказанные варианты можно применять и для параметров конструкторов и методов.
Аннотация @Value предоставляет удобный способ внедрять значения из внешних источников в Spring-компоненты, что делает конфигурацию приложения более гибкой и управляемой.7 991
URL, URI и URN: чем они отличаются?
URL, URI и URN - это основные термины для идентификации ресурсов в интернете и мы часто их встречали (особенно первые два).
Давайте попробуем разобраться в них.
▫️URL (Uniform Resource Locator/Унифицированный указатель местоположения ресурса)
Указывает расположение ресурса и схему доступа к нему. Из стандарта URL:
URL-адреса используются для «определения расположения» ресурсов путем предоставления абстрактной идентификации расположения ресурса. Обнаружив ресурс, система может выполнять с ним различные операции, которые можно охарактеризовать такими словами, как «доступ», «обновление», «замена», «поиск атрибутов».Любой URL можно представить в виде
<scheme>:<scheme-specific-part>
Где <scheme> - это схема обращения к ресурсу (чаще всего это сетевой протокол), a <scheme-specific-part> - часть URL специфичная для этой схемы.
Например для протокола http, URL будет вида:
http://<host>:<port>/<path>?<searchpart>
а для Telnet:
telnet://<user>:<password>@<host>:<port>/
▫️URN (Uniform Resource Name/Унифицированное имя ресурса)
Из стандарта URN:
Унифицированные имена ресурсов (URN) предназначены для использования в качестве постоянных, независимых от местоположения, идентификаторов ресурсов и предназначены для облегчения сопоставления других пространств имен (которые разделяют свойства URN) с URN-пространством.URN определяет уникальное имя ресурса, и в отличии от URL, не содержит данных о местоположении ресурса и способе доступа к нему. URN появилась, чтобы закрыть недостатки URL - ресурсы перемещаются по сети, а URL остаются, указывая на уже отсутствующие ресурсы. А URN постоянны и не зависят от систем и протоколов. Пример URN:
urn:isbn:9785389049949
Этот URN идентифицирует книгу по ее ISBN номеру, где isbn - идентификатор пространства имён, а 9785389049949 - строка из данного пространства имен(как идентификатор в пространстве).7 991
Варианты конкатенации строк
В Java существует несколько способов конкатенации строк, каждый из которых имеет свои особенности и области применения. Рассмотрим основные методы:
▫️Оператор "+"
Самый простой и интуитивно понятный способ.
var result = "Hello" + " " + "World";
▫️StringBuilder
Является более эффективным способом конкатенации строк, особенно в циклах или при частом изменении строк.
var sb = new StringBuilder("Hello");
sb.append(" ");
sb.append("World");
sb.toString(); // Hello World
Но такой вариант не потокобезопасен.
▫️StringBuffer
Работает аналогично StringBuilder, но он синхронизирован и, следовательно, потокобезопасен.
var sb = new StringBuffer("Hello");
sb.append(" ");
sb.append("World");
sb.toString(); // Hello World
Но потокобезопасноть идет в ущерб производительности по сравнению со StringBuilder.
▫️String.concat()
var str = "Hello";
var result = str.concat(" ").concat("World"); // Hello World
Помните, что оригинальная строка не меняется! Производительнее оператора "+", принимает параметром только String. В отличии от "+" при аргументе null кидает NPE.
▫️String.join()
Позволяет конкатенировать строки с использованием разделителя (в примере первый аргумент).
var result = String.join(" ", "Hello", "World"); // Hello World
Следующие аргументы - последовательность! Т.е. можете передать несколько параметров, или закинуть массив. Лучше использовать когда есть массив строк, чем конкатенировать их в цикле.
▫️String.format()
Позволяет форматировать строки с помощью шаблонов, что может сделать код более читабельным.
var str1 = "Hello";
var str2 = "World";
var result = String.format("%s %s", str1, str2); // Hello World
Меньшая производительность по сравнению с другими методами и более сложный синтаксис для простых задач.
Выбор метода, конечно, зависит от конкретных задач и требований.7 991
Встроенные исключения
Java определяет несколько классов исключений внутри стандартного пакета java.lang.
Наиболее общие из этих исключений являются подклассами стандартного типа RuntimeException. Поскольку java.lang неявно импортируется во все java-программы, то большинство исключений, полученных из RuntimeException, автоматические.
Java определяет несколько других типов исключений, которые относятся к его различным библиотекам класса. Ниже приведен список неконтролируемых исключений на этапе выполнения (Unchecked RuntimeException).
7 991
Что такое mutable?
Mutable называются объекты, чьи состояния и переменные можно изменить после создания. Например такие классы, как StringBuilder, StringBuffer.
7 991
Final класс
Нельзя расширять класс, объявленный как final. Проще говоря, никакой класс не может наследоваться от данного.
Прекрасным примером final класса в JDK является String. Первый шаг к созданию неизменяемого класса — пометить его как final, и тогда нельзя будет его расширить.
7 991
Паттерны проектирования
Шаблон (паттерн) проектирования (design pattern) — это проверенное и готовое к использованию решение. Это не класс и не библиотека, которую можно подключить к проекту, это нечто большее - он не зависит от языка программирования, не является законченным образцом, который может быть прямо преобразован в код и может быть реализован по-разному в разных языках программирования.
ПОРОЖДАЮЩИЕ ПАТТЕРНЫ
✔️ Абстрактная фабрика (Abstract factory) - Класс, который представляет собой интерфейс для создания других классов.
✔️ Строитель (Builder) - Класс, который представляет собой интерфейс для создания сложного объекта.
✔️ Фабричный метод (Factory method) - Делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.
✔️ Прототип (Prototype) - Определяет интерфейс создания объекта через клонирование другого объекта вместо создания через конструктор.
✔️ Одиночка (Singleton) - Класс, который может иметь только один экземпляр.
СТРУКТУРНЫЕ ПАТТЕРНЫ
✔️ Адаптер (Adapter) - Объект, обеспечивающий взаимодействие двух других объектов, один из которых использует, а другой предоставляет несовместимый с первым интерфейс.
✔️ Мост (Bridge) - Структура, позволяющая изменять интерфейс обращения и интерфейс реализации класса независимо.
✔️ Компоновщик (Composite) - Объект, который объединяет в себе объекты, подобные ему самому.
✔️ Декоратор (Decorator) - Класс, расширяющий функциональность другого класса без использования наследования.
✔️ Фасад (Facade) - Объект, который абстрагирует работу с несколькими классами, объединяя их в единое целое.
✔️ Приспособленец (Flyweight) - Это объект, представляющий себя как уникальный экземпляр в разных местах программы, но по факту не являющийся таковым.
✔️ Заместитель (Proxy) - Объект, который является посредником между двумя другими объектами, и который реализует/ограничивает доступ к объекту, к которому обращаются через него.
ПОВЕДЕНЧЕСКИЕ ПАТТЕРНЫ
✔️ Цепочка обязанностей (Chain of responsibility) - Предназначен для организации в системе уровней ответственности.
✔️ Команда (Command) - Представляет действие. Объект команды заключает в себе само действие и его параметры.
✔️ Интерпретатор (Interpreter) - Решает часто встречающуюся, но подверженную изменениям, задачу.
✔️ Итератор (Iterator) - Представляет собой объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из объектов, входящих в состав агрегации.
✔️ Посредник (Mediator) - Обеспечивает взаимодействие множества объектов, формируя при этом слабую связанность и избавляя объекты от необходимости явно ссылаться друг на друга.
✔️ Хранитель (Memento) - Позволяет, не нарушая инкапсуляцию зафиксировать и сохранить внутренние состояния объекта так, чтобы позднее восстановить его в этих состояниях.
✔️ Наблюдатель (Observer) - Определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом событии.
✔️ Состояние (State) - Используется в тех случаях, когда во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния.
✔️ Стратегия (Strategy) - Предназначен для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости.
✔️ Шаблонный метод (Template method) - Определяет основу алгоритма и позволяет наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.
✔️ Посетитель (Visitor) - Описывает операцию, которая выполняется над объектами других классов. При изменении класса Visitor нет необходимости изменять обслуживаемые классы.
7 991
Использование ScheduledExecutorService
ScheduledExecutorService - это интерфейс, который предоставляет удобный способ для выполнения задач с определенной задержкой или периодом. Он является частью пакета java.util.concurrent и предоставляет мощные возможности для управления планированием выполнения задач. Рассмотрим как его можно использовать:
▫️Создаем пул потоков и указываем его размер (сколько потоков будет в пуле):
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
▫️Запуск задачи с определенной задержкой:
executor.schedule(() -> {
System.out.println("Task executed after 5 seconds");
}, 5, TimeUnit.SECONDS);
Этот код выведет в консоль текст через 5 секунд.
▫️Периодическое выполнение задачи с фиксированной задержкой:
executor.scheduleAtFixedRate(() -> {
System.out.println("Task executed at fixed rate");
}, 3, 1, TimeUnit.SECONDS);
Этот код будет выводить в консоль текст, каждую 1 секунду, начиная через 3 секунды после запуска. Т.е. каждую секунду executor будет запускать выполнение задачи, а если выполнение задачи идет дольше, чем заданная задержка, следующее выполнение начнется сразу же после завершения предыдущего.
▫️Периодическое выполнение задачи с фиксированной задержкой между выполнениями:
executor.scheduleWithFixedDelay(() -> {
System.out.println("Task executed with fixed delay");
}, 3, 1, TimeUnit.SECONDS);
Этот код будет выполнять то же самое, что и предыдущий пример, но с одним важным отличием - 1 секунда задержки будет применятся после завершения предыдущего выполнения, что означает, что интервал между окончанием одного выполнения и началом следующего всегда одинаковый.
ScheduledExecutorService - полезный инструментом для создания планировщиков, таймеров и других приложений, требующих управления временем.7 991
Switch Expressions
В Java 12 появился новый синтаксис для
switch, который позволяет возвращать значение используя стрелки ->, что делает код более компактным и удобным. Теперь switch может использоваться как выражение, а не только как оператор, что упрощает его применение в логике.
Ранее switch использовался как оператор, и код мог выглядеть громоздко:
String day = "MONDAY";
int numLetters;
switch (day) {
case "MONDAY":
case "FRIDAY":
case "SUNDAY":
numLetters = 6;
break;
case "TUESDAY":
numLetters = 7;
break;
default:
numLetters = 8;
}
System.out.println(numLetters); // Вывод: 6
Со Switch Expressions можно переписать этот код более лаконично:
String day = "MONDAY";
int numLetters = switch (day) {
case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
case "TUESDAY" -> 7;
default -> 8;
};
System.out.println(numLetters); // Вывод: 6
Теперь switch может вернуть значение, и нет необходимости в break.7 991
LocalDateTime: форматируем и парсим
В Java 8 в JSR 310 пришел достаточно удобный класс для работы с датой и временем без учета часовых поясов
LocalDateTime. Давайте рассмотрим как мы можем спарсить произвольную строку с датой и временем в объект LocalDateTime и как можем вывести LocalDateTime в нужном нам формате.
Чтобы создать объект LocalDateTime из строки с временем и датой в помщь идет класс DateTimeFormatter в котором задается формат:
String dateStr = "31-01-2022 10:15:30";
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
LocalDateTime dateTime =
LocalDateTime.parse(dateStr, formatter);
System.out.println(dateTime);
Вывод будет:
2022-01-31T10:15:30
А теперь посмотрим как превраить LocalDateTime в строку в нужном нам формате. Для этого воспользуемся методом format() класса LocalDateTime.
Например:
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = dateTime.format(formatter);
System.out.println(formattedDateTime);
Вывод будет (смотря когда вызываете) что-то вроде:
2024-01-21 12:43:29
Конечно, вы можете комбинировать символы в паттерне как вам угодно, чтобы получить нужный формат.
Полный список символов используемых в паттернах (а их большое количество) можно посмотреть в Java доках (ссылка).
Какие-то наиболее часто используемые форматы предопределены в DateTimeFormatter. Например DateTimeFormatter.ISO_DATE_TIME выведет дату и время в виде:
2024-01-31T10:54:39.39187437 991
Что такое Optional и как его применять?
Как часто вы ловили
NullPointerException?
Есть элегантное решение, как уменьшить количество таких ошибок. И это Optional.
Optional - это обертка над другими объектами, которые могут иметь значение или быть null. При использовании такого подхода вы сделаете свой код более безопасным и чистым.
Вот простой пример создания Optional:
var optionalStr = Optional.of("Hello, World!");
var emptyOptional = Optional.empty();
Optional содержит множество методов: isEmpty(), isPresent(), ifPresent(), map() и тд. Их нужно использовать с умом, чтобы код был лаконичным.
Вместо не очень элегантных проверок на null:
if(optionalStr.isPresent()) {
return someMethod(optionalStr.get());
} else {
return "str";
}
или делать какие-либо действия над значением ЕСЛИ оно присутствует:
return optionalStr.map(this::someMethod)
.orElse("str");
Зная нужные методы класса Optional, мы сделали код более коротким и красивым.
В этом примере .map() принимает ссылку на метод и возвращает результат выполнения этого метода, по сути, преобразуя значение. В случае если optionalString будет пустым, то .orElse() вернет значение, указанное в круглых скобках.
Используя Optional можно выстраивать цепочку операций в функциональном стиле:
optionalStr.map(String::toUpperCase)
.filter(s -> s.startsWith("H"))
.ifPresent(System.out::println);
Данный код преобразует строку в верхний регистр, проверит полученное значение на соответствие условию, и, если это условие будет истинно, то выведет строку в консоль.
Не бойтесь использовать Optional в своем коде, делайте его более устойчивым и читабельным.7 991
Как получить текущий stack trace?
Stack trace представляет собой последовательность методов, от последнего к первому, которые были вызваны выполняемой программой (потоком). С помощью него можно отследить где было выброшено исключение или через какую цепочку методов программа дошла до какой-либо места в коде. Удобно для дебага.
Чтобы получить текущий stack trace можно использовать вызов метода
getStackTrace() у настоящего потока, например:
public class StackTraceExample {
public static void main(String[] args) {
method1();
}
public static void method1() {
method2();
}
public static void method2() {
StackTraceElement[] stackTrace =
Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
System.out.println(element.getMethodName());
}
}
}
В данном примере метод method2 выводит все вызванные методы в стеке вызовов. Результат выполнения программы будет следующим:
getStackTrace
method2
method1
main
Объекты класса StackTraceElement представляют элементы стек стрейса:) Можно узнать различную информацию у данного элемента, такую как имя метода в данном месте стека (getMethodName()), имя класса (getClassName()) и другую.
Для получения stack trace с использованием Throwable можно вызвать метод getStackTrace(), который также возвращает массив StackTraceElement.
Пример использования Throwable для получения stack trace:
try {
// some code that may throw an exception
throw new Exception("Error occurred");
} catch (Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
for (StackTraceElement element : stackTrace) {
System.out.println(element.toString());
}
}
Оба эти способа позволяют получить информацию о стеке вызовов для отслеживания ошибок и исключений в Java.7 991
Варианты конфигурации контекста Spring приложения.
Рассмотрим какие есть варианты на примере Spring Boot приложения.
▫️XML-конфигурация:
Мы имеем файл конфигурации в classpath, допустим
beans.xml:
<beans <!-- some schema data--->>
<bean id="userService" class="com.example.UserService">
<property name="userRepository" ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.UserRepository"/>
</beans>
Для поднятия контекста на xml добавляем аннотацию @ImportResource("classpath:beans.xml") к нашему классу помеченному @SpringBootApplication (либо @Configuration).
Но стоит отметить, что XML-конфигурация почти изжила себя и мало используется.
▫️Annotation-based конфигурация:
При помощи аннотаций @Component, @Service, @Repository, @Controller мы можем указывать Spring'у какие бины нужно создавать в контексте, а связь между ними задать через @Autowired:
@Repository
public class UserRepository {
//...
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
//...
}
▫️Java-based конфигурация:
Позволяет определять бины и их зависимости непосредственно в Java-коде с использованием аннотаций @Configuration и @Bean:
@Configuration
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepository) {
return new UserService(userRepository);
}
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}
В каждом примере мы определили два бина - UserService и UserRepository, а так же связь между ними (инжект userRepository в userService). Выбор способа конфигурации зависит от ваших предпочтений и требований вашего проекта.
Справедливости ради - есть еще groovy конфигурация, но в своей практике я ее не встречал. Напишите в комментах, если работали с groovy и в каких кейсах это было полезно.7 991
Нормализация SQL баз данных: Первая нормальная форма (1NF)
Нормализация баз данных нужна для того, чтобы сделать данные в них более организованными, структурированными и легко управляемыми. Это помогает избежать избыточности информации и дублирования данных, что делает базу данных более эффективной, улучшает производительность и уменьшает вероятность возникновения ошибок. Нормализация также способствует обеспечению целостности данных, что означает, что данные в базе остаются точными, актуальными и надежными во всех условиях.
Существует несколько нормальных форм и мы рассмотрим их все в серии постов.
Первая нормальная форма базы данных требует, чтобы каждая ячейка таблицы содержала только одно значение одного типа данных и чтобы каждая запись была уникальной.
Пример без нормализации:
Предположим, у нас есть таблица
students, содержащая информацию о студентах и курсах, на которых они учатся.
| name | courses |
|----------|------------------|
| Алиса | Математика, Физика |
| Боб | Физика, Химия |
В этом примере информация о курсах хранится в одной колонке courses, что является нарушением нормализации.
С нормализацией:
Чтобы привести таблицу к 1NF, мы разделим колонку courses на отдельные строки, каждая из которых содержит только один предмет:
| name | courses |
|--------|-----------|
| Алиса | Математика|
| Алиса | Физика |
| Боб | Физика |
| Боб | Химия |
Теперь каждая колонка содержит только одно значение, и таблица соответствует требованиям 1NF.7 991
Spring Profiles
Spring Profiles - это мощный механизм Spring Framework, который позволяет легко настраивать контекст и использовать разные конфигурации для разных сред.
▫️Как определить профили?
Профили определяются с использованием аннотации
@Profile. Каждый профиль может иметь свои собственные настройки и бины:
@Configuration
@Profile("dev")
public class DevConfig {
}
Так же можно повесить эту аннотацию на класс компонента:
@Component
@Profile("dev")
public class DevComponent {
}
или метод создания бина в классе конфигурации:
@Configuration
public class Config {
@Bean
@Profile("dev")
public MyBean myBean() {
return new MyBean();
}
Для xml конфигурации:
<beans profile="dev">
<bean id="myBean" class="org.demo.MyBean" />
</beans>
▫️Как установить активный профиль?
Чтобы установить активный профиль, так же есть несколько различных вариантов:
- Через JVM параметры
-Dspring.profiles.active=dev
- Профили можно активировать через переменные окружения (для Unix/Windows соответственно):
export SPRING_PROFILES_ACTIVE=dev
set SPRING_PROFILES_ACTIVE=dev
- Через файл application.properties (yaml):
spring.profiles.active=dev
- Программно через ConfigurableEnvironment:
@Autowired
private ConfigurableEnvironment env;
...
env.setActiveProfiles("dev");
▫️Рассмотрим простой пример для понимания:
Есть два класса конфигурации для дева и прода:
@Configuration
@Profile("dev")
public class DevConfig {
// Конфигурация для дев
}
@Configuration
@Profile("prod")
public class ProdConfig {
// Конфигурация для прода
}
В файле application.yml:
spring:
profiles:
active: dev
В этом примере Spring будет использовать конфигурацию из DevConfig , а ProdConfig - только при активации профиля prod.
Профили могут использоваться для конфигурации различных аспектов приложения, таких как базы данных, настройки безопасности, внешние сервисы и многое другое. Например, можно настроить разные подключения к базам данных для разных сред выполнения (тестирование, разработка, продакшн).7 991
TreeSet
TreeSet - это множество уникальных и упорядоченных (в отличии от HashSet) элементов. TreeSet наследуется от AbstractSet и реализует интерфейс NavigableSet. Эта коллекция под капотом имеет TreeMap, которая использует для хранения бинарное красно-черное дерево, из-за чего элементы в этой структуре данных хранятся в отсортированном порядке.
Так как при вставке и поиске значений TreeSet использует сравнение через метод
compare/compateTo, то добавляемые элементы должны реализовывать интерфейс Comparable, либо при создании коллекции TreeSet через конструктор необходимо указать Comparator.
Передавая через конструктор реализацию Comparator можно изменить способ сортировки элементов в коллекции.
Set<Integer> tree = new TreeSet<>(Comparator.reverseOrder());
При указании Comparator нужно быть аккуратным, так как TreeSet гарантирует уникальность элементов, сравнивая их через метод compare/compateTo !
Set<String> tree = new TreeSet<>(Comparator.comparing(String::length));
tree.add("Hello");
tree.add("World");
В данном примере TreeSet сравнивает строки по их длине. Так как "Hello" и "World" имеют одинаковую длину, то в коллекцию будет добавлена только первая строка.
Данную коллекцию имеет смысл использовать если:
▫️нужна автоматическая сортировка уникальных элементов
▫️необходимо быстро находить элементы в определенном диапазоне