У меня есть два похожих класса, Foo и Bar
public class Foo{
private String name;
public Foo(String name){
this.name = name;
}
}
public class Bar{
private String name;
public Bar(String name){
this.name = name;
}
}
И у меня есть два метода в другом классе, которые создают набор Foo (1-й метод) и Bar (2-й метод), и они почти одинаковы. Первый:
private Set<Foo> buildFoos(final List<String> names)
{
final Set<Foo> set = new HashSet<>();
for (int i = 0; i < names.size(); i++)
{
set.add(new Foo(names.get(i)));
}
return set;
}
И второй:
private Set<Bar> buildBars(final List<String> names)
{
final Set<Bar> set = new HashSet<>();
for (int i = 0; i < names.size(); i++)
{
set.add(new Bar(names.get(i)));
}
return set;
}
Как видите, оба метода практически одинаковы, поэтому я подумал, что могу использовать дженерики, чтобы использовать только один метод. Мой подход будет примерно таким:
private <T> Set<T> build(final Class<T> clazz, final List<String> names)
{
final Set<T> set = new HashSet<>();
for (int i = 0; i < names.size(); i++)
{
set.add(clazz.getConstructor(String.class).newInstance(names.get(i)));
}
return set;
}
Я протестировал это, и оно должно работать, но мой вопрос: что произойдет, если у Foo или Bar будет другой конструктор (так, например, у Bar будет другая строка в качестве второго параметра). Все, о чем я могу думать, это проверить экземпляр класса и выбрать один из двух конструкторов (что-то вроде этого)
private <T> Set<T> build(final Class<T> clazz, final List<String> names)
{
final Set<T> set = new HashSet<>();
for (int i = 0; i < names.size(); i++)
{
if (clazz.isInstance(Foo.class){
set.add(clazz.getConstructor(String.class).newInstance(names.get(i)));
}
else if (clazz.isInstance(Bar.class){
set.add(clazz.getConstructor(String.class, String.class).newInstance(names.get(i), "example"));
}
}
return set;
}
Есть ли лучший способ добиться этого? Это даже хорошая практика? Любой совет всегда приветствуется. Заранее спасибо!
Это хороший подход, о котором я не думал, спасибо!
Хорошо, я напишу это как ответ.
Мне кажется, лучше было бы взять Function<String, T>
вместо Class<T>
. Таким образом, вам не нужно отражение, и вызывающие могут передавать конструкторы, фабричные методы, лямбда-выражения, которые используют конструкторы с несколькими параметрами, все, что они хотят. Таким образом, ваш метод будет:
private <T> Set<T> build(final Function<String, T> factory, final List<String> names) {
final Set<T> set = new HashSet<>();
for (String name : names) {
set.add(factory.apply(name));
}
return set;
}
Я подозреваю, что это можно было бы написать проще, используя потоковую передачу, но это отдельная проблема. (Хотя для простоты я воспользовался возможностью использовать расширенный цикл for.)
Да проще со стримами: return names.stream().map( factory ).collect( Collectors.toSet())
. Обратите внимание, что вы можете вызвать этот метод с лямбдой: build( name -> new Bar(name), names )
. Если вы объявите, что он принимает коллекцию, а не список, вы можете применить его к другим типам коллекций.
Мне кажется, лучше было бы взять
Function<String, T>
вместоClass<T>
. Таким образом, вам не нужно отражение, и вызывающие могут передавать конструкторы, фабричные методы, лямбда-выражения, которые используют конструкторы с несколькими параметрами, все, что они хотят.