У многих рефлексия ассоциируется с раздутым кодом или с не правильным продуманным api.
Под катом несколько полезных примеров, которые покажут положительные стороны рефлексии.


Тренироваться будем на утках сортировках.

public interface ISort {
    int[] sort(int[] ints);
}

public class MergeSort implements ISort {
    // Реализация сортировки
}

public class QuickSort implements ISort {
    // Реализация сортировки
}

Сразу представим себе ситуацию, что сортировок мы хотим изучить много, хотим, чтобы все они работали и были оттестированы.

Однако писать на все алгоритмы тесты нам лень. Поэтому мы протестируем только интерфейс ISort, а все остальные классы, как бы сами собой будут протестированы )

// Делаем тест в виде шаблона
public abstract class ISortTest<T extends ISort> {
    private Class<T> aClass;
    private T sortAlgorithm;

    public ISortTest() {
        // Получаем тип дженерика заданного в шаблоне
        aClass = Optional.of(getClass())
                .map(Class::getGenericSuperclass)
                .filter(el -> el instanceof ParameterizedType)
                .map( el -> (ParameterizedType) el)
                .filter(el -> el.getActualTypeArguments().length > 0)
                .map(el -> el.getActualTypeArguments()[0])
                .filter(el -> el instanceof Class)
                .map(el -> (Class<T>) el)
                .orElse(null);
    }

    @BeforeEach
    void init() throws IllegalAccessException, InstantiationException {
        // Получаем экземпляр конкретной сортировки
        sortAlgorithm = aClass.newInstance();
    }

    @Test
    void sortTest() {
        assertNotNull(sortAlgorithm);
        int n = 10000;
        int[] ints = new Random().ints().limit(n).toArray();

        int[] sortInts = Arrays.stream(ints)
                .sorted()
                .toArray();

        int[] testSotrInts = sortAlgorithm.sort(ints);
        assertArrayEquals(sortInts, testSotrInts);
    }
}

Ну вот и всё, теперь можно официально объявить о победе лени.

Тестирование сортировок теперь будет сведено к созданию таких вот классов

class MergeSortTest extends ISortTest<MergeSort> {
      // Тут ничего нет
}

class QuickSortTest extends ISortTest<QuickSort> {
      // Тут ничего нет
}

Подобный подход применяется, например, в Spring Data

public interface TestRepository extends CrudRepository<MyEntity, Long> {
}

А так же в других местах, о которых мы даже и не догадываемся.

Комментарии (18)


  1. zagayevskiy
    30.03.2018 19:13

    Даже если не говорить о полезности и правильности таких "тестов", почему бы просто не пропихнуть тестируемый инстанс через фабричный метод? Кода было бы даже меньше, и без рефлексии.


    1. PqDn Автор
      30.03.2018 23:02

      так я понимаю в этом случае придется что-то писать в наследных тестах? Правильно я вас понял?


      1. zagayevskiy
        30.03.2018 23:51

        А здесь не надо?


        1. PqDn Автор
          31.03.2018 00:33

          нет, если функционал не расширять


          1. zagayevskiy
            31.03.2018 00:33

            MergeSortTest extends ISortTest

            A это как же?


            1. PqDn Автор
              31.03.2018 00:36

              Я имел ввиду в теле класса ничего писать не надо


              1. zagayevskiy
                31.03.2018 00:37

                А где плюс этого-то? Что не надо писать, одну строку? Серьёзно? Унесите это с хабра и закопайте где-нибудь.


                Простейший пример — в конструктор каждой конкретной реализации нужно передать некие аргументы, всегда разные. Что делать будете?


                1. PqDn Автор
                  31.03.2018 00:41

                  Это очень простой пример. Я всего лишь хотел показать как для таких ситуаций использовать рефлексию…
                  Поверьте можно оч. крутые вещи делать таким подходом


                1. PqDn Автор
                  31.03.2018 00:46

                  Простейший пример — в конструктор каждой конкретной реализации нужно передать некие аргументы, всегда разные. Что делать будете?

                  Да вы знатный троль


                  1. zagayevskiy
                    31.03.2018 00:52
                    -1

                    Эээ… Я тролль?..


  1. javaevangelist
    30.03.2018 22:59

    А почему просто нельзя то вернуть дата провайдер с имплиментациями алгоритмов сортировок и в одном тесте их все прогнать? Еще уродливый и не эффективный стрим в конструкторе.


    1. PqDn Автор
      30.03.2018 23:00

      нет там стримов


    1. PqDn Автор
      31.03.2018 00:53

      Представте, что таких классов несколько сотен


      1. javaevangelist
        31.03.2018 14:20

        и что? В вашем случае 100 дочерних пустых классов будет, нахер бы они нужны были? Просто мусор


      1. aleksandy
        01.04.2018 14:33

        Всё решается гораздо проще и без всякого наследования.

        JUnit4
        @RunWith(Parameterized.class)
        public class ISortTest {
        
            private ISort sortAlgorithm;
        
            public ISortTest(ISort sortAlgorithm) {
                this.sortAlgorithm = sortAlgorithm;
            }
            
            @Test
            public void sort() {
                assertNotNull(sortAlgorithm);
                int n = 10000;
                int[] ints = new Random().ints().limit(n).toArray();
        
                int[] sortInts = Arrays.stream(ints)
                        .sorted()
                        .toArray();
        
                int[] testSotrInts = sortAlgorithm.sort(ints);
                assertArrayEquals(sortInts, testSotrInts);
            }
        
            @Parameterized.Parameters
            public static Collection<Object[]> data() {
                return Arrays.asList(
                    new Object[] { new BubleSort() },
                    new Object[] { new MergeSort() }
                );
            }
        
        }
        


        1. javaevangelist
          01.04.2018 14:46

          Дак вот! Я о том же, и без рефлексии, по крайней мере самопальной


          1. PqDn Автор
            01.04.2018 16:01

            Ну тут всего лишь пример, так получилось что для тестирования…
            Пример показывает как использовать рефлексию для работы с дженериками.

            Если брать JUnit, то там внутри очень много рефлексии.
            Если взять спринг, то там очень много рефлексии.

            Всё это удобно и классно

            Но чтобы сделать что-то подобное самому, нужно понимать как и самому сделать такое.


            1. javaevangelist
              01.04.2018 20:00

              Тогда надо было назвать статью «Как работать с generics через reflection api»