Pull to refresh

Comments 12

Работает не только с интерфейсами. И со статическими методами тоже.

import sun.misc.SharedSecrets;
import sun.reflect.ConstantPool;

import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.function.Function;

public class MethodRefs {

    public static void main(String[] args) throws Exception {
        Function<Object, Integer> hashCodeRef = Object::hashCode;
        System.out.println(unreference(hashCodeRef));
    }

    public static Method unreference(Function<?, ?> ref) {
        ConstantPool pool = SharedSecrets.getJavaLangAccess().getConstantPool(ref.getClass());
        int size = pool.getSize();
        for (int i = 1; i < size; i++) {
            try {
                Member member = pool.getMethodAt(i);
                if (member instanceof Method) {
                    return (Method) member;
                }
            } catch (IllegalArgumentException e) {
                // skip non-method entry
            }
        }
        throw new IllegalArgumentException("Not a method reference");
    }
}
Но ваш вариант мне тоже понравился!

А подход с пулом костант хорош!
Правда у меня есть одно замечание — брать первый попавшийся метод некорректно, там может быть что-угодно (например java.lang.Integer.valueOf(int), используемый для боксинга). Во всяком случае мне неизвестно, всегда ли нужный метод будет встречаться первым. Нужен дополнительный анализ

Для методов без аргументов искомый Method всегда окажется первым. Так уж LambdaMetafactory устроена. А, вот, для преобразования аргументов могут вызываться и другие методы. И тут уже без анализа байткода не обойтись. Строго говоря, и байткода-то может не быть. Всё это детали конкретной реализации.

Неплохой вариант, но, к сожалению не работает начиная с Java 9. Развернутый комментарий написал в статье

А где такие хаки могут понадобиться?

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

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


class SimpleWebFramework {
    public static String notFound() {
        return "Page not found!";
    }

    public String invokeMethodForPath(Map<String, Method> methods, String path) {
        Method m = methods.getOrDefault(path, unreference(SimpleWebFramework::notFound));
        ....
        return String.valueOf(m.invoke(....));
        ....
    }
}

Прикольно, но очень не нравится что делается вызов исходного метода. Если нам не повезет и он реально будет принимать Object, то мы на вызове unreference сначала сотворим side effect, а потом еще и замаскируем его с помощью RuntimeException("Something's wrong").

Всё верно, для методов Object этот код совершенно не годится.

Я про другое. С методами класса Object просто вылетит ексепшн мол "я такое не умею" и все будет хорошо. А вот если у меня есть статический метод, который принимает Object, то внутри он может делать какой-нибудь side effect. И вызов unreference этот side effect успешно после себя оставит.


Понятно, что это очень теоретическая проблема и статических методов с сайд эффектом, принимающих Object, не существует. Но, все-таки, в библиотечной функции такая, даже сугубо теоретическая, проблема недопустима.


p.s. Но повторюсь. Решение прикольное (в хорошем смысле этого слова).

Sign up to leave a comment.

Articles