11 ноября 2017 г.

Простой способ создания коллекций в Java 9

Актуальная версия статьи на моём сайте devmark.ru

Java 9 предоставляет новый способ создания read-only коллекций при помощи стандартных универсальных методов List.of(), Set.of() и Map.of().

Например, если мы хотим создать список строк, представляющих собой дни недели, то он будет выглядеть так:

        List<String> days = List.of(
                "понедельник",
                "вторник",
                "среда",
                "четверг",
                "пятница",
                "суббота",
                "воскресенье");

При этом при попытке модифицировать полученную коллекцию (например, добавить новый элемент) вы получите ошибку, т.к. в данном случае вы работаете с неизменямой коллекцией.

Теперь рассмотрим работы с мапой. Метод Map.of() принимает чётное количество элементов, где каждый нечётный - это ключ коллекции, а каждый чётный - значение. Для предыдущего примера вариант с мапой будет выглядеть так:

        Map<Integer, String> days = Map.of(
                1, "понедельник",
                2, "вторник",
                3, "среда",
                4, "четверг",
                5, "пятница",
                6, "суббота",
                7, "воскресенье");

Данная коллекция опять-таки является неизменяемой. Но есть и ещё один нюанс. Здесь вы можете объявить не более 10 элементов мапы. Если вы захотите объявить 11 и более элементов, компилятор выдаст ошибку.

Предположим, вы хотите создать коллекцию из названий месяцев вместе с их порядковыми номерами. Тут нам на помощь придёт метод Map.ofEntries(). И каждый элемент придётся обернуть в другой статический метод Map.entry().

        Map<Integer, String> months = Map.ofEntries(
                Map.entry(1, "январь"),
                Map.entry(2, "февраль"),
                Map.entry(3, "март"),
                Map.entry(4, "апрель"),
                Map.entry(5, "май"),
                Map.entry(6, "июнь"),
                Map.entry(7, "июль"),
                Map.entry(8, "август"),
                Map.entry(9, "сентябрь"),
                Map.entry(10, "октябрь"),
                Map.entry(11, "ноябрь"),
                Map.entry(12, "декабрь")
        );

В случае с методоми Set.of() и List.of() такой проблемы нет, так как там нет ключей.

Кстати, метод Map.entry() также является неплохой альтернативой для создания любой пары значений, ведь в Java нет стандартного класса Pair.

5 ноября 2017 г.

Передача null в перегруженный метод

Актуальная версия статьи доступна на моём новом сайте devmark.ru.

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

interface MyInterface {
    void doWork();
}

class MyBaseObject implements MyInterface {
    @Override
    public void doWork() {
        System.out.println("Base");
    }
}

class MyChildObject extends MyBaseObject {
    @Override
    public void doWork() {
        System.out.println("Child");
    }
}

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

void over(MyInterface face) {
    System.out.println("Interface");
}

void over(MyBaseObject base) {
    System.out.println("Base");
}

void over(MyChildObject child) {
    System.out.println("Child");
}

Вопрос: какой из этих методов будет использован, если мы захотим вызвать метод over() и передадим в него null?

Ответ: в таком случае на экране мы увидим слово "Child".

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

P.S. Пример несколько синтетический. На практике я бы избегал ситуации, когда в метод нужно передавать null. Вполне вероятно, что если внутри него нет специальной проверки, то будет NullPointerException.

Также я бы избегал перегрузки методов. Это может и выглядит красиво, но в Enterprise-приложениях, когда у вас over 9000 классов, это вносит дополнительную сложность в понимании.

Отсюда мораль для работодателей: не задавайте на собеседованиях глупых вопросов! А если кто-то любит писать такой код, то лучше его сразу уволить)

Генерация строки на основе шаблона

Актуальная версия статьи доступна на моём новом сайте devmark.ru.

Преположим, вам нужно генерить текст по определённому шаблону. У вас есть заранее заготовленный текст, куда вы передаёте параметры для подстановки. Например, текст sms для клиента.

Самое первое, что приходит на ум - это воспользоваться методом String.format(). Как известно, он чувствителен к порядку следования элементов. Но что, если сам шаблон лежит у вас где-нибудь в базе данных и может измениться в любое время, а порядок параметров "зашит" в коде самого приложения. Согласитесь, было бы удобнее, чтобы каждый параметр подставлялся по имени, а не по порядку.

И тут первое, что приходит на ум - это метод String.replace(). Но чтобы не писать очередной велосипед, лучше воспользоваться классом org.apache.commons.lang3.text.StrSubstitutor из стандартной библиотеки Apache Common.

Подключим её через maven pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.1</version>
</dependency>

А теперь напишем код, который будет подставлять в шаблон необходимые параметры:

import org.apache.commons.lang3.text.StrSubstitutor;

import java.util.HashMap;
import java.util.Map;

public class StringTemplateExample {

    private static final String SMS_TEMPLATE =
            "Уважаемый ${user.name}! Ваш заказ будет доставлен по адресу ${user.address}.";

    public static void main(String[] args) {
        Map<String, String> valuesMap = new HashMap<>();
        valuesMap.put("user.name", "Николай Петрович");
        valuesMap.put("user.ddress", "Москва, Ленинский проспект, дом 1");
        StrSubstitutor sub = new StrSubstitutor(valuesMap);
        String resolvedString = sub.replace(SMS_TEMPLATE);
        System.out.println(resolvedString);
    }
}


Здесь в шаблоне мы указываем места для подстановки параметров в формате ${имя_параметра}. Имя параметра может быть произвольным. Далее в целевом методе создаём мапу с информацией об имени пользователя и его адресе. И затем передаём эту мапу в класс StrSubstitutor, где через метод replace() получаем уже конечную строку, которую отправим пользователю.

Уважаемый Николай Петрович! Ваш заказ будет доставлен по адресу Москва, Ленинский проспект, дом 1.

Важно заметить, что если какой-то параметр не будет передан в мапе, то ошибки не возникнет и пользователь увидит часть нашего шаблона в "сыром" виде. В зависимости от логики вашего приложения, имеет смысл заранее проверять наличие всех необходимых параметров.

Генерация N одинаковых элементов через Stream API

Актуальная версия статьи доступна на моём новом сайте devmark.ru.

Предположим, вы хотите сгенерировать N одинаковых элементов. Это очень легко сделать в Java при помощи Stream API. Рассмотрим следующий метод:

private List<String> repeatedValueGenerator(String text, int count) {
    return Stream.generate(() -> text)
            .limit(count)
            .collect(Collectors.toList());
}

Сначала стрим при помощи метода generate() создаёт бесконечный стрим (Stream), а потом мы его ограничиваем при помощи метода limit().

Например, для того, чтобы вывести на экран список из трех одинаковых строк с текстом "test", нужно вызвать его следующим образом:

System.out.println(repeatedValueGenerator("test", 3));

В результате получим:

[test, test, test]

Генерация случайных чисел при помощи Stream API

Актуальная версия статьи доступна на моём новом сайте devmark.ru.

В Java 8 у класса java.util.Random появился набор удобных методов для генерации стримов (Stream) случайных чисел для всех основных числовых типов. Например:

private List<Integer> randomIntsGenerator() {
    return new Random()
            .ints(25, 1, 6)
            .boxed()
            .collect(Collectors.toList());
}

В данном примеры мы генерим 25 случайных чисел из диапазона от 1 до 5 (указываем 6, т.к. верхняя граница не входит). Затем для удобства производим упаковку примитивов в список (каждый элемент из int преобразуется в Integer). В результате получаем случайные целые числа:

[4, 4, 1, 4, 3, 1, 5, 1, 5, 4, 3, 4, 3, 2, 2, 1, 1, 5, 5, 1, 5, 4, 1, 1, 2]

Класс Random позволяет генерить не только целые числа типа int, но и числа типа long при помощи метода longs() и double через метод doubles().

P.S. Если вам крайне важно равномерное распределение случайных чисел, рекомендуется использовать класс java.security.SecureRandom.