Сравнение Map.of() и New HashMap() в Java

Источник: DZone Содержание этой статьи посвящено использованию Map.of() и new HashMap<>() в Java, разнице между ними и преимуществам применения Map.of(). Кофе-брейк #211. Сравнение Map.of() и New HashMap() в Java. Что такое обработка исключений в Java и когда ее выполнять - 1Язык Java имеет множество структур данных, очень полезных при разработке программ. Одной из таких структур является интерфейс Map, который используется для хранения данных в парах ключ-значение.

Что такое Map.of()?

Map.of() — это метод, представленный в Java 9, который позволяет разработчикам создавать неизменяемый Map, содержащий до десяти пар ключ-значение. Он обеспечивает удобный и лаконичный способ создания Map без необходимости написания большого количества кода. Map.of() является улучшением по сравнению с предыдущим способом создания небольших Map с использованием конструктора класса HashMap, который может быть довольно громоздким и многословным.

Что такое New HashMap<>()?

New HashMap<>() — это конструктор, предоставляемый классом HashMap в Java, который позволяет разработчикам создавать новый экземпляр класса HashMap. Он используется для создания изменяемого Map. Это означает, что Map можно изменить, добавив, удалив или обновив пары ключ-значение. Это широко используемый метод создания Map в Java, особенно при работе с большими наборами данных.

Сравнение Map.of() и New HashMap<>()

Чтобы сравнить производительность Map.of() и new HashMap<>() в Java, мы можем использовать инструменты бенчмаркинга для измерения времени, затрачиваемого на выполнение различных операций с Map, созданными с использованием этих методов. В нашем тесте мы будем измерять время, необходимое для получения значения из Map, и время, необходимое для вставки значений в Map. Стоит отметить, что наши тесты ограничены небольшим набором данных: десятью элементами. Возможно, результаты могут отличаться для больших наборов данных или более сложных вариантов использования.

package ca.bazlur;

import org.openjdk.jmh.annotations.Benchmark;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 20, time = 1)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation
public class MapBenchmark {
    private static final int SIZE = 10;

    private Map<Integer, String> mapOf;
    private Map<Integer, String> hashMap;

    @Setup
    public void setup() {
        mapOf = Map.of(
                0, "value0",
                1, "value1",
                2, "value2",
                3, "value3",
                4, "value4",
                5, "value5",
                6, "value6",
                7, "value7",
                8, "value8",
                9, "value9"
        );

        hashMap = new HashMap<>();

        hashMap.put(0, "value0");
        hashMap.put(1, "value1");
        hashMap.put(2, "value2");
        hashMap.put(3, "value3");
        hashMap.put(4, "value4");
        hashMap.put(5, "value5");
        hashMap.put(6, "value6");
        hashMap.put(7, "value7");
        hashMap.put(8, "value8");
        hashMap.put(9, "value9");
    }

    @Benchmark
    public void testMapOf(Blackhole blackhole) {
        Map<Integer, String> map = Map.of(
                0, "value0",
                1, "value1",
                2, "value2",
                3, "value3",
                4, "value4",
                5, "value5",
                6, "value6",
                7, "value7",
                8, "value8",
                9, "value9"
        );
        blackhole.consume(map);
    }


    @Benchmark
    public void testHashMap(Blackhole blackhole) {
        Map<Integer, String> hashMap = new HashMap<>();
        hashMap.put(0, "value0");
        hashMap.put(1, "value1");
        hashMap.put(2, "value2");
        hashMap.put(3, "value3");
        hashMap.put(4, "value4");
        hashMap.put(5, "value5");
        hashMap.put(6, "value6");
        hashMap.put(7, "value7");
        hashMap.put(8, "value8");
        hashMap.put(9, "value9");
        blackhole.consume(hashMap);
    }

    @Benchmark
    public void testGetMapOf() {
        for (int i = 0; i < 10; i++) {
            mapOf.get(i);
        }
    }

    @Benchmark
    public void testGetHashMap() {
        for (int i = 0; i < SIZE; i++) {
            hashMap.get(i);
        }
    }
}
Benchmark Mode Cnt Score Error Units MapBenchmark.testGetHashMap avgt 20 14.999 ± 0.433 ns/op MapBenchmark.testGetMapOf avgt 20 16.327 ± 0.119 ns/op MapBenchmark.testHashMap avgt 20 84.920 ± 1.737 ns/op MapBenchmark.testMapOf avgt 20 83.290 ± 0.471 ns/op
Это результаты тестов для сравнения производительности при использовании new HashMap<>() и Map.of() в Java. Эталонный тест проводился с ограниченным и небольшим набором данных (10). Результаты показывают, что в HashMap немного более быстрые операции get по сравнению с неизменяемыми Map, созданными с использованием Map.of(). Однако создание неизменяемого файла Map с использованием Map.of() по-прежнему выполняется быстрее, чем создание файла HashMap. Обратите внимание, что в зависимости от вашего дистрибутива JDK и компьютера результаты тестов могут немного отличаться. Однако в большинстве случаев они должны совпадать. Всегда полезно провести собственные тесты, чтобы убедиться, что вы сделали правильный выбор для своего конкретного случая использования. Кроме того, помните, что микротесты всегда следует воспринимать с долей скептицизма и не использовать в качестве единственного фактора при принятии решения. Следует учитывать и другие факторы, такие как использование памяти, безопасность потоков и читабельность кода. Исходный код теста можно найти на GitHub. На мой взгляд, небольшие различия в производительности в большинстве случаев не имеют большого значения. При выборе между HashMap и Map.of() важно учитывать другие аспекты, такие как конкретный вариант использования, насколько он лаконичен, хорошо ли организован код и наличие необходимых функций (например, изменяемый или неизменяемый характер). Для простых сценариев Map.of() все еще более предпочтителен.

Преимущества использования Map.of()

Вот несколько преимуществ использования Map.of() по сравнению с new HashMap<>() в Java:
  1. Краткость: Map.of() предоставляет лаконичный и удобный способ создания небольших Map на Java. Это делает код более читабельным и простым в обслуживании.
  2. Неизменяемость: Map.of() создает неизменяемые Map, что означает, что после создания Map его нельзя изменить. Это обеспечивает определенную степень безопасности для хранящихся данных.
  3. Безопасность типов: Map.of() обеспечивает безопасность типов для ключей и значений Map, что помогает предотвратить ошибки, связанные с типами, которые могут возникнуть при использовании new HashMap<>().

Заключение

Map.of() — это мощный и полезный метод, представленный в Java 9, который обеспечивает более краткий способ создания небольших Map в Java с дополнительными преимуществами, такими как неизменность и безопасность типов. Наш бенчмаркинг показывает, что задержки для небольших Map между Map.of() и new HashMap<>() близки, учитывая степень погрешности, что затрудняет окончательный вывод о том, что один метод значительно быстрее другого, основываясь только на этих данных. Преимущества использования Map.of() включают его краткость, неизменность и безопасность типов. Хотя разница в производительности может быть незначительной, судя по предоставленным результатам тестов, другие преимущества делают Map.of() более привлекательным, особенно при создании небольших Map на Java.

Что такое обработка исключений в Java и когда ее выполнять

Источник: Medium Это руководство поможет вам лучше понять, в каких ситуациях выполняется обработка исключений и для чего она необходима. Обработка исключений — одна из наиболее важных функций программирования на Java. Она позволяет обнаруживать и обрабатывать ошибки, которые могут возникнуть во время выполнения программы. Язык Java предоставляет широкий набор механизмов обработки исключений, гарантирующих, что программы могут изящно обрабатывать непредвиденные ситуации и восстанавливаться после ошибок без каких-либо сбоев. В Java исключение — это объект, отображающий ненормальное состояние, возникшее во время выполнения программы. Исключения могут быть проверяемые (checked) или непроверяемые (unchecked). Проверяемые исключения — это те, которые компилятор требует от вас явно обрабатывать, а непроверяемые исключения — это те, которые не требуют явной обработки. Для обработки исключений в Java вам нужно использовать блок try-catch. Блок try содержит код, который может вызвать исключение, а блок catch содержит код, обрабатывающий исключение. Вот пример:

try { 
// код, который может вызвать исключение
 } 
catch (ExceptionType1 e1) 
{ // код для обработки ExceptionType1 } 
catch (ExceptionType2 e2) 
{ // код для обработки ExceptionType2 }} 
finally {
 // опциональный (необязательный) блок кода, который будет выполняться после блоки try/catch }
В этом примере блок try содержит код, который может вызвать исключение. Если возникнет исключение, блоки catch поймают его и выполнят соответствующий код. Последний блок является опциональным (необязательным) и используется для указания блока кода, который будет выполняться после блоков try/catch, независимо от того, было ли выброшено исключение или нет. Java также предоставляет оператор throw, который позволяет вам вручную генерировать исключение из вашего кода. Он полезен, когда вы хотите указать, что в вашем коде произошла ошибка, и вы хотите обработать ее с помощью блока try-catch.

throw new ExceptionType("Error message");
В данном примере мы выбрасываем новый экземпляр класса ExceptionType с сообщением об ошибке. Это заставит программу перейти к блоку catch для ExceptionType, где мы сможем обработать ошибку.

Исключения в Java

В языке Java исключение — это событие, возникающее во время выполнения программы и нарушающее ее нормальную работу. Когда возникает исключение, JVM (виртуальная машина Java) создает объект, представляющий исключение, и выдает (выбрасывает) его. Затем программа ищет обработчик исключения, который может обработать сгенерированное исключение. Java предоставляет два типа исключений: проверяемые и непроверяемые исключения.
  1. Проверяемые исключения — это исключения, которые компилятор проверяет и требует от программиста явной обработки. Примеры проверенных исключений включают IOException, SQLException и ClassNotFoundException.
  2. Непроверяемые исключения — это исключения, которые не проверяются компилятором и не требуют явной обработки. Эти исключения возникают во время выполнения и могут быть вызваны ошибками программирования, такими как пустые указатели, арифметические переполнения или выход за пределы индекса. Примеры непроверенных исключений включают NullPointerException, ArithmeticException и IndexOutOfBoundsException.
Основное различие между ними заключается в том, как их обрабатывают компилятор и программист. Вот пример обработки проверяемого исключения:

int a = 10; int b = 0; 
int c = a / b;
 // Выдает арифметическое исключение (ArithmeticException)
В этом коде мы пытаемся разделить 10 на 0, что приводит к ArithmeticException. Поскольку это непроверяемое исключение, нам не нужно его перехватывать или объявлять, что метод генерирует исключение.

Заключение

Исключения — важная концепция в программировании на Java, которая позволяет обрабатывать ошибки и нештатные ситуации, возникающие во время выполнения программы. Java предоставляет два типа исключений: проверяемые и непроверяемые исключения. Проверяемые исключения проверяются компилятором во время компиляции и требуют, чтобы программист либо обработал исключение, либо объявил, что метод создает исключение. Примеры проверенных исключений включают IOException, SQLException и ClassNotFoundException. Непроверяемые исключения не проверяются компилятором во время компиляции и возникают во время выполнения. Они не требуют явной обработки или объявления программистом. Примеры непроверенных исключений включают NullPointerException, ArithmeticException и IndexOutOfBoundsException. Для обработки исключений в Java используется блок try-catch-finally, а оператор throw используется для генерирования исключения вручную. Понимая, как обрабатывать исключения в Java, программисты могут писать более надежный и безошибочный код.