fa
Feedback
Секреты Java

Секреты Java

رفتن به کانال در Telegram

Обсуждаем Java, архитектуру, фреймворки и всё, о чем должны говорить разработчики. Владелец: @Alexey_Morl Реклама на бирже: https://telega.in/c/java_secrets

نمایش بیشتر
7 991
مشترکین
اطلاعاتی وجود ندارد24 ساعت
اطلاعاتی وجود ندارد7 روز
اطلاعاتی وجود ندارد30 روز
آرشیو پست ها
Пакетное выполнение запросов — это техника, которая позволяет выполнять несколько SQL-запросов в одной транзакции, что помогает оптимизировать производительность и уменьшить накладные расходы на соединение с базой данных. Основные методы для работы с пакетами запросов — это addBatch() и executeBatch(), которые являются частью интерфейса Statement (а также PreparedStatement и CallableStatement). Метод addBatch() добавляет SQL-запрос (или его параметризованную версию в случае с PreparedStatement) в пакет запросов для последующего выполнения. Каждый вызов этого метода добавляет один SQL-запрос в очередь пакета. Метод executeBatch() выполняет все запросы, добавленные в пакет. Пример:

Class.forName("oracle.jdbc.OracleDriver");
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "username", "password");
Statement s = con.createStatement();

String sql1 = "CREATE TABLE STUDENT(STUDENTID VARCHAR2(10) PRIMARY KEY,NAME VARCHAR2(20),DEPARTMENT VARCHAR2(10))";
String sql2 = "INSERT INTO STUDENT VALUES('S101','JEAN','CSE')";
String sql3 = "INSERT INTO STUDENT VALUES('S102','ANA','CSE')";
String sql4 = "INSERT INTO STUDENT VALUES('S103','ROBERT','ECE')";

s.addBatch(sql1);
s.addBatch(sql2);
s.addBatch(sql3);
s.addBatch(sql4);

s.executeBatch();
con.commit();
con.close();

⌨️ Какие существуют модификаторы доступа? В Java модификаторы доступа определяют, из каких частей программы можно получить доступ к классу, методу или переменной. Всего их четыре: public – предоставляет полный доступ к классу или его членам из любого другого класса в любом пакете. Применяется для методов, полей и классов, которые должны быть доступны всем. protected – доступ к члену класса разрешен в пределах того же пакета и для подклассов, даже если они находятся в других пакетах. Используется для полей и методов, которые могут быть полезны в наследовании. default (пакетный доступ) – если модификатор не указан явно, доступ к члену класса разрешен только внутри того же пакета. Этот доступ используется для внутренних структур, не предназначенных для использования в других пакетах. private – доступен только внутри того же класса. Поля и методы, помеченные как private, невидимы для других классов, включая подклассы. Эти модификаторы помогают контролировать видимость и инкапсуляцию данных в программе.

Swagger: документирование REST API вашего Spring приложения Зачем нам нужна документация? Когда мы пишем backend приложение,
Swagger: документирование REST API вашего Spring приложения Зачем нам нужна документация? Когда мы пишем backend приложение, то ожидаем, что к нему будут обращаться другие системы, будь то фронт, моб. приложения или прочие сервисы. Разработчикам, которые захотят использовать наше приложение понадобится API документация, чтобы знать по какому протоколу с нами взаимодействовать (на какие URL какие запросы посылать). Написание документации - точно не самое увлекательное занятие, но библиотека Springdoc OpenAPI сделает все за нас. Она интегрируется напрямую с нашим кодом и автоматически создает документацию Swagger для ваших контроллеров, моделей, запросов и ответов. Для начала работы нужно всего лишь добавить соответствующую зависимость в build.gradle нашего проекта:
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
для Spring boot версии 2, либо:
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
для Spring boot версии 3. И это все! Springdoc OpenAPI автоматически отсканирует наше приложение и сгенерирует документацию. Мы можем добавить деталей:
@Tag(name = "Entities")
@RestController
public class Controller {

    @Operation(summary = "Get entity")
    @GetMapping("/")
    public Object get() {
        return "Hello world!";
    }
}
Тут мы использовали доп. аннотации @Tag для объединения эндпоинтов по тегам и @Operation, чтобы задать описание эндпоинта (страница Swagger после будет выглядеть как на изображении к посту). Конечно, есть и другие аннотации и инструменты этой библиотеки, которые помогут подробно, в деталях, описать ваш API. По дефолту: ▫️http://<host>:<port>/swagger-ui.html - доступ к Swagger UI ▫️http://<host>:<port>/v3/api-docs - API docs Springdoc OpenAPI предоставляет простой и удобный способ создания и поддержания документации вашего API на основе Spring Framework. Используйте его в вашем проекте, чтобы значительно упростить себе жизнь, когда у вас попросят документацию по REST API вашего приложения.

Что такое механизм try-with-resources? Данная конструкция, которая появилась в Java 7, позволяет использовать блок try-catch
Что такое механизм try-with-resources? Данная конструкция, которая появилась в Java 7, позволяет использовать блок try-catch не заботясь о закрытии ресурсов, используемых в данном сегменте кода. Ресурсы объявляются в скобках сразу после try, а компилятор уже сам неявно создаёт секцию finally, в которой и происходит освобождение занятых в блоке ресурсов. Под ресурсами подразумеваются сущности, реализующие интерфейс java.lang.Autocloseable. Стоит заметить, что блоки catch и явный finally выполняются уже после того, как закрываются ресурсы в неявном finally.

Dockerfile Как говорилось в прошлом посте про Docker, Dockerfile - это текстовый файл, который содержит команды для сборки образа Docker. Этот файл определяет, как именно будет выглядеть контейнер, включая операционную систему, зависимости, настройки и команды, которые нужно выполнить. Вот список основных инструкций, которые могут быть применены при написании dockerfile: ▫️FROM - указывает родительский образ ▫️RUN - выполняет команды в контейнере на этапе сборки ▫️COPY - копирование файлов проекта ▫️ADD - копирует файлы по ссылке и распаковываете архивы ▫️CMD - указывает команду, которая будет выполняться при запуске контейнера ▫️ENV - задает переменные среды окружения внутри контейнера ▫️WORKDIR - задаёт рабочую папку проекта ▫️EXPOSE - указывает какой порт будет слушать сервис в контейнере Напишем простой dockerfile для приложения, в котором есть только один REST контроллер:
FROM gradle:7.6.0-jdk17
WORKDIR /app
COPY . .
RUN gradle build --no-daemon
EXPOSE 8080
CMD ["java", "-jar", "/app/build/libs/demo-0.0.1-SNAPSHOT.jar"]
1) В качестве основы использует образ, который содержит Gradle с JDK 17 2) Создаем рабочую директорию 3) Копируем все файлы (файлы для gradle и исходный код) 4) Собираем приложение 5) Открываем порт для приложения 6) Указываем команду для запуска нашего приложения Осталось выполнить пару команд в консоли, чтобы собрать наш образ и запустить на его основе контейнер:
docker build -t demo .
docker run -p 8080:8080 demo
Аргументы первой команды: ▫️-t demo - название образа ▫️. - путь по которому Docker будет искать Dockerfile. Точка = текущая директория Аргументы второй команды: ▫️-p 8080:8080 - связываем порт 8080 контейнера с портом 8080 на хосте ▫️demo - название Docker образа, на основе которого будет запускаться контейнер Выполнив команду docker images можем увидеть наш образ:
REPOSITORY  TAG     CREATED        SIZE
demo        latest  9 seconds ago  889MB
Убедимся, что контейнер запущен, выполнив docker ps в командной строке:
CONTAINER ID  IMAGE  STATUS          PORTS                   
e343769f7330  demo   15 seconds ago  0.0.0.0:8080->8080/tcp 
Если контейнер успешно запущен, то вы должны увидеть подобный результат. Поздравляю, теперь вы научились запускать свое приложение в Docker. Если вас тоже смущает тяжелый docker образ (889MB) для небольшого демо приложения, то подготовлю для вас пост про оптимизацию dockerfile)

double vs. BigDecimal В Java есть два типа данных, которые могут быть использованы для работы с числами с плавающей точкой: double и BigDecimal, однако они имеют свои особенности и различия. Тип double представляет собой 64-битное число с плавающей точкой, работающее по стандарту IEEE 754. Этот стандарт определяет способ преобразования действительных чисел в двоичный формат, а т.к в двоичном виде иногда невозможно точно представить десятичные дроби, то как следствие double имеют ограниченную точность и проблемы с округлением. Рассмотрим это на примере:
double first = 0.1;
double second = 0.2;
double result = first + second;

System.out.println(result); // Выводит 0.30000000000000004
Мы ожидали результат 0.3, но фактически он отличается. Класс BigDecimal представляет неизменяемое (immutable) десятичное число с произвольной точностью. Говоря простым языком - BigDecimal состоит из двух частей: целого числа и значения scale, определяющего количество цифр после запятой. Класс BigDecimal обеспечивает более высокую точность, чем double, поскольку он выполняет вычисления с десятичными дробями используя целочисленную арифметику, которая более точна, чем арифметика чисел с плавающей точкой. Те же вычисления, но с BigDecimal:
var first = new BigDecimal("0.1");
var second = new BigDecimal("0.2");
var result = first.add(second);

System.out.println(result); // Выводит 0.3
Результат точный, но важно отметить, что работа с BigDecimal может быть более медленной по сравнению с double, так как BigDecimal использует более сложный механизм для хранения и выполнения операций с числами. В заключение, если вам необходимо обрабатывать числа с высокой точностью без проблем с округлением, например финансовые вычисления, то используйте BigDecimal. Если скорость и производительность является приоритетом, а абсолютная точность не столь важна, то используйте double.

Чем отличаются JAR и WAR файлы? JAR (Java ARchive) и WAR (Web Application Archive) - это два основных типа архивных файлов в Java, каждый из которых предназначен для различных целей и сред выполнения. Чем же они отличаются? ▫️Расширение: Самое очевидное это, конечно, расширения файлов - .jar и .war соответственно. ▫️Назначение: JAR файлы используются для упаковки и распространения библиотек, приложений или модулей в Java. Они могут содержать Java классы, ресурсы, библиотеки и метаданные. А WAR файлы предназначены для развертывания веб-приложений. И, помимо перечисленного для JAR, они содержат весь необходимый контент для работы веб-приложения, включая HTML, CSS, JavaScript, JSP, Servlets, библиотеки и другие ресурсы. ▫️Структура: JAR файлы могут иметь различную структуру, но обычно они содержат скомпилированные классы Java, а также ресурсы и метаданные проекта. Самый простой пример структуры:
META-INF/
    MANIFEST.MF
com/
    myapp/
        MyApp.class
WAR файлы имеют специальную структуру, соответствующую структуре веб-приложения. Обычно они содержат каталоги WEB-INF, META-INF, а также ресурсы веб-приложения:
META-INF/
    MANIFEST.MF
WEB-INF/
    web.xml
    jsp/
        myApp.jsp
    classes/
        static/
        templates/
        application.properties
    lib/
        // different *.jar libs
▫️Среда выполнения: JAR файлы могут быть запущены непосредственно в среде JVM без необходимости сервера приложений. WAR файлы развертываются и запускаются на сервере приложений, таких как Apache Tomcat/Jetty/WildFly, который обеспечивает контейнер сервлетов и другие службы веб-приложений.

Проброс исключений Иногда возникает необходимость передать исключение дальше по стеку вызовов, чтобы оно было обработано на более высоком уровне. Для этого можно использовать ключевое слово throws в сигнатуре метода и не перехватывать исключение в блоке catch, либо пробросить исключение дальше внутри блока catch.

public void someMethod() throws IOException {
    try {
        // код, который может выбросить IOException
    } catch (IOException e) {
        // возможно, выполнить какую-то обработку
        throw e; // проброс исключения дальше
    }
}

Описание и назначение разных циклов Цикл for со счетчиком

for (initialization; condition; update) {
    // тело цикла
}
Описание: initialization: выполняется один раз перед началом цикла. Обычно используется для инициализации счетчика. condition: проверяется перед каждой итерацией. Если условие истинно, выполняется тело цикла. update: выполняется после каждой итерации. Обычно используется для изменения счетчика.

int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
    System.out.println(i + ": " + numbers[i]);
}
Применение: Используется, когда известно количество итераций заранее. Часто применяется для работы с массивами или коллекциями, когда нужен индекс. Цикл for-each

for (type element : collection) {
    // тело цикла
}
Описание: Итерируется по каждому элементу коллекции (например, массива, списка, множества). Обеспечивает удобный способ доступа к каждому элементу без использования индексов.

int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
    System.out.println(number);
}
Применение: Предпочтителен, когда не нужен доступ к индексам элементов. Цикл while

while (condition) {
    // тело цикла
}
Описание: condition: проверяется перед каждой итерацией. Если условие истинно, выполняется тело цикла.

String code = UUID.randomUUID().toString();
boolean isCodeExists = repo.isCodeExists(code);
while (isCodeExists) {
    code = UUID.randomUUID().toString();
    isCodeExists = repo.isCodeExists(code);
}
repo.saveCode(code);
Применение: Используется, когда количество итераций неизвестно заранее и определяется во время выполнения программы. Цикл может не выполниться ни одного раза. Цикл do-while

do {
    // тело цикла
} while (condition);
Описание: Выполняет тело цикла хотя бы один раз, а затем проверяет условие condition. Если условие истинно, цикл повторяется.

String command;
do {
    command = readCommand();
    doCommand(command);
} while (!"EXIT".equals(command));
Применение: Используется, когда тело цикла должно выполниться хотя бы один раз, независимо от условия. Часто применяется для меню или ввода данных, где сначала нужно выполнить действие, а затем проверить условие.

Чем абстрактный класс отличается от интерфейса? В каких случаях следует использовать абстрактный класс, а в каких интерфейс? ✅ В Java класс может одновременно реализовать несколько интерфейсов, но наследоваться только от одного класса. ✅ Абстрактные классы используются только тогда, когда присутствует тип отношений «is a» (является). Интерфейсы могут реализоваться классами, которые не связаны друг с другом. ✅ Абстрактный класс - средство, позволяющее избежать написания повторяющегося кода, инструмент для частичной реализации поведения. Интерфейс - это средство выражения семантики класса, контракт, описывающий возможности. Все методы интерфейса неявно объявляются как public abstract или (начиная с Java 8 ) default - методами с реализацией по-умолчанию, а поля - public static final. ✅ Интерфейсы позволяют создавать структуры типов без иерархии. ✅ Наследуясь от абстрактного, класс «растворяет» собственную индивидуальность. Реализуя интерфейс, он расширяет собственную функциональность. Абстрактные классы содержат частичную реализацию, которая дополняется или расширяется в подклассах. При этом все подклассы схожи между собой в части реализации, унаследованной от абстрактного класса, и отличаются лишь в части собственной реализации абстрактных методов родителя. Поэтому абстрактные классы применяются в случае построения иерархии однотипных, очень похожих друг на друга классов. В этом случае наследование от абстрактного класса, реализующего поведение объекта по умолчанию может быть полезно, так как позволяет избежать написания повторяющегося кода. Во всех остальных случаях лучше использовать интерфейсы.

Скрытие полей (Hiding Fields) Внутри класса поле, имеющее то же имя, что и поле суперкласса, скрывает поле суперкласса, даже
Скрытие полей (Hiding Fields) Внутри класса поле, имеющее то же имя, что и поле суперкласса, скрывает поле суперкласса, даже если их типы различны. Внутри подкласса на поле суперкласса нельзя ссылаться по простому имени. Вместо этого доступ к полю должен осуществляться через super. Скрывать поля не рекомендуем, поскольку это затрудняет чтение кода.

Чем IllegalArgumentException лучше чем NullPointerException? Речь здесь идет о выборе подхода к обработке ошибки, когда пользователь передал в non-nullable параметр значение null. Технически, оба этих исключения unchecked, оба из стандартной библиотеки, и особой разницы нет. Однако семантически эти исключения отличаются. NullPointerException говорит пользователю о попытке обратиться к членам класса через null-ссылку. Это лишь техническое описание, без информации для пользователя о логике программы. IllegalArgumentException, напротив, явно говорит о недопустимом значении аргумента – это понятная для пользователя информация. Семантическая разница иногда проявляется и технически. Например, в обработчиках исключений некоторых фреймворков именно IllegalArgumentException превращается в HTTP-ответ с кодом 400 Bad Request, в то время как NPE остается общим кодом «неизвестной ошибки» 500 Internal Server Error. Кроме того, чтобы выбросить NullPointerException не требуется явного кода обработки null. Остается неизвестным, ожидаемо ли в этом случае исключение, или разработчик попросту забыл добавить корректную обработку. Явная обработка null и выброс IllegalArgumentException для пользователя кода будет служить документацией, а сообщение об ошибке в аргументе его конструктора внесет еще больше ясности.

Как получить гарантированный дедлок? Сначала поговорим о том, что это такое. Deadlock – это взаимная блокировка, ситуация, ко
Как получить гарантированный дедлок? Сначала поговорим о том, что это такое. Deadlock – это взаимная блокировка, ситуация, когда два или более потока «наступают друг-другу на хвост» – зависают в вечном ожидании ресурсов, захваченных друг другом. Livelock – похожая проблема, с тем лишь отличием, что потоки не останавливаются, а вместо этого зацикливаются, выполняя одни и те же бесполезные действия, ходят по кругу. Стандартный подход к обеспечению гарантии защиты от дедлока – установка строгого порядка взятия блокировок. Если для мониторов A и B соблюдается всеобщий порядок захвата AB (и соответственно отпускания BA), то ни с одним потоком не случится попасть на ожидание B, успешно при этом захватив A. Из этого можно догадаться, простой способ гарантировать возможность дедлока – явно нарушить это условие. Нарушение условия даст дедлок «скорее всего когда-нибудь». Чтобы получить его точно и с первого раза, нужно гарантировать, что оба потока окажутся на этапе между захватами одного и другого ресурса в одно время. Это можно сделать множеством способов, в примере ниже использован CyclicBarrier.

⌨️ Что такое finalize()? Зачем он нужен? Через вызов метода finalize() (который наследуется от java.lang.Object) JVM реализуется функциональность аналогичная функциональности деструкторов в С++, используемых для очистки памяти перед возвращением управления операционной системе. Данный метод вызывается при уничтожении объекта сборщиком мусора (garbage collector) и переопределяя finalize() можно запрограммировать действия необходимые для корректного удаления экземпляра класса - например, закрытие сетевых соединений, соединений с базой данных, снятие блокировок на файлы и т.д. После выполнения этого метода объект должен быть повторно собран сборщиком мусора (и это считается серьезной проблемой метода finalize() т.к. он мешает сборщику мусора освобождать память). Вызов этого метода не гарантируется, т.к. приложение может быть завершено до того, как будет запущена сборка мусора. Объект не обязательно будет доступен для сборки сразу же - метод finalize() может сохранить куда-нибудь ссылку на объект. Подобная ситуация называется «возрождением» объекта и считается антипаттерном. Главная проблема такого трюка - в том, что «возродить» объект можно только 1 раз. Пример:

public class MainClass {
 public static void main(String args[]) {
  TestClass a = new TestClass();
  a.a();
  a = null;
  a = new TestClass();
  a.a();
  System.out.println("!!! done");
 }
}

public class TestClass {
 public void a() {
  System.out.println("!!! a() called");
 }

 @Override
 protected void finalize() throws Throwable {
  System.out.println("!!! finalize() called");
  super.finalize();
 }
}
Так как в данном случае сборщик мусора может и не быть вызван (в силу простоты приложения), то результат выполнения программы с большой вероятностью будет следующий:
!!! a() called
!!! a() called
!!! done
Теперь несколько усложним программу, добавив принудительный вызов Garbage Collector:

public class MainClass {
 public static void main(String args[]) {
  TestClass a = new TestClass();
  a.a();
  a = null;
  System.gc(); // Принудительно зовём сборщик мусора
  a = new TestClass();
  a.a();
  System.out.println("!!! done");
 }
}
Как и было сказано ранее, Garbage Collector может в разное время отработать, поэтому результат выполнения может разниться от запуска к запуску. Вариант а:
!!! a() called
!!! a() called
!!! done
!!! finalize() called
Вариант б:
!!! a() called
!!! a() called
!!! finalize() called
!!! done

Как используется метод Lock.newCondition()? Если реализации интерфейса Lock представляют высокоуровневую альтернативу блока synchronized, то реализации его спутника, интерфейса Condition – альтернатива методам notify/wait. Оба этих интерфейса относятся к пакету java.util.concurrent.locks. Как и ожидание на мониторе, Condition реализует примитив синхронизации «Условная переменная». Один или несколько потоков зависают на объекте-кондишне с помощью варианта метода await (ждут удовлетворения условия). Другой поток пробуждает их методами signal и signalAll (сигнализирует об удовлетворении условия). Конкретные реализации Condition всегда решают те же задачи, что блокировка на мониторе, но в теории могут отличаться в нюансах поведения. Например, может не быть требования вызывать ожидание/сигнал только при захваченном локе (аналог требования, по которому notify/wait всегда вызываются в synchronized). Или может гарантироваться порядок получения сигнала ожидающими потоками. Возвращаясь к поставленному вопросу, Condition всегда связан со своим объектом типа Lock, и метод Lock.newCondition() – единственный правильный способ создания кондишна.

Что такое Java-конфигурация? Как она применяется? Чтобы в Spring создать класс с конфигурацией на основе Java-кода, нужно анн
Что такое Java-конфигурация? Как она применяется? Чтобы в Spring создать класс с конфигурацией на основе Java-кода, нужно аннотировать его с помощью @Configuration. Этот класс будет содержать фабричные методы для создания бинов в контейнере. Данные методы должны быть аннотированы аннотацией @Bean. Класс с конфигурацией поместит в контейнер экземпляр класса DataSource. Потом его можно будет использовать при доступе к базе данных.

Поддерживает ли язык Java множественное наследование? Это очень хитрый вопрос. Интервьюеры часто говорят: если язык C++ может поддерживать непосредственное множественное наследование, то почему Java не может? Ответ несколько более сложен, чем может показаться на первый взгляд, поскольку Java поддерживает множественное наследование типов, ведь интерфейс в нём может расширять другие интерфейсы. Но множественное наследование реализаций язык Java не поддерживает.

Конвертируем массив в список Arrays.asList() — это статический метод класса Arrays, который позволяет конвертировать обычный
Конвертируем массив в список Arrays.asList() — это статический метод класса Arrays, который позволяет конвертировать обычный массив в список List. — Принимает массив в качестве аргумента и возвращает объект типа List с элементами этого массива. — Возвращаемый список имеет фиксированный размер, равный длине исходного массива. Добавлять/удалять элементы в него нельзя. — Изменения в возвращаемом списке будут вноситься в исходный массив, т. к. список работает как «представление» массива. Метод работает для массивов примитивных и ссылочных типов. Arrays.asList хорош для чтения элементов массива, но не для записи из-за неизменяемого размера.

Что выведет код?
Anonymous voting

public class Test {

    public static int VAL;

    {
        VAL = 5;
    }

    public Test() {
        VAL = 10;
    }

    public static void print() {
        System.out.println(VAL);
    }

}

public class Quest {
    public static void main(String[] args) {
        Test.print();
    }
}