Это плохая привычка (пере) использовать отражение?

16

Является ли хорошей практикой использование отражения, если значительно сокращается количество стандартного кода?

По сути, существует компромисс между производительностью и, возможно, удобочитаемостью с одной стороны и абстракцией / автоматизацией / сокращением стандартного кода с другой стороны.

Изменить: Вот пример рекомендуемого использования отражения .

Чтобы привести пример, предположим, что существует абстрактный класс, Baseкоторый имеет 10 полей и имеет 3 подкласса SubclassA, SubclassBи SubclassCкаждый с 10 различными полями; они все простые бобы. Проблема в том, что вы получаете две Baseссылки на типы и хотите увидеть, имеют ли их соответствующие объекты одинаковый (под) тип и равны ли они.

В качестве решения есть необработанное решение, в котором вы сначала проверяете, равны ли типы, а затем проверяете все поля или вы можете использовать отражение и динамически определять, относятся ли они к одному типу, и выполнять итерацию по всем методам, которые начинаются с «get» (соглашение через конфигурацию), вызовите их на обоих объектах и ​​вызовите равных по результатам.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}
m3th0dman
источник
20
Злоупотребление чем-либо - плохая привычка.
Тулаинс Кордова
1
@ user61852 Правильно, слишком большая свобода ведет к диктатуре. Какой-то древнегреческий уже знал об этом.
ot--
6
«Слишком много воды будет вредно для вас. Очевидно, что слишком много это именно то количество, которое чрезмерно - вот что это значит! »- Стивен Фрай
Джон Пурди
4
«Каждый раз, когда вы обнаруживаете, что пишете код формы», если объект имеет тип T1, то что-то делаете, но если он имеет тип T2, тогда делайте что-то еще », - пощечина. Javapractices.com/topic/TopicAction.do?Id = 31
rm5248

Ответы:

25

Отражение была создана для определенной цели, чтобы открыть функциональность класса , который был известен во время компиляции, похоже на то , что dlopenи dlsymфункции делают в C. Любое использование вне , которые должны быть в значительной степени изучены.

Вам когда-нибудь приходило в голову, что сами Java-дизайнеры столкнулись с этой проблемой? Вот почему практически у каждого класса есть equalsметод. Разные классы имеют разные определения равенства. В некоторых случаях производный объект может быть равен базовому объекту. В некоторых случаях равенство может быть определено на основе частных полей без получателей. Вы не знаете

Вот почему каждый объект, который хочет пользовательского равенства, должен реализовать equalsметод. В конце концов, вы захотите поместить объекты в набор или использовать их в качестве хеш-индекса, тогда вам придется все equalsравно реализовать . Другие языки делают это по-другому, но Java использует equals. Вы должны придерживаться правил вашего языка.

Кроме того, «шаблонный» код, если поместить его в правильный класс, довольно сложно испортить. Отражение добавляет дополнительную сложность, что означает дополнительные шансы на ошибки. Например, в вашем методе два объекта считаются равными, если один возвращается nullдля определенного поля, а другой - нет. Что если один из ваших получателей вернет один из ваших объектов без соответствующего equals? Ваш if (!object1.equals(object2))провал. Кроме того, он подвержен ошибкам, так как рефлексия используется редко, поэтому программисты не так хорошо знакомы с ее ошибками.

Карл Билефельдт
источник
12

Злоупотребление отражением, вероятно, зависит от используемого языка. Здесь вы используете Java. В этом случае отражение следует использовать с осторожностью, потому что зачастую это всего лишь обходной путь для плохого дизайна.

Итак, вы сравниваете разные классы, это идеальная проблема для переопределения методов . Обратите внимание, что экземпляры двух разных классов никогда не должны рассматриваться как равные. Вы можете сравнить на равенство, только если у вас есть экземпляры одного класса. См. Https://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-java для примера того, как правильно реализовать сравнение на равенство.

Sulthan
источник
16
+1 - Некоторые из нас считают, что ЛЮБОЕ использование отражения - это красный флаг, указывающий на плохой дизайн.
Росс Паттерсон
1
Скорее всего, в этом случае желательно решение, основанное на равенстве, но как общая идея, что не так с решением с отражением? На самом деле это очень универсально, и методы equals не нужно явно писать в каждом классе и подклассе (хотя они могут быть легко сгенерированы хорошей IDE).
m3th0dman
2
@RossPatterson Почему?
m3th0dman
2
@ m3th0dman Потому что это приводит к таким вещам, как ваш compare()метод, предполагая, что любой метод, начинающийся с «get», является геттером и безопасен и уместен для вызова как часть операции сравнения. Это нарушение предполагаемого определения интерфейса объекта, и хотя это может быть целесообразным, оно почти всегда неверно.
Росс Паттерсон
4
@ m3th0dman Это нарушает инкапсуляцию - базовый класс должен обращаться к атрибутам в своих подклассах. Что, если они являются частными (или частными получателями)? Как насчет полиморфизма? Если я решу добавить другой подкласс, и я хочу сделать сравнение по-другому в нем? Ну, базовый класс уже делает это для меня, и я не могу это изменить. Что делать, если получатели лениво загружают что-то? Я хочу, чтобы метод сравнения сделал это? Как я узнаю, что метод, начинающийся с, getявляется геттером, а не пользовательским методом, возвращающим что-то?
Sulthan
1

Я думаю, что у вас есть две проблемы здесь.

  1. Сколько динамического и статического кода я должен иметь?
  2. Как я могу выразить пользовательскую версию равенства?

Динамический и статический код

Это постоянный вопрос, и ответ очень самоуверенный.

С одной стороны, ваш компилятор очень хорошо ловит все виды плохого кода. Это осуществляется посредством различных форм анализа, причем типовой анализ является распространенным. Он знает, что вы не можете использовать Bananaобъект в коде, который ожидает Cog. Это говорит вам об ошибке компиляции.

Теперь он может делать это только тогда, когда он может вывести как принятый Тип, так и данный Тип из контекста. Сколько можно сделать вывод и насколько общий вывод, во многом зависит от используемого языка. Java может вывести информацию о типе с помощью таких механизмов, как Наследование, Интерфейсы и Обобщения. Пробег меняется, некоторые другие языки предоставляют меньше механизмов, а некоторые предоставляют больше. Это все еще сводится к тому, что компилятор может знать, чтобы быть правдой.

С другой стороны, ваш компилятор не может предсказать форму внешнего кода, и иногда общий алгоритм может быть выражен для многих типов, которые не могут быть легко выражены с использованием системы типов языка. В этих случаях компилятор не всегда может знать результат заранее, и он может даже не знать, какой вопрос задать. Отражение, интерфейсы и класс Object - это способ решения этих проблем в Java. Вы должны будете обеспечить правильные проверки и обработку, но иметь такой код не вредно для здоровья.

Делать ли ваш код очень конкретным или очень общим, сводится к проблеме, которую вы пытаетесь решить. Если вы можете легко выразить это, используя систему типов, сделайте это. Пусть компилятор использует свои сильные стороны и поможет вам. Если система типов не может знать заранее (внешний код), или система типов плохо подходит для общей реализации вашего алгоритма, тогда рефлексия (и другие динамические средства) - правильный инструмент для использования.

Просто знайте, что выход за пределы системы типов вашего языка удивителен. Представьте, что вы подошли к своему другу и начали разговор на английском. Внезапно добавьте несколько слов из испанского, французского и кантонского языков, которые точно выражают ваши мысли. Контекст много расскажет вашему другу, но он также может не знать, как правильно обращаться с этими словами, что приводит к всевозможным недоразумениям. Разве лучше справляться с этими недоразумениями, чем объяснять эти идеи на английском, используя больше слов?

Пользовательское равенство

Хотя я понимаю, что Java в значительной степени опирается на equalsметод для общего сравнения двух объектов, он не всегда подходит в данном контексте.

Есть другой способ, и это тоже стандарт Java. Его называют компаратором .

То, как вы реализуете свой компаратор, будет зависеть от того, что вы сравниваете и как.

  • Он может применяться к любым двум объектам независимо от их конкретной equalsреализации.
  • Он может реализовать общий (основанный на отражении) метод сравнения для обработки любых двух объектов.
  • Стандартные функции сравнения могут быть добавлены для обычно сравниваемых типов объектов, обеспечивая безопасность типов и оптимизацию.
Kain0_0
источник
1

Я предпочитаю избегать рефлексивного программирования, насколько это возможно, потому что это

  • затрудняет статическую проверку кода компилятором
  • делает код сложнее рассуждать о
  • затрудняет рефакторинг кода

Это также намного менее производительно, чем простые вызовы методов; Раньше он был медленнее на порядок или более.

Статическая проверка кода

Любой рефлексивный код ищет классы и методы, используя строки; в исходном примере он ищет любой метод, начинающийся с «get»; он будет возвращать получатели, но также и другие методы, такие как "gettysburgAddress ()". Правила могут быть ужесточены в коде, но суть в том, что это проверка во время выполнения ; IDE и компилятор не могут помочь. В целом, мне не нравится «строго типизированный» или «примитивно одержимый» код.

Труднее рассуждать о

Рефлексивный код более многословен, чем простые вызовы методов. Больше кода = больше ошибок или, по крайней мере, больше возможностей для ошибок, больше кода для чтения, тестирования и т. Д. Меньше значит больше.

Труднее рефакторинг

Потому что код является строковым / динамическим; как только отражение появляется на сцене, вы не можете рефакторировать код со 100% -ной достоверностью, используя инструменты рефакторинга IDE, поскольку IDE не может использовать рефлексивное использование.

По сути, избегайте отражения в общем коде, если это вообще возможно; Ищите улучшенный дизайн.

Дэвид Керр
источник
0

Злоупотребление чем-либо по определению плохо, верно? Итак, давайте избавимся от (более) на данный момент.

Вы бы назвали удивительно тяжелое внутреннее использование отражения весной плохой привычкой?

Spring приручает отражение с помощью аннотаций - как и Hibernate (и, возможно, десятки / сотни других инструментов).

Следуйте этим шаблонам, если вы используете его в своем собственном коде. Используйте аннотации, чтобы гарантировать, что IDE вашего пользователя все еще может им помочь (даже если вы являетесь единственным «пользователем» вашего кода, небрежное использование отражения, вероятно, в конечном итоге вернется, чтобы укусить вас в задницу).

Однако без учета того, как ваш код будет использоваться разработчиками, даже простейшее использование отражения, вероятно, является чрезмерным.

Билл К
источник
0

Я думаю, что большинство из этих ответов не имеют смысла.

  1. Да, вы, вероятно, должны написать equals()и hashcode(), как отметил @KarlBielefeldt.

  2. Но для класса со многими полями это может быть утомительным примером.

  3. Итак, это зависит .

    • Если вам редко нужны равенства и хэш-код , прагматично и, вероятно, нормально, использовать вычисления отражения общего назначения. По крайней мере, как быстрый и грязный первый проход.
    • но если вам нужно много равных, например, эти объекты помещаются в HashTables, так что производительность будет проблемой, вы обязательно должны написать код. это будет намного быстрее.
  4. Другая возможность: если у ваших классов действительно так много полей, что написание равных утомительно, подумайте

    • положить поля в карту.
    • вы все еще можете написать собственные геттеры и сеттеры
    • используйте Map.equals()для вашего сравнения. (или Map.hashcode())

например ( примечание : игнорируя нулевые проверки, вероятно, следует использовать Enums вместо ключей String, многое не показано ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
user949300
источник