Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Подборка лучших практик программирования на Java для экономии времени, оптимизации и улучшения качества кода.
На Java работают более 3 миллиардов устройств и кодят больше 9 миллионов разработчиков. Подсчитать количество приложений просто невозможно. Это мощный и надежный язык, но иногда программирование на Java может быть трудоемким.
На отладку, оптимизацию и повышение производительности кода уходит много времени, поэтому хороший разработчик всегда использует лучшие практики программирования.
В этой статье мы подобрали небольшую коллекцию таких практик, трюков и советов, которые сэкономят вам время.
Если вы начинающий Java-программист, то скачать Java можно на официальном сайте.
☕ Подтянуть свои знания по Java вы можете на нашем телеграм-канале «Библиотека Java для собеса»
Организация работы
Чистый код
В больших проектах на первый план выходит не создание нового кода, а поддержка существующего. Поэтому очень важно его правильно организовывать с самого начала. Проектируя новое приложение, всегда помните о трех основных принципах чистого и поддерживаемого кода:
- правило 10-50-500. В одном пакете не может быть больше 10 классов. Каждый метод должен быть короче 50 строк кода, а каждый класс – короче 500 строк;
- дизайн-принципы SOLID;
- использование паттернов проектирования.
Работа с ошибками
Трассировка стека
Отлов багов – это, возможно, самая трудоемкая составляющая процесса разработки на Java. Трассировка стека позволяет отследить, в каком именно месте проекта было выброшено исключение.
import java.io.*; Exception e = …; java.io.StringWriter sw = new java.io.StringWriter(); e.printStackTrace(new java.io.PrintWriter(sw)); String trace = sw.getBuffer().toString();
NullPointer Exception
Исключения нулевого указателя возникает в Java довольно часто при попытке вызова метода несуществующего объекта.
Возьмем для примера следующую строчку кода:
int noOfStudents = school.listStudents().count;
Если объект school окажется равен Null
или его метод listStudents
вернет Null
, вы получите исключение NullPointerException
.
Хорошей практикой разработки на Java является предварительная проверка на Null
в методах:
private int getListOfStudents(File[] files) { if (files == null) throw new NullPointerException("File list cannot be null"); }
🧩☕ Интересные задачи по Java для практики можно найти на нашем телеграм-канале «Библиотека задач по Java»
Дата и время
System.currentTimeMillis vs System.nanoTime
В Java есть два стандартных способа проведения тайм-операций и не всегда понятно, какой из них следует выбрать.
Метод System.currentTimeMillis
возвращает текущее количество миллисекунд от начала эпохи Unix в формате Long
. Его точность составляет от 1 до 15 тысячных долей секунды в зависимости от системы.
long startTime = System.currentTimeMillis(); long estimatedTime = System.currentTimeMillis() - startTime;
Метод System.nanoTime
имеет точность до одной миллионной доли секунды (наносекунды) и возвращает текущее значение наиболее точного доступного системного таймера.
long startTime = System.nanoTime(); long estimatedTime = System.nanoTime() - startTime;
Таким образом, System.currentTimeMillis
отлично подходит для отображения абсолютного времени и синхронизации с ним, а System.nanoTime
– для измерения относительных интервалов.
Валидность строки с датой
Если вам необходимо получить объект даты из обычной строки на Java, воспользуйтесь этим полезным вспомогательным классом, который возьмет на себя все сложности валидации и конвертации.
Строки
Оптимизация строк
При конкатенации строк на Java с помощью оператора +
, например, в цикле for
, каждый раз создается новый объект String
, что приводит к потере памяти и увеличению времени работы программы.
Также следует избегать создания Java строки с помощью конструктора класса:
// медленное инстанцирование String bad = new String("Yet another string object"); // быстрое инстанцирование String good = "Yet another string object"
Одинарные и двойные кавычки
Что вы ожидаете получить от этого кода?
public class Haha { public static void main(String args[]) { System.out.print("H" + "a"); System.out.print('H' + 'a'); } }
Казалось бы, должна вернуться строка HaHa
, но на деле будет Ha169
.
Двойные кавычки обрабатывают символы как строки, но одинарные ведут себя иначе. Они преобразуют символьные операнды ('H'
и 'a'
) к целочисленным значениям через расширение примитивных типов – получается 169
.
Математика
Float vs Double
Программисты часто не могут выбрать необходимую им точность для чисел с плавающей точкой. Float требует всего 4 байта, но и значащих цифр у него только 7, в то время как Double в два раза точнее (15 цифр), но и в два раза расточительнее.
На самом деле большинство процессоров способны работать с Float и Double одинаково эффективно, поэтому воспользуйтесь рекомендацией Бьёрна Страуструпа:
"Выбор нужной точности в реальных задачах требует хорошего понимания природы машинных вычислений. Если у вас его нет, либо проконсультируйтесь с кем-нибудь, либо изучите проблему сами, либо используйте double и надейтесь на лучшее."
Проверка нечетности
Можно ли использовать этот код для точного определения нечетности числа?
public boolean oddOrNot(int num) { return num % 2 == 1; }
Надеюсь, вы заметили подвох. Если мы решим проверить таким образом отрицательное нечетное число (-5, к примеру), остаток от деления не будет равен единице (а чему он равен?). Поэтому используйте более точный метод:
public boolean oddOrNot(int num) { return (num & 1) != 0; }
Он не только решает проблему отрицательных чисел, но и работает более продуктивно, чем его предшественник. Арифметические и логические операции выполняются гораздо быстрее умножения и деления.
Вычисление степени
Возвести число в степень можно двумя способами:
- простым умножением;
- с помощью функции
Math.pow(double base, double exponent)
.
Использовать библиотечную функцию рекомендуется только при крайней необходимости, например, в случае дробной или отрицательной степени.
double result = Math.pow(625, 0.5); // 25.0
Простое умножение на Java работает в 300-600 раз эффективнее, к тому же его можно дополнительно оптимизировать:
double square = double a * double a; double cube = double a * double a * double a; // не оптимизировано double cube = double a * double square; // оптимизировано double quad = double a * double a * double a * double a; // не оптимизировано double quad = double square * double square; // оптимизировано
JIT-оптимизация
Java-код обрабатывается с помощью JIT-компиляции: сначала транслируется в платформонезависимый байт-код, а уже после этого в машинный код. При этом оптимизируется все, что возможно, и разработчик может помочь компилятору создать максимально эффективную программу.
В качестве примера взглянем на две простые операции:
// 1 n += 2 * i * i; // 2 n += 2 * (i * i);
Измерим время выполнения каждой из них:
// 1 long startTime1 = System.nanoTime(); int n1 = 0; for (int i = 0; i < 1000000000; i++) { n1 += 2 * i * i; } double resultTime1 = (double)(System.nanoTime() - startTime1) / 1000000000; System.out.println(resultTime1 + " s"); // 2 long startTime2 = System.nanoTime(); int n2 = 0; for (int i = 0; i < 1000000000; i++) { n2 += 2 * (i * i); } double resultTime2 = (double)(System.nanoTime() - startTime2) / 1000000000; System.out.println(resultTime2 + " s");
Запустив этот код несколько раз, получим что-то подобное:
2*(i*i) | 2*i*i ----------+---------- 0.5183738 | 0.6246434 0.5298337 | 0.6049722 0.5308647 | 0.6603363 0.5133458 | 0.6243328 0.5003011 | 0.6541802 0.5366181 | 0.6312638 0.515149 | 0.6241105 0.5237389 | 0.627815 0.5249942 | 0.6114252 0.5641624 | 0.6781033 0.538412 | 0.6393969 0.5466744 | 0.6608845 0.531159 | 0.6201077 0.5048032 | 0.6511559 0.5232789 | 0.6544526
Закономерность очевидна: группировка переменных с помощью скобок ускоряет работу программы. Это происходит из-за генерации более эффективного байт-кода при умножении одинаковых значений.
Подробнее об этом эксперименте вы можете почитать здесь. А провести собственное испытание можно с помощью онлайн-компилятора Java.
Структуры данных
Объединение хеш-таблиц
Объединять два хеша, итерируя их значения вручную, весьма неэффективно. Вот альтернативное решение этой задачи, которое точно вам понравится:
import java.util.*; Map m1 = …; Map m2 = …; m2.putAll(m1); // добавляет в m2 все элементы из m1
Array vs ArrayList
Выбор между Array и ArrayList зависит от специфики задачи на Java, которую требуется решить. Помните о следующих особенностях этих типов:
- Array имеет фиксированный размер и память для него выделяется в момент объявления, а размер ArrayLists может динамически изменяться.
- Массивы Java работают гораздо быстрее, а в ArrayList намного проще добавлять/удалять элементы.
- При работе с Array велика вероятность получить ошибку
ArrayIndexOutOfBoundsException
. - У ArrayList только одно измерение, а вот массивы Java могут быть многомерными.
import java.util.ArrayList; public class arrayVsArrayList { public static void main(String[] args) { // объявление Array int[] myArray = new int[6]; // обращение к несуществующему индексу myArray[7]= 10; // ArrayIndexOutOfBoundsException // объявление ArrayList ArrayList<Integer> myArrayList = new ArrayList<>(); // простое добавление и удаление элементов myArrayList.add(1); myArrayList.add(2); myArrayList.add(3); myArrayList.add(4); myArrayList.add(5); myArrayList.remove(0); // получение элементов ArrayList for(int i = 0; i < myArrayList.size(); i++) { System.out.println("Element: " + myArrayList.get(i)); } // многомерный Array int[][][] multiArray = new int [3][3][3]; } }
JSON
Сериализация и десериализация
JSON – невероятно удобный и полезный синтаксис для хранения и обмена данными. Java полностью поддерживает его.
Сериализовать данные можно так:
import org.json.simple.JSONObject; import org.json.simple.JSONArray; public class JsonEncodeDemo { public static void main(String[] args) { JSONObject obj = new JSONObject(); obj.put("Novel Name", "Godaan"); obj.put("Author", "Munshi Premchand"); JSONArray novelDetails = new JSONArray(); novelDetails.add("Language: Hindi"); novelDetails.add("Year of Publication: 1936"); novelDetails.add("Publisher: Lokmanya Press"); obj.put("Novel Details", novelDetails); System.out.print(obj); } }
Получится вот такая JSON-строка:
{"Novel Name":"Godaan","Novel Details":["Language: Hindi","Year of Publication: 1936","Publisher: Lokmanya Press"],"Author":"Munshi Premchand"}
Десериализация на Java выглядит примерно так:
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Iterator; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; public class JsonParseTest { private static final String filePath = "//home//user//Documents//jsonDemoFile.json"; public static void main(String[] args) { try { // читаем json-файл FileReader reader = new FileReader(filePath); JSONParser jsonParser = new JSONParser(); JSONObject jsonObject = (JSONObject)jsonParser.parse(reader); // получаем данные из json объекта Long id = (Long) jsonObject.get("id"); System.out.println("The id is: " + id); String type = (String) jsonObject.get("type"); System.out.println("The type is: " + type); String name = (String) jsonObject.get("name"); System.out.println("The name is: " + name); Double ppu = (Double) jsonObject.get("ppu"); System.out.println("The PPU is: " + ppu); // получаем массив System.out.println("Batters:"); JSONArray batterArray= (JSONArray) jsonObject.get("batters"); Iterator i = batterArray.iterator(); // перебираем все элементы массива отдельно while (i.hasNext()) { JSONObject innerObj = (JSONObject) i.next(); System.out.println("ID "+ innerObj.get("id") + " type " + innerObj.get("type")); } System.out.println("Topping:"); JSONArray toppingArray= (JSONArray) jsonObject.get("topping"); Iterator j = toppingArray.iterator(); while (j.hasNext()) { JSONObject innerObj = (JSONObject) j.next(); System.out.println("ID "+ innerObj.get("id") + " type " + innerObj.get("type")); } } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } catch (ParseException ex) { ex.printStackTrace(); } catch (NullPointerException ex) { ex.printStackTrace(); } } }
JSON-файл, использованный в примере (jsonDemoFile.json):
{ "id": 0001, "type": "donut", "name": "Cake", "ppu": 0.55, "batters": [ { "id": 1001, "type": "Regular" }, { "id": 1002, "type": "Chocolate" }, { "id": 1003, "type": "Blueberry" }, { "id": 1004, "type": "Devil's Food" } ], "topping": [ { "id": 5001, "type": "None" }, { "id": 5002, "type": "Glazed" }, { "id": 5005, "type": "Sugar" }, { "id": 5007, "type": "Powdered Sugar" }, { "id": 5006, "type": "Chocolate with Sprinkles" }, { "id": 5003, "type": "Chocolate" }, { "id": 5004, "type": "Maple" } ] }
Ввод-вывод
FileOutputStream vs. FileWriter
Запись файлов на Java осуществляется двумя способами: FileOutputStream
и FileWriter
. Какой именно метод выбрать, зависит от конкретной задачи.
FileOutputStream
предназначен для записи потоков необработанных байтов. Это делает его идеальным решением для работы, например, с изображениями.
File foutput = new File(file_location_string); FileOutputStream fos = new FileOutputStream(foutput); BufferedWriter output = new BufferedWriter(new OutputStreamWriter(fos)); output.write("Buffered Content");
У FileWriter
другое призвание: работа с потоками символов. Так что если вы пишете текстовые файлы, выбирайте этот метод.
FileWriter fstream = new FileWriter(file_location_string); BufferedWriter output = new BufferedWriter(fstream); output.write("Buffered Content");
Производительность и лучшие практики
Пустая коллекция вместо Null
Если ваша программа может возвращать коллекцию, которая не содержит ни одного значения, убедитесь, что возвращена именно пустая коллекция, а не Null
. Это сэкономит вам время на различные проверки.
public class getLocationName { return (null==cityName ? "": cityName); }
Создание объектов только при необходимости
Создание объектов – одна из самых затратных операций в Java. Лучшая практика – создавать их только при необходимости, когда они действительно нужны.
import java.util.ArrayList; import java.util.List; public class Employees { private List Employees; public List getEmployees() { // инициализация только при необходимости if(null == Employees) { Employees = new ArrayList(); } return Employees; } }
Взаимоблокировки (deadlocks)
Взаимные блокировки потоков могут возникать по многим причинам, и полностью защититься от них в Java 8 очень сложно. Чаще всего это происходит, когда один синхронизированный объект ожидает ресурсов, которые заблокированы другим синхронизированным объектом.
Вот пример такой взаимоблокировки потоков:
public class DeadlockDemo { public static Object addLock = new Object(); public static Object subLock = new Object(); public static void main(String args[]) { MyAdditionThread add = new MyAdditionThread(); MySubtractionThread sub = new MySubtractionThread(); add.start(); sub.start(); } private static class MyAdditionThread extends Thread { public void run() { synchronized (addLock) { int a = 10, b = 3; int c = a + b; System.out.println("Addition Thread: " + c); System.out.println("Holding First Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Addition Thread: Waiting for AddLock..."); synchronized (subLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } } private static class MySubtractionThread extends Thread { public void run() { synchronized (subLock) { int a = 10, b = 3; int c = a - b; System.out.println("Subtraction Thread: " + c); System.out.println("Holding Second Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Subtraction Thread: Waiting for SubLock..."); synchronized (addLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } } }
Вывод этой программы:
===== Addition Thread: 13 Subtraction Thread: 7 Holding First Lock... Holding Second Lock... Addition Thread: Waiting for AddLock... Subtraction Thread: Waiting for SubLock...
Взаимоблокировки можно избежать, если изменить порядок вызова потоков:
private static class MySubtractionThread extends Thread { public void run() { synchronized (addLock) { int a = 10, b = 3; int c = a - b; System.out.println("Subtraction Thread: " + c); System.out.println("Holding Second Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Subtraction Thread: Waiting for SubLock..."); synchronized (subLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } }
Вывод:
===== Addition Thread: 13 Holding First Lock... Addition Thread: Waiting for AddLock... Threads: Holding Add and Sub Locks... Subtraction Thread: 7 Holding Second Lock... Subtraction Thread: Waiting for SubLock... Threads: Holding Add and Sub Locks...
Резервирование памяти
Некоторые Java-приложения очень требовательны к ресурсам и могут работать медленно. Для повышения производительности можно выделять Java-машине больше оперативной памяти.
export JAVA_OPTS="$JAVA_OPTS -Xms5000m -Xmx6000m -XX:PermSize=1024m -XX:MaxPermSize=2048m"
- Xms – минимальный пул выделения памяти;
- Xmx – максимальный пул выделения памяти;
- XX:PermSize – начальный размер, который будет выделен при запуске JVM;
- XX:MaxPermSize – максимальный размер, который может быть выделен при запуске JVM.
Решение распространенных задач
Содержимое директории
Java позволяет получить имена всех подкаталогов и файлов в папке в виде массива, который затем можно последовательно просмотреть.
import java.io.*; public class ListContents { public static void main(String[] args) { File file = new File("//home//user//Documents/"); String[] files = file.list(); System.out.println("Listing contents of " + file.getPath()); for(int i=0 ; i < files.length ; i++) { System.out.println(files[i]); } } }
Выполнение консольных команд
Java позволяет выполнять консольные команды прямо из кода с помощью класса Runtime
. При этом очень важно не забывать об обработке исключений.
Для примера попробуем открыть PDF-файл через терминал на Java:
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; public class ShellCommandExec { public static void main(String[] args) { String gnomeOpenCommand = "gnome-open //home//user//Documents//MyDoc.pdf"; try { Runtime rt = Runtime.getRuntime(); Process processObj = rt.exec(gnomeOpenCommand); InputStream stdin = processObj.getErrorStream(); InputStreamReader isr = new InputStreamReader(stdin); BufferedReader br = new BufferedReader(isr); String myoutput = ""; while ((myoutput=br.readLine()) != null) { myoutput = myoutput+"\n"; } System.out.println(myoutput); } catch (Exception e) { e.printStackTrace(); } } }
Воспроизведение звука
Звук – важная составляющая часть многих приложений, например, игр. Язык программирования Java предоставляет средства для работы с ним.
import java.io.*; import java.net.URL; import javax.sound.sampled.*; import javax.swing.*; public class playSoundDemo extends JFrame { // конструктор public playSoundDemo() { this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setTitle("Play Sound Demo"); this.setSize(300, 200); this.setVisible(true); try { URL url = this.getClass().getResource("MyAudio.wav"); AudioInputStream audioIn = AudioSystem.getAudioInputStream(url); Clip clip = AudioSystem.getClip(); clip.open(audioIn); clip.start(); } catch (UnsupportedAudioFileException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (LineUnavailableException e) { e.printStackTrace(); } } public static void main(String[] args) { new playSoundDemo(); } }
Отправка email
Отправка электронной почты на Java очень проста. Нужно лишь установить Java Mail и указать путь к нему в classpath проекта.
import java.util.*; import javax.mail.*; import javax.mail.internet.*; public class SendEmail { public static void main(String [] args) { String to = "recipient@gmail.com"; String from = "sender@gmail.com"; String host = "localhost"; Properties properties = System.getProperties(); properties.setProperty("mail.smtp.host", host); Session session = Session.getDefaultInstance(properties); try{ MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.addRecipient(Message.RecipientType.TO,new InternetAddress(to)); message.setSubject("My Email Subject"); message.setText("My Message Body"); Transport.send(message); System.out.println("Sent successfully!"); } catch (MessagingException ex) { ex.printStackTrace(); } } }
Захват координат курсора
Чтобы захватить события мыши, необходимо реализовать интерфейс MouseMotionListener
. Когда курсор попадает в определенную область, срабатывает обработчик события mouseMoved
, из которого можно получить точные координаты.
import java.awt.event.*; import javax.swing.*; public class MouseCaptureDemo extends JFrame implements MouseMotionListener { public JLabel mouseHoverStatus; public static void main(String args[]) { new MouseCaptureDemo(); } MouseCaptureDemo() { setSize(500, 500); setTitle("Frame displaying Coordinates of Mouse Motion"); mouseHoverStatus = new JLabel("No Mouse Hover Detected.", JLabel.CENTER); add(mouseHoverStatus); addMouseMotionListener(this); setVisible(true); } public void mouseMoved(MouseEvent e) { mouseHoverStatus.setText("Mouse Cursor Coordinates => X:"+e.getX()+" | Y:"+e.getY()); } public void mouseDragged(MouseEvent e) {} }
И немного о регулярных выражениях в Java.
Комментарии