Ответ 1
Один из вариантов Reflection - генерировать файл класса динамически. Этот сгенерированный класс должен выполнять требуемое действие, например. вызывает метод, обнаруженный во время выполнения, и реализует interface
, известный во время компиляции, так что его можно вызвать сгенерированный метод не отражающим образом с использованием этого интерфейса. Theres один catch: если применимо, Reflection делает тот же трюк внутри. Это не работает в особых случаях, например. при вызове метода private
, поскольку вы не можете создать файл юридического класса, вызывающий его. Таким образом, в реализации Reflection существуют разные типы обработчиков вызовов, используя либо сгенерированный код, либо собственный код. Вы не можете победить.
Но более важно то, что Reflection выполняет проверки безопасности при каждом вызове. Таким образом, ваш сгенерированный класс будет проверяться только при загрузке и создании экземпляров, что может стать большой победой. Но в качестве альтернативы вы можете вызвать setAccessible(true)
в экземпляре Method
, чтобы включить проверки безопасности. Тогда остается только незначительная потеря производительности при создании автобоксинга и массиве varargs.
Так как Java 7 есть альтернатива обоим, MethodHandle
. Большим преимуществом является то, что, в отличие от двух других, он даже работает в среде с ограничениями безопасности. Проверки доступа для MethodHandle
выполняются при его приобретении, но не при его вызове. Он имеет так называемую "полиморфную подпись", что означает, что вы можете ссылаться на нее с произвольными типами аргументов без автоматического бокса или создания массива. Конечно, неправильные типы аргументов создадут соответствующий RuntimeException
.
(Обновление)
С помощью Java 8 существует возможность использовать фоновый интерфейс выражения языка lambda и язык ссылки на метод во время выполнения. Этот бэкэнд выполняет именно то, что описано в начале, генерируя класс динамически, который реализует interface
, который ваш код может вызывать непосредственно, когда он известен во время компиляции. Точная механика специфична для реализации, поэтому undefined, но вы можете предположить, что реализация попытается изо всех сил сделать вызов как можно быстрее. Нынешняя реализация Oracles JRE делает это отлично. Мало того, что это избавляет вас от бремени генерации такого класса accessor, оно также способно делать то, что вы никогда не могли сделать, - вызывать даже private
методы с помощью сгенерированного кода. Я обновил пример, чтобы включить это решение. В этом примере используется стандартный interface
, который уже существует и имеет желаемую подпись метода. Если такого соответствия interface
не существует, вам необходимо создать свой собственный интерфейс доступа с помощью метода с правильной подписью. Но, конечно, теперь код примера требует запуска Java 8.
Вот простой пример:
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.IntBinaryOperator;
public class TestMethodPerf
{
private static final int ITERATIONS = 50_000_000;
private static final int WARM_UP = 10;
public static void main(String... args) throws Throwable
{
// hold result to prevent too much optimizations
final int[] dummy=new int[4];
Method reflected=TestMethodPerf.class
.getDeclaredMethod("myMethod", int.class, int.class);
final MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh=lookup.unreflect(reflected);
IntBinaryOperator lambda=(IntBinaryOperator)LambdaMetafactory.metafactory(
lookup, "applyAsInt", MethodType.methodType(IntBinaryOperator.class),
mh.type(), mh, mh.type()).getTarget().invokeExact();
for(int i=0; i<WARM_UP; i++)
{
dummy[0]+=testDirect(dummy[0]);
dummy[1]+=testLambda(dummy[1], lambda);
dummy[2]+=testMH(dummy[1], mh);
dummy[3]+=testReflection(dummy[2], reflected);
}
long t0=System.nanoTime();
dummy[0]+=testDirect(dummy[0]);
long t1=System.nanoTime();
dummy[1]+=testLambda(dummy[1], lambda);
long t2=System.nanoTime();
dummy[2]+=testMH(dummy[1], mh);
long t3=System.nanoTime();
dummy[3]+=testReflection(dummy[2], reflected);
long t4=System.nanoTime();
System.out.printf("direct: %.2fs, lambda: %.2fs, mh: %.2fs, reflection: %.2fs%n",
(t1-t0)*1e-9, (t2-t1)*1e-9, (t3-t2)*1e-9, (t4-t3)*1e-9);
// do something with the results
if(dummy[0]!=dummy[1] || dummy[0]!=dummy[2] || dummy[0]!=dummy[3])
throw new AssertionError();
}
private static int testMH(int v, MethodHandle mh) throws Throwable
{
for(int i=0; i<ITERATIONS; i++)
v+=(int)mh.invokeExact(1000, v);
return v;
}
private static int testReflection(int v, Method mh) throws Throwable
{
for(int i=0; i<ITERATIONS; i++)
v+=(int)mh.invoke(null, 1000, v);
return v;
}
private static int testDirect(int v)
{
for(int i=0; i<ITERATIONS; i++)
v+=myMethod(1000, v);
return v;
}
private static int testLambda(int v, IntBinaryOperator accessor)
{
for(int i=0; i<ITERATIONS; i++)
v+=accessor.applyAsInt(1000, v);
return v;
}
private static int myMethod(int a, int b)
{
return a<b? a: b;
}
}
Th старой программы, напечатанной в моей настройке Java 7: direct: 0,03s, mh: 0,32s, reflection: 1,05s
, которая предположила, что MethodHandle
была хорошей альтернативой. Теперь обновленная программа, работающая под Java 8 на том же компьютере, напечатанном direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s
, которая наглядно показывает, что производительность отражения была улучшена до такой степени, что это может сделать ненужным работу с MethodHandle
, если вы не используете ее для выполнения лямбда-трюка, что явно превосходит все отражающие альтернативы, что не вызывает удивления, поскольку это просто прямой вызов (ну, почти: один уровень косвенности). Обратите внимание, что я сделал целевой метод private
, чтобы продемонстрировать возможность эффективного вызова методов private
.
Как всегда, я должен указать на простоту этого теста и насколько он искусственен. Но я думаю, что эта тенденция четко видна и даже более важна, результаты убедительно объяснимы.