Дмитрий Зайцев
эксперт по разработке ПО компании «Рексофт»
В этой статье собрана небольшая коллекция практик, трюков и подсказок, с помощью которых вы сэкономите своё время при изучении Java и написании кода на этом языке программирования.Организация работы
Чистый код
В крупных проектах на первый план выходит не создание нового кода, а поддержка существующего, поэтому очень важно с самого начала его правильно организовать. При разработке нового приложения всегда помните о трех основных принципах чистого и поддерживаемого кода:- Правило 10-50-500. В одном пакете не может быть более 10 классов. Каждый метод должен быть короче 50 строк кода, а каждый класс — короче 500 строк.
- SOLID принципы.
- Использование паттернов проектирования.
Работа с ошибками
Stack Trace (Трассировка стека)
Выявление ошибок — это, пожалуй, самая трудоемкая часть процесса разработки на Java. Трассировка стека позволяет вам точно отслеживать, где именно в проекте возникла ошибка или исключение (exception).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();
NullPointerException
Исключения, возникающие из-за null значений (NullPointerException), довольно часто появляются при попытке вызвать метод у несущестующего объекта.Возьмем для примера следующий код:
int noOfStudents = school.listStudents().count;
private int getListOfStudents(File[] files) {
if (files == null)
throw new NullPointerException("File list cannot be null");
}
Прим. перев. А вот пример от меня как переводчика материала:
String anyString = null;
if (anyString.equals("some string")) { // здесь будет null pointer exception, т.к. anyString == null
// ...
}
Дата и Время
System.currentTimeMillis или 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() для измерения относительных интервалов времени.
Валидация Даты из строки
Если необходимо достать объект Date из обычной строки в Java, можете использовать небольшой утилитный класс, который приведен ниже. Он позаботится обо всех сложностях валидации и преобразовании строки в объект Date.package net.viralpatel.java;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class DateUtil {
// List of all date formats that we want to parse.
// Add your own format here.
private static List;
dateFormats = new ArrayList() {{
add(new SimpleDateFormat("M/dd/yyyy"));
add(new SimpleDateFormat("dd.M.yyyy"));
add(new SimpleDateFormat("M/dd/yyyy hh:mm:ss a"));
add(new SimpleDateFormat("dd.M.yyyy hh:mm:ss a"));
add(new SimpleDateFormat("dd.MMM.yyyy"));
add(new SimpleDateFormat("dd-MMM-yyyy"));
}
};
/**
* Convert String with various formats into java.util.Date
*
* @param input
* Date as a string
* @return java.util.Date object if input string is parsed
* successfully else returns null
*/
public static Date convertToDate(String input) {
Date date = null;
if(null == input) {
return null;
}
for (SimpleDateFormat format : dateFormats) {
try {
format.setLenient(false);
date = format.parse(input);
} catch (ParseException e) {
//Shhh.. try other formats
}
if (date != null) {
break;
}
}
return date;
}
}
Пример его использования:
package net.viralpatel.java;
public class TestDateUtil {
public static void main(String[] args) {
System.out.println("10/14/2012" + " = " + DateUtil.convertToDate("10/14/2012"));
System.out.println("10-Jan-2012" + " = " + DateUtil.convertToDate("10-Jan-2012"));
System.out.println("01.03.2002" + " = " + DateUtil.convertToDate("01.03.2002"));
System.out.println("12/03/2010" + " = " + DateUtil.convertToDate("12/03/2010"));
System.out.println("19.Feb.2011" + " = " + DateUtil.convertToDate("19.Feb.2011" ));
System.out.println("4/20/2012" + " = " + DateUtil.convertToDate("4/20/2012"));
System.out.println("some string" + " = " + DateUtil.convertToDate("some string"));
System.out.println("123456" + " = " + DateUtil.convertToDate("123456"));
System.out.println("null" + " = " + DateUtil.convertToDate(null));
}
}
Результат:
10/14/2012 = Sun Oct 14 00:00:00 CEST 2012
10-Jan-2012 = Tue Jan 10 00:00:00 CET 2012
01.03.2002 = Fri Mar 01 00:00:00 CET 2002
12/03/2010 = Fri Dec 03 00:00:00 CET 2010
19.Feb.2011 = Sat Feb 19 00:00:00 CET 2011
4/20/2012 = Fri Apr 20 00:00:00 CEST 2012
some string = null
123456 = null
null = null
Строки
Оптимизация строки
Для конкатенации (сложения) строк в Java используется оператор «+», для примера, в цикле for новый объект может создаваться для каждой новой строки, что приводит к потере памяти и увеличению времени работы программы.https://tproger.ru/quiz/test-your-java/
Необходимо избегать создания 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».
https://tproger.ru/jobs/android-developer/?utm_source=in_text
Двойные кавычки обрабатывают символы как строки, но одинарные кавычки ведут себя иначе. Они преобразуют символьные операнды ('H' и 'a') в целые значения посредством расширения примитивных типов — получается 169.
Математика
Float или 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 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.
https://tproger.ru/blogs/jvm-insides/
Структуры данных
Комбинирование хеш-таблиц
Комбинирование двух хеш-таблиц вручную через цикл очень неэффективно. Вот альтернативное решение этой проблемы, которое вам возможно понравится:import java.util.*;
Map m1 = …;
Map m2 = …;
m2.putAll(m1); // добавить к m2 все элементы из m1
Array или ArrayList?
Выбор между Array и ArrayList зависит от специфики задачи Java, которую вы хотите решить. Запомните следующие особенности этих типов:- Массив имеет фиксированный размер, и память для него выделяется во время объявления, а размер ArrayList может динамически меняться.
- Массивы Java работают намного быстрее, а в ArrayList намного проще добавлять и удалять элементы.
- При работе с Array скорее всего возникнет ошибка ArrayIndexOutOfBoundsException.
- ArrayList может быть только одномерным, когда массивы Java могут быть многомерными.
public class ArrayVsArrayList {
public static void main(String[] args) {
// создаем массив
int[] myArray = new int[6];
// ссылаемся на несуществующий индекс
myArray[7]= 10; // ArrayIndexOutOfBoundsException
// создаем ArrayList
List 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));
}
// многомерный массив
int[][][] multiArray = new int[3][3][3];
}
}
JSON
Сериализация и Десериализация
JSON — невероятно удобный и полезный синтаксис для хранения и обмена данными. Java полностью поддерживает это.Прим. перев. Для использования JSON из примера необходимо подключить библиотеку JSON Simple.
Вы можете сериализовать данные следующим образом:
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|IOException|ParseException|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" }
]
}
Прим. перев. В Java проектах очень часто для работы с JSON используют библиотеки Gson от Google или Jackson. Обе библиотеки очень популярны и хорошо поддерживаются. Попробуйте и их.
Ввод и Вывод
FileOutputStream или 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. Это сэкономит вам время на различные проверки и избавит от многих ошибок.import java.util.*
public List getLocations() {
List result = someService.getLocationsFromDB();
return result == null ? Collections.emptyList() : result;
}
Создание объектов только когда необходимо
Создание объектов — одна из самых затратных операций в Java. Лучше всего создавать их только тогда, когда они действительно нужны.import java.util.ArrayList;
import java.util.List;
public class Employees {
private List Employees;
public List getEmployees() {
// initialization only if necessary
if(null == Employees) {
Employees = new ArrayList();
}
return Employees;
}
}
Deadlocks (Дедлоки)
Взаимная блокировка (Deadlock) потоков может происходить по многим причинам, и полностью защититься от них в 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 -XXermSize=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);
}
}
}
Выполнение консольных команд
Java позволяет выполнять консольные команды прямо из кода, используя класс Runtime. Очень важно не забывать об обработке исключений.Например, давайте попробуем открыть файл PDF через терминал Java (на Linux’e):
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 {
// constructor
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|IOException|LineUnavailableException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new PlaySoundDemo();
}
}
Отправка email
Отправить электронную почту на Java очень просто. Вам просто нужно установить Java Mail и указать путь к нему в пути к классам проекта.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, из которого вы можете получить точные координаты (используя Swing для UI)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) {
}
}
Источник статьи: https://tproger.ru/translations/java-tips-and-tricks-for-begginer/