Pascal4eg

 Java | Фишки и трюки

Множественная реализация интерфейсов с одинаковыми методами по умолчанию

interface Magician {
    default String getInfo() {
        return "Magician";
    }
}

interface QuidditchPlayer {
    default String getInfo() {
        return "QuidditchPlayer";
    }
}

static class HarryPotter implements Magician, QuidditchPlayer {
    @Override
    public String getInfo() {
        return Magician.super.getInfo() + ", " + QuidditchPlayer.super.getInfo();
    }
}

public static void main(String[] args) {
    HarryPotter harryPotter = new HarryPotter();
    System.out.println(harryPotter.getInfo());
    // Magician, QuidditchPlayer
}

Поскольку Java позволяет классам реализовывать несколько интерфейсов, важно знать, что происходит, когда класс реализует несколько интерфейсов, определяющих одни и те же методы по умолчанию.

Читать дальше

Reflection API

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // Получение объекта Class для класса Person
        Class personClass = Class.forName("com.example.Person");

        // Получение информации о полях класса
        Field[] fields = personClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("Field: " + field.getName() + ", Type: " + field.getType());
        }
        
        // Получение информации о методах класса и вызов метода
        Method method = personClass.getDeclaredMethod("getName");
        Object person = personClass.newInstance();
        Object result = method.invoke(person);

        System.out.println("Result of getName(): " + result);
    }
}

Reflection API в Java предоставляет средства для анализа и манипуляции классами, методами, полями и другими элементами программы во время выполнения. Это позволяет программам получать информацию о структуре классов и объектов, вызывать методы, создавать экземпляры классов и даже изменять их состояние во время выполнения. Reflection API является мощным инструментом, но также может быть опасным и требует осторожного использования.

Читать дальше

Default Methods in Interfaces

interface Vehicle {
    double getSpeed();
    default double getSpeedInMiles() {
        return getSpeed() * 0.621371;
    }
}

static class Car implements Vehicle {
    @Override
    public double getSpeed() {
        return 100;
    }
}

public static void main(String[] args) {
    Car car = new Car();
    System.out.println("Speed in km/h " + car.getSpeed());
    // Speed in km/h 100.0
    System.out.println("Speed in mph " + car.getSpeedInMiles());
    // Speed in mph 62.137100000000004
}

В отличие от обычных методов интерфейса, мы объявляем их с ключевым словом default в начале сигнатуры метода, и они предоставляют реализацию.

Причина, по которой в версию Java 8 включены методы по умолчанию, довольно очевидна.
В типичном проекте, основанном на абстракциях, где интерфейс имеет одну или несколько реализаций, если к интерфейсу добавляется один или несколько методов, все реализации также будут вынуждены их реализовать. В противном случае конструкция просто сломается.

Читать дальше

Внедрение зависимостей (Dependency Injection)

@Service
public class Toyota implements Car {

    @Autowired
    private Engine engine;

    @Override
    public String getInfo() {
        return "Model: Toyota, Engine: " + engine.getInfo();
    }
}

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

Читать дальше

HashMap

// Создание и заполнение HashMap
Map hashMap = new HashMap<>();
hashMap.put("one", 1);
hashMap.put("two", 2);
hashMap.put("three", 3);

// Получение значения по ключу
int value = hashMap.get("two");
System.out.println("Value for key 'two': " + value);

HashMap - это реализация интерфейса Map, которая представляет собой структуру данных, которая позволяет хранить пары ключ-значение и обеспечивает эффективный доступ к значениям по ключу. HashMap основан на хеш-таблице, что позволяет достигать высокой производительности при операциях вставки, удаления и поиска элементов. Вот как это работает:

Читать дальше

Netty

Netty - это мощный и высокопроизводительный фреймворк для разработки сетевых приложений на языке Java. Он предоставляет асинхронное и событийно-ориентированное программирование для создания масштабируемых сетевых приложений, таких как серверы, прокси, клиенты и другие.

Читать дальше

try-with-resources

try (Connection cnn = dataSource. getConnection();
     PreparedStatement stmt = cnn.prepareStatement("SELECT * FROM operation");
     ResultSet rs = stmt.executeQuery()) {
    processOperations(rs);
} catch (Exception e) {
    LOGGER.error("Database error", e);
}

Благодаря конструкции "try-with-resources" вы можете элегантно и безопасно управлять ресурсами, такими как потоки, сокеты или другие объекты, требующие явного закрытия после использования. Это улучшение ввода-вывода позволяет автоматически закрывать открытые ресурсы после завершения блока try.

Читать дальше

Switch-выражения

String season = switch (month) {
    case JANUARY, FEBRUARY -> "winter";
    case MARCH, APRIL, MAY -> "spring";
    case JUNE, JULY, AUGUST -> "summer";
    case SEPTEMBER, OCTOBER, NOVEMBER -> {
        System.out.println("winter is coming!");
        yield "autumn";
    }
    case DECEMBER -> "winter";
}

Чтобы получить значение из switch-выражения, раньше приходилось создавать отдельную переменную и постоянно использовать break;.

Читать дальше

Text Blocks (Текстовые блоки)

public static final String TEST_STUDENT_XML = """
    <student>
        <name>Harry Potter</name>
        <faculty>Gryffindor</faculty>
    </student>
    """;

В Java 15 появилась возможность задать константу типа String состоящую из нескольких строк без использования конкатенации.

Мы можем использовать текстовые блоки, объявив строку с """ (три двойные кавычки).

Это, безусловно, самый удобный способ объявления многострочной строки.

Читать дальше

Guava: Google Core Libraries for Java

RangeSet numberRangeSet = TreeRangeSet.create();

numberRangeSet.add(Range.closed(0, 2));
numberRangeSet.add(Range.closed(3, 5));
numberRangeSet.add(Range.closed(6, 8));

assertTrue(numberRangeSet.contains(1));
assertFalse(numberRangeSet.contains(9));

Guava — это набор общих библиотек Java от Google, который включает в себя новые типы коллекций (такие как multimap и multiset), неизменяемые коллекции, библиотеку графов и утилиты для параллелизма, ввода-вывода, хеширования, примитивов, строк и многого другого! Он широко используется в большинстве проектов Java в Google, а также во многих других компаниях.

Читать дальше

Stream API: filter() перед map()

var list = Arrays.asList(1, 2, 3, 4, 5);
var filteredList = list.stream()
        .filter(i -> i % 2 == 0)
        .map(i -> i * 2)
        .collect(Collectors.toList());

В Stream API используйте filter() перед map(), чтобы избежать ненужной обработки.

Если у вас есть поток, который может содержать большое количество элементов, не соответствующих вашим критериям, используйте filter() перед map(), чтобы избежать ненужной обработки. Это может улучшить производительность вашего кода.

Читать дальше

Jetty

int port = 80;
Server server = new Server(port);
ServletContextHandler context = new ServletContextHandler(server, "/");
context.addServlet(MyServlet.class, "/*");
server.start();

Jetty - это встраиваемый веб-сервер и контейнер сервлетов, разрабатываемый Eclipse Foundation. Jetty предоставляет компактное и легковесное решение для обработки HTTP-запросов и запуска веб-приложений в среде Java. Он широко используется в индустрии веб-разработки и часто встречается в различных проектах и приложениях, где требуется встроенный сервер.

Читать дальше

Java NIO

Java NIO (New I/O) — это набор функциональных возможностей для работы с вводом и выводом данных (I/O), представленный в Java 1.4. Этот набор функций предоставляет более эффективный и мощный способ обработки операций ввода-вывода по сравнению с традиционным I/O API, известным как Java IO (или Java I/O).

Вот основные компоненты Java NIO:

Буферы (Buffers): Основная идея в Java NIO - работа с данными через буферы. Буферы - это области памяти, которые используются для временного хранения данных перед и после их обработки. Java NIO предоставляет разные типы буферов, такие как ByteBuffer, CharBuffer, IntBuffer и другие, каждый из которых предназначен для работы с определенными типами данных.

Каналы (Channels): Каналы представляют собой абстракцию для работы с источниками данных и целевыми местами данных. Каналы могут быть использованы для чтения из файлов, записи в файлы, работы с сетевыми соединениями и т. д. Основные классы для каналов включают FileChannel, SocketChannel, ServerSocketChannel и DatagramChannel.

Селекторы (Selectors): Селекторы предоставляют механизм для мультиплексирования операций ввода-вывода на нескольких каналах. Селекторы позволяют одному потоку обрабатывать несколько каналов одновременно, что делает Java NIO особенно полезным для серверных приложений, обслуживающих множество клиентов.

Читать дальше

JTwig

import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;

public class TwigExample {
    public static void main(String[] args) {
        JtwigTemplate template = JtwigTemplate.inline("Hello, {{ name }}!");
        JtwigModel model = JtwigModel.newModel().with("name", "JTwig" ) ;

        String output = template.render(model);
        System.out.println(output); // Hello, JTwig!
    }
}

JTwig - это библиотека шаблонизации для языка программирования Java. Она предоставляет удобный способ создания и обработки шаблонов для генерации текстового или HTML-кода. JTwig особенно полезен, когда вам нужно создавать динамические страницы в веб-приложениях, генерировать отчеты, email-рассылки или форматировать текстовый вывод.

Читать дальше

SerialVersionUID

private static final long serialVersionUID = -6849794470754667710L;

SerialVersionUID - это private static final поле типа long класса который реализует интерфейс Serializable.

Во время сериализации, если мы не объявляем SerialVersionUID в нашем классе, среда выполнения Java создает номер версии для класса, так что она может десереализировать его позже. Если во время десериализации, SerialVersionUID не соответствует, то процесс завершится с исключением InvalidClassException.

Читать дальше

Константы в Java

public static final double PI = 3.1415;

В Java нет специального ключевого слова для определения константы как в других языках.

Константа в языке Java реализована комбинацией двух ключевых слов: static и final.

static означает, что значение принадлежит классу. А final разрешает присвоить переменной значение только один раз.

Читать дальше

ThreadLocal

ThreadLocal - это класс в Java, который позволяет создавать локальные переменные, привязанные к каждому потоку. Это полезно, когда вам нужно иметь уникальную копию переменной для каждого потока, чтобы избежать конфликтов и обеспечить безопасность в многопоточной среде.

Преимущества ThreadLocal:

  1. Безопасность потоков: Каждый поток имеет свою собственную копию переменной, что устраняет соревнование и конфликты доступа.
  2. Улучшение производительности: Использование ThreadLocal может уменьшить необходимость синхронизации при доступе к переменным в многопоточной среде.
  3. Удобство: ThreadLocal делает работу с потоками более удобной и чистой, позволяя каждому потоку иметь свои собственные данные.
Читать дальше

Тернарный оператор

public int max(int a, int b) {
    return a > b ? a : b;
}

Тернарный оператор (также известный как условный оператор) - это сокращенная форма записи условного выражения.

Он имеет следующий синтаксис:
условие ? значение_если_истина : значение_если_ложь;

Читать дальше

Множественное присваивание

int a, b, c;
a = b = c = 5;
System.out.println(a); // 5
System.out.println(b); // 5
System.out.println(c); // 5

Операция присваивания работает следующим образом: сначала вычисляется значение выражения в правой части, потом это значение присваивается переменной, которая размещается в левой части от знака ‘=‘.

Операция присваивания может иметь вид цепочки:
переменная1 = переменная2 = выражение;

Читать дальше

Библиотека Lombok

import lombok.Data;

@Data
public class Person {
    private String name;
    private int age; 
}

Разрабатывая на Java, мы сталкиваемся с множеством рутинных операций, связанных с написанием геттеров, сеттеров, equals, hashCode и других стандартных методов. Есть способ существенно упростить этот процесс. В этом нам поможет библиотека Lombok!

Lombok – это основанная на аннотациях библиотека Java, позволяющая сократить шаблонный код. В Lombok предлагаются различные аннотации, цель которых – заменить ненужный повторяющийся код, писать который утомительно. Например, Lombok избавит вас от написания безаргументных конструкторов, методов toString(), equals() и hashCode(), для этого нужно просто добавить несколько аннотаций. Плюс, эта библиотека подключается прямо к вашей IDE, поэтому работа воспринимается так, словно вы написали весь шаблонный код сами.

Читать дальше

🍝 Спагетти-код: когда разработка становится беспорядком

Спагетти-код – это одна из самых неприятных "болезней" в мире программирования. Этот термин используется для описания плохо организованного и труднопонимаемого кода, который напоминает спагетти – пасту, перепутанную в неразгадываемую массу.

Почему спагетти-код возникает?

  1. Недостаток планирования: Недостаточное предварительное проектирование приложения может привести к хаотичной разработке, когда каждая часть кода создается независимо от других.
  2. Отсутствие структуры: Если разработчики не следят за структурой проекта, код может быстро превратиться в беспорядок.
  3. Отсутствие документации: Неадекватно задокументированный код может сделать его понимание и редактирование настоящим испытанием.
  4. Недостаток опыта: Новички в программировании могут создавать спагетти-код из-за отсутствия опыта и знаний о лучших практиках.
Читать дальше

Рекурсия

public class Factorial {
    public static void main(String[] args) {
        int num = 6;
        long factorial = factorial(num);
        System.out.println("Factorial of " + num + " = " + factorial);
        // Factorial of 6 = 720
    }

    /**
     * Вычисляет факториал переданного числа
     * @param num значение для которого нужно вычислить факториал
     * @return результат
     */
    public static long factorial(int num) {
        if (num == 1) {
            return 1;
        }
        return num * factorial(num - 1);
    }
}

🔁 Рекурсия — определение, описание, изображение какого-либо объекта или процесса внутри самого этого объекта или процесса, то есть ситуация, когда объект является частью самого себя.

В программировании рекурсия — вызов функции (процедуры) из неё же самой, непосредственно (простая рекурсия) или через другие функции (сложная или косвенная рекурсия).

Читать дальше

Liquibase

Liquibase - это мощный инструмент для управления изменениями в схеме базы данных. Он позволяет разработчикам и администраторам баз данных эффективно управлять эволюцией структуры базы данных в течение всего жизненного цикла приложения.

Вот несколько ключевых особенностей Liquibase:

  1. Контроль версий: Liquibase предоставляет механизм для контроля версий схемы базы данных. Вы можете определить изменения в формате XML, SQL, YAML и других, и Liquibase отслеживает, какие изменения уже были применены к базе данных, и какие ещё предстоит применить.
  2. Кросс-платформенность: Liquibase поддерживает множество СУБД, включая PostgreSQL, MySQL, Oracle, Microsoft SQL Server и многие другие. Это позволяет разработчикам использовать один и тот же инструмент для управления изменениями в разных системах.

...

Читать дальше

Интернирование строк (string intern)

String s1 = "Hello, World!";
String s2 = new String("Hello, World!").intern();
String s3 = "Hello, World!";
String s4 = new String("Hello, World!");

System.out.println(s1 == s2);
// true, так как s2 ссылается на интернированный экземпляр строки

System.out.println(s1 == s3);
// true, так как s3 ссылается на интернированный экземпляр строки

System.out.println(s1 == s4);
// false, так как s4 ссылается на новую строку, созданную в памяти

Интернирование строк (string intern) - это процесс хранения одной и той же строки только в одном экземпляре в пуле строк (string pool) для оптимизации использования памяти и ускорения сравнения строк.

Вместо того чтобы создавать новый объект строки каждый раз, когда вы используете литерал строки (например, "Hello, World!"), Java проверяет, существует ли уже такая строка в пуле строк. Если она там есть, Java использует существующий экземпляр строки, иначе создает новый и добавляет его в пул строк.

Читать дальше

Порядок инициализации объекта

Инициализация это процесс задания начальных значений переменным и объектам перед их использованием. Этот процесс необходим для того, чтобы обеспечить корректное начальное состояние переменных и объектов в программе.

Читать дальше

volatile

volatile - это ключевое слово, которое может быть применено к переменным. Ключевое слово volatile играет важную роль в управлении видимостью и синхронизацией данных между потоками. Когда переменная объявлена как volatile, это означает, что операции чтения и записи этой переменной будут синхронизированы между потоками.

Читать дальше

Optional

// Создаем Optional с пустым значением
Optional emptyOptional = Optional.empty();

// Создаем Optional с непустым значением
Optional nonEmptyOptional = Optional.of("String value");

// Можем там же создать Optional с возможностью отсутствия значения
Optional optionalWithNullable = Optional.ofNullable(null);

// Проверяем, есть ли значение в Optional, и выполняем действие при его наличии
if (nonEmptyOptional.isPresent()) {
    System.out.println(nonEmptyOptional.get()); // String value
}

// Используем метод orElse для предоставления альтернативного значение,
// если значение отсутствует
String result = emptyOptional.orElse("default");
System.out.println(result);  // default

Optional представляет собой класс, введенный в Java 8, который предназначен для борьбы с проблемами, связанными с отсутствием значения (null) и обеспечивает более безопасную и чистую обработку возможных отсутствующих значений.

Читать дальше

Nashorn

public static void main(String[] args) throws ScriptException, NoSuchMethodException {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    Invocable invocable = (Invocable) engine;
    engine.eval("var StringBuilder = Java.type('java.lang.StringBuilder')");
    engine.eval("var sb = new StringBuilder();");
    engine.eval("function add(str) { sb.append(str); }");
    engine.eval("function toString() { return sb.toString(); }");

    invocable.invokeFunction("add", "Hello");
    invocable.invokeFunction("add", " scripting!");
    System.out.println(invocable.invokeFunction("toString"));
    // Hello scripting!
}

Nashorn — это движок JavaScript, разработанный на языке программирования Java первоначально Oracle, а затем сообществом OpenJDK. Nashorn был включен в JDK в версии Java 8 и до Java 14. С Java 15 его нужно подключать явно.

Читать дальше

Системы сборки в мире Java

🛠 Системы сборки в мире Java — это незаменимые инструменты, которые делают процесс разработки проще и эффективнее.

Что бы ваш код стал работающим приложением, его нужно собрать в файл который можно запустить. Это может быть .war или .jar файл.

Читать дальше

Weak Reference: Слабая связь с объектами

В мире Java, WeakReference - это мощный инструмент для управления памятью. Он позволяет создавать ссылки на объекты, которые могут быть автоматически удалены сборщиком мусора, если на них больше нет сильных ссылок (обычная ссылка на объект).

Чем это полезно? Это помогает избежать утечек памяти в приложениях, где объекты могут оставаться неиспользуемыми, но все еще иметь сильные ссылки.

Читать дальше

🚀🧵 Гармония в параллельном мире Java: Thread-Safe

В мире Java, параллельное выполнение потоков - это норма. Однако без должных мер предосторожности, оно может привести к состоянию гонки (race condition) и ошибкам. Вот почему важно понимать и применять концепцию Thread-Safe.

Thread-Safe означает, что ваш код или структуры данных могут безопасно использоваться из множества потоков, не вызывая нежелательных конфликтов. Как добиться Thread-Safety:

Читать дальше

Унарный оператор

boolean b = false;
System.out.println(b); // false
System.out.println(!b); // true

int i = -5;
System.out.println(i); // -5
System.out.println(-i); // 5
System.out.println(+i); // -5

int j = 5;
int k = 5;
System.out.println(++j + k++); // 11
System.out.println(j); // 6
System.out.println(k); // 6

Унарный оператор - это оператор, который принимает на вход один аргумент и возвращает некоторое значение.

К унарным операторам относятся: +, -, !
А так же, пре-унарный оператор и пост-унарный оператор.

Читать дальше

Varargs (Variable Arguments List)

public static void main(String[] args) {
    System.out.println(concat("one", 1, BigDecimal.ONE));
    // one,1,1
}

private static String concat(Object... args) {
    return Arrays.stream (args)
            .map (Object::toString)
            .collect(Collectors.joining(","));
}

Varargs (Variable Arguments List, изменяющийся список аргументов) — это способ создания методов, которые могут принимать произвольное количество аргументов одного типа (от нуля и более). Данная возможность появилась в JDK 5.

Запись вида Object... args и есть varargs.

Читать дальше

Бесконечные потоки

Stream infiniteStream = Stream.iterate(0, i -> i + 2);
List intList = infiniteStream
                        .limit(10)
                        .collect(Collectors.toList());
// 0, 2, 4, 6, 8, 10, 12, 14, 16, 18

Supplier randomUUIDSupplier = UUID::randomUUID;
Stream infiniteUUIDStream = Stream.generate(randomUUIDSupplier);
List uuidList = infiniteUUIDStream
                        .skip(10) // пропустить первые 10
                        .limit(10)
                        .collect(Collectors.toList());

Интерфейс Stream имеет два статических метода для генерации бесконечных потоков: iterate() и generate().

Читать дальше

Stream

Все операции Stream делятся на промежуточные и терминальные и объединяются в потоковые конвейеры.

Потоковый конвейер состоит из источника (например, коллекции, массива, функции-генератора, канала ввода-вывода или генератора бесконечной последовательности) за которым следует ноль или более промежуточных операций и терминальной операции.

Промежуточные операции
📌 Промежуточные операции не выполняются до тех пор, пока не будет вызвана какая-либо терминальная операция.

Они составляют конвейер выполнения Stream. Промежуточную операцию можно добавить в конвейер Stream методами:
filter()
map()
flatMap()
distinct()
sorted()
peek()
limit()
skip()

Читать дальше

Защита от повторного запроса с помощью БД

Если вы разрабатываете Веб-приложение или REST-сервис, то рано или поздно столкнётесь с повторными запросами. Что имеется в виду? Объясню на примере Веб-страницы с кнопкой. По нажатию на кнопку, на бэкенд отправляется запрос. Запрос, соответственно, синхронный и пока серверная часть делает какую-то работу, браузер клиента показывает, что загружает страницу. Если это происходит продолжительное время, клиент может подумать, что его запрос завис и нажать кнопку ещё раз. Также повторное нажатие может произойти случайно.

Какая тут может произойти проблема? Если это, например, какой-то запрос данных, то в общем-то проблемы и нет, но если это действие, которое должно отработать только один раз, то тут могут быть весьма неприятные последствия. Для примера в интернет-магазине собрана корзина и создан заказ на оплату (статус REGISTERED), далее по нажатию кнопки "оплатить" с клиента списывают деньги и переводят заказ в статус оплачен (PAID). И если в этом процессе произойдёт двойной запрос, то с клиента могут списать деньги за заказ два раза.

Читать статью

Читать дальше

Комбинирование предикатов

List fruits = List.of("apple", "orange", "pineapple", "grapefruit", "melon");
Predicate isEqualsOrange = Predicate.isEqual("orange");
Predicate isEqualsGrapefruit = Predicate.isEqual("grapefruit");
Predicate isEqualsCitrus = isEqualsOrange.or(isEqualsGrapefruit);
Predicate isNotEqualsCitrus = isEqualsCitrus.negate();

fruits.stream().filter(isEqualsOrange).forEach(System.out::println);
// orange

fruits.stream().filter(isEqualsCitrus).forEach(System.out::println);
// orange grapefruit

fruits.stream().filter(isNotEqualsCitrus).forEach(System.out::println);
// apple pineapple melon

🧩 Predicate - встроенный функциональный интерфейс, добавленный в Java 8 в пакет java.util.function.

Читать дальше

Запись (record)

public record Point (int x, int y) { }

public static void main(String[] args) {
    Point point = new Point(100, 100);
    System.out.println(point);
}
// Point[x=100, y=100]

📝 Запись (record) — это класс, объявленный с ключевым словом record вместо ключевого слова class. Запись служит контейнером неизменяемых данных и предназначена для лаконичного описания DTO (Data Transfer Object).

Читать дальше

Sentry for Java

Sentry for Java — это программный инструмент, призванный помочь разработчикам отслеживать и управлять ошибками, исключениями и сбоями в приложениях Java.

Читать дальше

Double braces инициализация

// Double braces initialization
List planets = new ArrayList<>() {{
    add("Mercury");
    add("Venus"); 
    add("Earth"); 
    add("Mars");
    add("Jupiter");
    add("Saturn"); 
    add("Uranus"); 
    add("Neptune");
}};

Double braces инициализация - это создание и инициализация объекта в одном java выражении. Чаще всего используется с коллекциями.

На самом деле в этот момент происходит создание анонимного внутреннего класса, расширяющего ArrayList и определение в нём блока инициализации экземпляра.

Читать дальше

Stream API

Stream API появился в Java 8 и существенно упростил работу с коллекциями. Теперь можно писать короче и красивее фильтрацию, сортировку и обработку данных.

private final List properties = getProperties();

/**
 * Поиск свойства по имени в списке
 * @param propName наименование свойства
 * @return найденное свойство или null если не найдено
 */
public Property getPropertyByName(String propName) {
    return properties.stream().filter(p -> p.getName().equals(propName)).findFirst().orElse(null);
}
Читать дальше

Пожалуй самый распространенный способ избежания Null Pointer Exception

Часто можно получить NPE при проверке на равенство переменной и константы. Дело в том, что переменная может содержать null и при вызове метода equals у такой переменной, мы получим исключение NullPointerException.

Читать дальше