В этой статье собрана небольшая коллекция практик, трюков и подсказок, с помощью которых вы сэкономите своё время при изучении 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
// ...
}
В 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;
}
}
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 новый объект может создаваться для каждой новой строки, что приводит к потере памяти и увеличению времени работы программы.
Необходимо избегать создания 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 или Double?
Программисты часто не могут выбрать необходимую точность для чисел с плавающей запятой. Float требует всего 4 байта, но имеет только 7 значащих цифр, а Double в два раза точнее (15 цифр), но в два раза прожорливее.
Фактически, большинство процессоров могут одинаково эффективно работать как с Float, так и с Double, поэтому воспользуйтесь рекомендацией Бьорна Страуструпа (автор языка С++):
Выбор правильной точности для решения реальных задач требует хорошего понимания природы машинных вычислений. Если у вас его нет, либо посоветуйтесь с кем-нибудь, либо изучите проблему самостоятельно, либо используйте Double и надейтесь на лучшее.
Проверка на нечетность
Можно ли использовать этот код для точного определения нечетного числа?
public boolean oddOrNot(int num) {
return num % 2 == 1;
}
Надеюсь, вы заметили хитрость. Если мы решим таким образом проверить отрицательное нечетное число (например, -5), остаток от деления не будет равен единице, поэтому воспользуйтесь более точным методом:
Он не только решает проблему отрицательных чисел, но и работает более производительно, чем предыдущий метод. Арифметические и логические операции выполняются намного быстрее, чем умножение и деление.
Возведение в степень
Возвести число в степень можно двумя способами:
простое умножение;
используя метод 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");
Запустив этот код несколько раз, мы получим примерно следующее:
Схема очевидна: группировка переменных в круглые скобки ускоряет работу программы. Это связано с генерацией более эффективного байт-кода при умножении одинаковых значений.
Вы можете узнать больше об этом эксперименте здесь. Или можете провести свой собственный тест, используя онлайн-компилятор Java.
Прим. перев. В 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 больше оперативной памяти.
Чтобы фиксировать события мыши, вам необходимо реализовать интерфейс 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) {
}
}