List, Set, Map і дженерики в тестових даних
Основні колекції (пакет java.util):
- List — впорядкований список із дублікатами; доступ за індексом.
ArrayList — типовий вибір. Результат findElements, рядки таблиці, елементи відповіді API — зазвичай List.
- Set — унікальні елементи.
HashSet без порядку, LinkedHashSet зберігає порядок вставки, TreeSet — відсортований. Корисно, щоб перевірити відсутність дублікатів: new HashSet<>(list).size() == list.size().
- Map — пари ключ-значення.
HashMap без порядку, LinkedHashMap із порядком вставки, TreeMap за ключем. Зручно для тестових даних за роллю чи оточенням: Map<Role, User>.
Незмінні колекції: List.of(...), Set.of(...), Map.of(...) не дозволяють змін і null; add кидає UnsupportedOperationException. Arrays.asList(...) має фіксований розмір: елемент замінити можна, додати — ні. Якщо потрібна змінювана копія, new ArrayList<>(List.of(...)).
Дженерики (List<String>) переносять перевірку типів у компіляцію: не треба кастів, помилка типу видна одразу. Не використовуйте «сирі» типи (List без параметра). Під час виконання параметр типу стирається (type erasure), тому new T() і instanceof List<String> неможливі; це важливо, коли Jackson десеріалізує List<User> і потрібен TypeReference.
Під час обходу не змінюйте колекцію: видалення елемента в циклі for-each кидає ConcurrentModificationException. Використовуйте removeIf чи ітератор.
hashCode/equals, читання JSON і .properties, Jackson та POJO
Контракт equals/hashCode. Якщо a.equals(b), то a.hashCode() == b.hashCode(). HashMap і HashSet спершу шукають кошик за хешем, а потім порівнюють через equals. Перевизначили лише equals — рівні об’єкти матимуть різні хеші, і set.contains(...) не знайде елемент. Завжди перевизначайте обидва методи разом (або використовуйте record, Lombok @EqualsAndHashCode). Не кладіть у ключі мапи об’єкти, які змінюють поля, що входять у хеш.
Файли тестових даних. Лежать у src/test/resources і потрапляють у classpath. Читайте їх через classpath, а не через відносний шлях, що залежить від робочої теки:
try (InputStream in = getClass().getResourceAsStream("/users.json")) {
List<User> users = mapper.readValue(in, new TypeReference<List<User>>() {});
}
.properties: Properties props = new Properties(); props.load(in);, значення — props.getProperty("baseUrl", "http://localhost"). Секрети не зберігайте у файлі в репозиторії: беріть зі змінних оточення.
Jackson (ObjectMapper) перетворює JSON у POJO і назад. POJO — звичайний клас (чи record) із полями й акцесорами, без залежності від фреймворку.
- Потрібен конструктор без аргументів із сетерами, або record, або
@JsonCreator.
- У Jackson 2.x невідоме поле в JSON за замовчуванням дає
UnrecognizedPropertyException (FAIL_ON_UNKNOWN_PROPERTIES). Для відповідей API, що можуть розширюватись, додайте @JsonIgnoreProperties(ignoreUnknown = true) або вимкніть цю опцію в мапері. У Jackson 3 ця опція за замовчуванням вимкнена.
@JsonProperty("user_id") зв’язує поле з іншою назвою; для LocalDate/Instant у Jackson 2.x потрібен модуль jackson-datatype-jsr310 (у Jackson 3 підтримка java.time вбудована), а @JsonFormat задає формат.
ObjectMapper потокобезпечний після конфігурації: створіть один і використовуйте повторно, бо створення дороге.