Ещё одно полезное применение lambda-функций в Java 8 в живом шаблоне в IDEA

· автор BaRoN · На чтение уйдёт 3 минуты · (559 слов)

Одно время, чтобы не отставать, так сказать, от прогресса, я ходил на StackOverflow code review, и переписывал код с использованием Stream API (и соответственно лямбда-функций). Иногда получалось хорошо и коротко и красиво, иногда не очень. Но это надо было сделать, - примерно как решать однотипные математические задачки в школе. И вот, вроде бы привык к “хорошему”, местами код даже начинал напоминать какой-то JavaScript. Но одно дело, делать что-то с нуля или решать задачки, а другое - традиционный такой проект, махровая Java.

Я с frontend-программистом как раз немножко общаюсь, прошу его быть богобоязненным и напоминаю о том, что я знаю, где он живёт. А всё дело в том, что он мне хочет присылать любое количество полей профиля, а мне надо с этим как-то жить. Итак, покажу, как это примерно выглядит:

@PostMapping("/profile/{profileId}")
public ResponseEntity<Profile> put(@PathVariable("profileId") Long profileId, @RequestBody ProfileDTO body) throws ApiErrorException {
	final Profile profile = profileRepository.findOne(profileId);
	if (body.getAddress() != null) {
		profile.setAddress(body.getAddress());
	}
	if (body.getAvatar() != null) {
		profile.setAvatar(body.getAvatar());
	}
	return ResponseEntity.ok(profileRepository.save(profile));
}

А всего там полей около 30, в четырёх разных классах. Сначала я чуть было не начал писать свой mapstruct, но осознав свою никчёмность, плюнул и начал копипастить. Копипастить оказалось не очень-то хорошо, неминуемо однажды я ошибусь. Тогда я задумался, как бы свести шанс ошибок к минимуму. Первым шагом на пути стало использование лямбда-функций и новых API Java. Меньше кода - меньше ошибок, тут надо сделать лишь две замены вместо трёх:

@PostMapping("/profile/{profileId}")
public ResponseEntity<Profile> put(@PathVariable("profileId") Long profileId, @RequestBody ProfileDTO body) throws ApiErrorException {
	final Profile profile = profileRepository.findOne(profileId);
	Optional.ofNullable(body.getAddress()).ifPresent(profile::setAddress);
	Optional.ofNullable(body.getAvatar()).ifPresent(profile::setAvatar);
	return ResponseEntity.ok(profileRepository.save(profile));
}

Следующим этапом стал Live Template. Дело в том, что я всегда и принципиально пользуюсь JetBrains IDEA, и там есть возможность задать собственные шаблоны для генерации код. В данном случае я сделал шаблон sto (хотел переименовать body в source, а profile в target для универсальности, а шаблон начать с st). Оказалось, что от переименования body можно отказаться, а вот profile таки пришлось переименовать в target. И после этого родилась первая версия живого шаблона:

Optional.ofNullable($SELECTION$).ifPresent(target::set);

Например, я писал body.getBio() и нажимал Ctrl+Alt+T для вызова живого шаблона. В результате я получал

Optional.ofNullable(body.getBio()).ifPresent(target::set);

И курсор был на слое Optional. Таким образом, перейдя к слову set, можно было вызвать автодополнение и выбрать правильный метод. Честно говоря, ненамного лучше Ctrl+C/Ctrl+V, не правда ли? Тогда я полез разбираться в переменные, и нашёл там возможность выполнить динамический скрипт на Groovy. Шаблон стал таким:

Optional.ofNullable($SELECTION$).ifPresent($MNAME$);

А переменной $MNAME$ я присвоил значение

groovyScript('_1.trim().replaceAll("(\\w+)\\.get(\\w+)\\(\\)", "target::set\$2")', SELECTION)

И что? Правильно, ничего. Почему? А чёрт его знает, спрошу у разработчиков. Тогда родилась третья, стрёмненькая версия, но она существенно лучше первой. Если мне не дают подставить свой текст, подставлю часть текста и вызову автокомплит. Сказано - сделано, шаблон стал таким:

Optional.ofNullable($SELECTION$).ifPresent(target::set$MNAME$);

А значение переменной $MNAME$ стало таким:

completeSmart()

Теперь я пишу body.getBio(), нажимаю Ctrl+Alt+T, вызываю живой шаблон sto и получаю на выходе:

Optional.ofNullable(body.getBio()).ifPresent(target::set);

При этом курсор стоит после слова set, и вызвано автодополнение. К сожалению, автодополнение совсем не умное, потому что предлагает вызвать любой сеттер, вне зависимости от типа. Если кто не знал, в IDEA есть более умное автодополнение: например, я пишу строчку target.setBio(body. и нажимаю Ctrl+Shift+Пробел. Код сам превратится в target.setBio(body.getBio());, потому что больше ни один из методов класса ProfileDTO не возвращает объект класса Bio. Это, кстати, можно попробовать сгрузить в виде Feature Request.

Резюмирую: используя возможности IDEA, удалось написать живой шаблон, который весьма компактен за счёт использования Java 8, который безопаснее чем копипаста, и экономит время.

Полезное