Создание универсальных классов с разными аргументами конструктора

У меня есть два похожих класса, 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>. Таким образом, вам не нужно отражение, и вызывающие могут передавать конструкторы, фабричные методы, лямбда-выражения, которые используют конструкторы с несколькими параметрами, все, что они хотят.

Jon Skeet 07.04.2022 15:54

Это хороший подход, о котором я не думал, спасибо!

andrestascon 07.04.2022 15:55

Хорошо, я напишу это как ответ.

Jon Skeet 07.04.2022 15:57
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мне кажется, лучше было бы взять 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 ). Если вы объявите, что он принимает коллекцию, а не список, вы можете применить его к другим типам коллекций.

Andy Thomas 07.04.2022 16:19

Другие вопросы по теме