Типобезопасное отражение метода в Java - PullRequest
6 голосов
/ 06 января 2010

Есть ли практический способ ссылаться на метод в классе безопасным для типов способом?Базовый пример: если бы я хотел создать что-то вроде следующей служебной функции:

public Result validateField(Object data, String fieldName, 
                            ValidationOptions options) { ... }

Чтобы вызвать ее, мне нужно было бы сделать:

validateField(data, "phoneNumber", options);

Что заставляет менялибо используйте волшебную строку, либо объявите где-нибудь константу с этой строкой.

Я почти уверен, что нет способа обойти это с помощью стандартного языка Java, но есть ли какой-то (производственный класс) пред-компилятор или альтернативный компилятор, который может предложить обходной путь?(аналогично тому, как AspectJ расширяет язык Java) Было бы неплохо сделать что-то вроде следующего:

public Result validateField(Object data, Method method, 
                            ValidationOptions options) { ... }

И вызвать его с помощью:

validateField(data, Person.phoneNumber.getter, options);

Ответы [ 8 ]

4 голосов
/ 06 января 2010

Как уже упоминали другие, нет реального способа сделать это ... и я не видел прекомпилятор, который поддерживает это. Синтаксис был бы интересен, если не сказать больше. Даже в вашем примере он может охватывать лишь небольшое подмножество потенциальных отражательных возможностей, которые может захотеть сделать пользователь, поскольку он не будет обрабатывать нестандартные методы доступа или методы, которые принимают аргументы и т. Д.

Даже если невозможно проверить во время компиляции, если вы хотите, чтобы плохой код завершился сбоем как можно скорее, тогда один из подходов состоит в том, чтобы разрешить ссылочные объекты Method во время инициализации класса.

Представьте, что у вас есть служебный метод для поиска объектов Method, который может выдавать ошибку или исключение во время выполнения:

public static Method lookupMethod( Class c, String name, Class... args ) {
    // do the lookup or throw an unchecked exception of some kind with a really
    // good error message
}

Тогда в ваших классах есть константы для предварительного разрешения методов, которые вы будете использовать:

public class MyClass {
    private static final Method GET_PHONE_NUM = MyUtils.lookupMethod( PhoneNumber.class, "getPhoneNumber" );

    ....

    public void someMethod() {
        validateField(data, GET_PHONE_NUM, options);
    }
}

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

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

3 голосов
/ 28 июля 2015

Извлечение https://jodd.org/ref/methref.html. Он использует прокси-библиотеку Jodd (Proxetta) для прокси вашего типа. Не уверен насчет его эксплуатационных характеристик, но он обеспечивает безопасность типов.

Пример: предположим, что Str.class имеет метод .boo(), и вы хотите получить его имя в виде строки "boo":

Methref<Str> m = Methref.on(Str.class);

// `.to()` returns a proxied instance of `Str` upon which you
// can call `.boo()` Methods on this proxy are empty except when
// you call them, the proxy stores the method's name. So doing this
// gets the proxy to store the name `"boo"`.

m.to().boo();

// You can get the name of the method you called by using `.ref()`:

m.ref();   // returns "boo"                                 

В API есть нечто большее, чем в примере выше: https://oblac.github.io/jodd-site/javadoc/jodd/methref/Methref.html

3 голосов
/ 06 января 2010

В языке пока нет ничего - но, как мне кажется, часть предложения замыканий для Java 7 включает литералы методов.

У меня нет никаких предложений, боюсь.

2 голосов
/ 12 июля 2013

Java пропускает синтаксический сахар, чтобы сделать что-то такое же хорошее, как Person.phoneNumber.getter. Но если Person является интерфейсом, вы можете записать метод получения, используя динамический прокси. Вы также можете записывать методы в нефинальные классы, используя CGLib, точно так же, как это делает Mockito.

MethodSelector<Person> selector = new MethodSelector<Person>(Person.class);
selector.select().getPhoneNumber();
validateField(data, selector.getMethod(), options);

Код для MethodSelector: https://gist.github.com/stijnvanbael/5965609

1 голос
/ 06 января 2010

Есть ли практический способ ссылки на метод в классе безопасным для типов образом?

Прежде всего, отражение является типобезопасным. Просто он динамически типизирован, а не статически.

Итак, предполагая, что вы хотите статически типизированный эквивалент отражения, теоретический ответ таков: это невозможно. Учтите это:

Method m;
if (arbitraryFunction(obj)) {
    obj.getClass().getDeclaredMethod("foo", ...);
} else {
    obj.getClass().getDeclaredMethod("bar", ...);
}

Можем ли мы сделать это так, чтобы исключения типа времени выполнения не возникали? В общем случае НЕТ, поскольку это повлечет за собой доказательство того, что arbitraryFunction(obj) заканчивается. (Это эквивалентно проблеме останова, которая в целом доказана неразрешимой и неразрешимой с использованием современной технологии доказательства теорем ... AFAIK.)

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

На мой взгляд, в настоящее время единственным умеренно практичным подходом является замена отражающего кода чем-то, что генерирует и компилирует исходный код Java. Если этот процесс происходит до того, как вы «запустите» приложение, вы выполнили требование статической безопасности типов.


Я больше спрашивал об отражении, в котором результат всегда одинаков. И.Е. Person.class.getMethod("getPhoneNumber", null) всегда возвращает один и тот же метод, и его можно полностью разрешить во время компиляции.

Что произойдет, если после компиляции класса, содержащего этот код, вы измените Person для удаления метода getPhoneNumber?

Единственный способ убедиться, что вы можете разрешить getPhoneNumber рефлексивно, - это если вы как-то предотвратите изменение Person. Но вы не можете сделать это в Java. Привязка классов во время выполнения является фундаментальной частью языка.

(Для записи, если бы вы сделали это для метода, который вы вызывали неотражающим образом, вы бы получили IncompatibleClassChangeError некоторого вида, когда два класса были загружены ...)

0 голосов
/ 08 января 2019

Используйте Manifold's @Jailbreak для времени компиляции type-safe доступ к приватным полям, методам и т. Д.

@Jailbreak Foo foo = new Foo();
foo.privateMethod();
foo.privateMethod("hey");
foo._privateField = 88;

public class Foo {
  private final int _privateField;

  public Foo(int value) {
    _privateField = value;
  }

  private String privateMethod() {
    return "hi";
  }

  private String privateMethod(String param) {
    return param;
  }
}

Узнать больше: тип-безопасное отражение

0 голосов
/ 12 марта 2015

Фреймворк отмычка позволяет выполнять следующие действия:

class Data {
  private PhoneNumber phoneNumber;
}

interface OpenData {
  PhoneNumber getPhoneNumber(); //is mapped to the field phoneNumber
}

Object data = new Data();
PhoneNumber number = ObjectAccess
  .unlock(data)
  .features(OpenData.class)
  .getPhoneNumber();

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

 assertThat(Data.class, providesFeaturesOf(OpenData.class));
0 голосов
/ 02 февраля 2012

Вдохновленные насмешливыми фреймворками, мы могли бы придумать следующий синтаксис:

validator.validateField(data, options).getPhoneNumber();
Result validationResult = validator.getResult();

Хитрость заключается в общем объявлении:

class Validator {
    public <T> T validateField(T data, options) {...}
}

Теперь возвращаемый тип метода совпадает с типом объекта данных, и вы можете использовать завершение кода (и статическую проверку) для доступа ко всем методам, включая методы получения.

Как недостаток, код не совсем интуитивно понятен для чтения, поскольку вызов метода получения фактически ничего не получает, а вместо этого дает команду валидатору проверить поле.

Другой возможный вариант - аннотировать поля в вашем классе данных:

class FooData {
    @Validate(new ValidationOptions(...))
    private PhoneNumber phoneNumber;
}

А потом просто позвоните:

FooData data;
validator.validate(data);

для проверки всех полей в соответствии с аннотированными параметрами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...