JAVA Параметризация метода двумя типами, которые могут быть разными типами и могут совпадать
Есть метод, который параметризован двумя типами:
protected <T, R> Page<T> savePageInSeparatedTransaction(
Function<Pageable, Page<T>> sourceSupplier,
Function<T, R> converter,
Consumer<List<R>> saver,
Pageable pageRequest
) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return transactionTemplate.execute(s -> {
Page<T> sourcePage = sourceSupplier.apply(pageRequest);
List<R> converted = converter == null
? (List<R>) sourcePage.getContent()
: sourcePage.stream().map(converter).collect(Collectors.toList());
saver.accept(converted);
return sourcePage;
})
}
В этом методе:
T - исходный тип, а
R - результирующий тип.
конвертер converter передан в метод.
Но если converter == null, то значит исходный тип совпадает с результирующим типом.
В таком случае я хочу коллекцию исходного типа sourcePage привести к типу коллекции результирующего типа.
Мне нужно, чтобы эти два дженерика могли быть разными типами и могли быть одним типом.
Но тогда возникает ошибка при компиляции в вызывающем контексте:
Вызов этого метода такой:
savePageInSeparatedTransaction(
skaRepository::findAll,
null,
bufferRepository::insertRecords,
PageRequest.of(0, 20_000, Sort.Direction.ASC, "id.lim")
);
Текст ошибки компиляции:
reason: No instance of Type variables R exist so that List<R> conforms to VmCertEntity
Как, не делая перегрузок, можно исправить метод таким образом, чтобы дженерики могли восприниматься разными типами и одним типом?
Ответы (2 шт):
У вас есть два дженерики <T, R> для java это значит что используются два разных типа данных и нужна явно показать что это один и тот же тип использовать вырожение в виде t -> t вместо null.
Во-первых, проблема с исходным кодом НЕ воспроизвелась, т.е. ошибка при компиляции НЕ возникла, см. упрощённый пример на online GDB.
В зависимости от выбранной реализации saver могло быть выброшено ClassCastException.
Во-вторых, можно было бы передавать в качестве конвертера функцию идентичности в виде стандартной реализации Function.identity() или лямбды t -> t, как предложил @AP-JavaCod в предыдущем ответе, но тогда проверка на null в представленной реализации savePageInSeparatedTransaction теряет смысл и можно было бы обойтись без неё:
private TransactionTemplate getTransactionTemplate() {
TransactionTemplate tt = new TransactionTemplate(transactionManager);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return tt;
}
protected <T, R> Page<T> savePageInSeparatedTransaction(
Function<Pageable, Page<T>> sourceSupplier,
Function<T, R> converter,
Consumer<List<R>> saver,
Pageable pageRequest
) {
Objects.requireNonNull(converter);
return getTransactionTemplate().execute(s -> {
Page<T> sourcePage = sourceSupplier.apply(pageRequest);
List<R> converted = sourcePage.stream()
.map(converter)
.collect(Collectors.toList());
saver.accept(converted);
return sourcePage;
});
}
Соответственно, при использовании такого варианта возникает нежелательная конвертация списка посредством функции идентичности, и более логичное решение для одинаковых типов T и R состоит имхо в использовании перегруженной функции с одним типом T без лишнего конвертера в списке аргументов:
protected <T> Page<T> savePageInSeparatedTransaction(
Function<Pageable, Page<T>> sourceSupplier,
Consumer<List<T>> saver,
Pageable pageRequest
) {
return getTransactionTemplate().execute(s -> {
Page<T> sourcePage = sourceSupplier.apply(pageRequest);
saver.accept(sourcePage.getContent());
return sourcePage;
});
}
И такие решения действительно позволят отлавливать некорректные консьюмеры на этапе компиляции:
savePageInSeparatedTransaction(
repoFoo::findAll, // returns Page<Foo>
t -> t, // Foo -> Foo
repoBar::saveAll, // expects Iterable<Bar>
PageRequest.of(0, 20_000, Sort.Direction.ASC, "id.lim")
);
/*
error: incompatible types: incompatible types: inference variable R has incompatible bounds
new Main().savePageInSeparatedTransaction(
^
equality constraints: Bar
lower bounds: Foo
where R,T are type-variables:
R extends Object declared in method <T,R>savePageInSeparatedTransaction(Function<Pageable,Page<T>>,Function<T,R>,Consumer<List<R>>,Pageable)
T extends Object declared in method <T,R>savePageInSeparatedTransaction(Function<Pageable,Page<T>>,Function<T,R>,Consumer<List<R>>,Pageable)
*/
savePageInSeparatedTransaction(
repoFoo::findAll, // returns Page<Foo>
repoBar::saveAll, // expects Iterable<Bar>
PageRequest.of(0, 20_000, Sort.Direction.ASC, "id.lim")
);
/*
error: incompatible types: inference variable T has incompatible equality constraints Bar,Foo
*/