Я взламываю легкую эзотерическую библиотеку, и я озадачен, пожалуй, самой гиперспецифичной проблемой в истории. Я хочу проверить, может ли взятие ссылки на метод какого-либо метода m
реализовать некоторый функциональный интерфейс FI
с параметром получателя или без него (т. Е. Если m
принадлежит какому-то классу C
и c instanceof C
, я хочу проверить c::m
и C::m
).
Я решил это для некоторых случаев ... проверил arity, проверил типы аргументов, проверил тип возврата, проверил типы исключений, разберись с возможными параметрами получателя, yada yada. Но это становится волосатым, когда дженерики вступают в игру.
interface TypeMatcher {
boolean isSubtype(final Type type);
boolean isSupertype(final Type type);
static TypeMatcher.of(final Type type) {
// ...
}
}
class TypeVariableMatcher implements TypeMatcher {
private final TypeVariable<?> typeVariable;
private TypeMatcher binding;
TypeVariableMatcher(final TypeVariable<?> typeVariable) {
this.typeVariable = typeVariable;
}
@Override public boolean isSubtype(final Type type) {
// TODO: check bounds... how?
return check(type, TypeMatcher::isSubtype);
}
@Override public boolean isSupertype(final Type type) {
// TODO: check bounds... how?
return check(type, TypeMatcher::isSupertype);
}
private boolean check(final Type type, final BiPredicate<TypeMatcher, Type> callback) {
if (binding == null) {
binding = TypeMatcher.of(type);
return true;
}
return callback.test(binding, type);
}
}
Чтобы проверить, может ли метод реализовать функциональный метод, я просто создаю кэш TypeMatcher
(пусть cache
будет Map
, затем matcherFactory = type -> cache.computeIfAbsent(type, TypeMatcher::of)
). Затем я просто пытаюсь сопоставить аргументы, возвращаемый тип и типы исключений в указанном порядке (это не так просто из-за varargs и возможного параметра получателя и т. Д., Но вы получите jist). Кэш помогает обрабатывать интерфейсы, такие как java.util.BinaryOperator
, но ...
Как отмечается в // TODO
, здесь не проверяются параметры ограниченного типа, поэтому этот тестовый случай завершится неудачей:
interface FI<R extends Runnable> {
void fm(R r1, R r2);
}
interface C {
void cannotImplementFI(String s1, String s2);
}
... поскольку String
будет сообщено как "супертип" R
, хотя это не так. Я также не уверен, как обращаться с ограниченными символами:
interface FI {
<T> void fm(Supplier<? extends T> s, Consumer<? super T> c);
}
interface C {
void canImplementFI(Supplier<String> s, Consumer<Object>);
void cannotImplementFI(Supplier<Number> s, Consumer<Integer> c);
}
Возможно, мой подход неверен, или, может быть, я слишком долго боролся с той же проблемой, и я упускаю что-то очевидное. Любые указатели о том, как поступить отсюда, были бы просто замечательными !!