Одна из альтернатив Reflection - генерировать файл класса динамически. Этот сгенерированный класс должен выполнять требуемое действие, например. вызывает метод, обнаруженный во время выполнения, и реализует interface
, известный во время компиляции, так что с помощью этого интерфейса можно вызвать сгенерированный метод не отражающим образом. Есть один улов: если применимо, Reflection делает тот же трюк внутри. Это не работает в особых случаях, например. при вызове метода private
, поскольку вы не можете создать файл юридического класса, вызывающий его. Таким образом, в реализации Reflection существуют разные типы обработчиков вызовов, используя либо сгенерированный код, либо собственный код. Вы не можете победить.
Но более важно то, что Reflection выполняет проверки безопасности при каждом вызове. Таким образом, ваш сгенерированный класс будет проверяться только при загрузке и создании экземпляров, что может стать большой победой. Но в качестве альтернативы вы можете вызвать setAccessible(true)
на экземпляр Method
, чтобы включить проверки безопасности. Тогда остается только незначительная потеря производительности автобоксинга и создания массива varargs.
С Java 7 есть альтернатива обоим, MethodHandle
. Большим преимуществом является то, что, в отличие от двух других, он даже работает в среде с ограничениями безопасности. Проверки доступа для MethodHandle
выполняются при его приобретении, но не при его вызове. Он имеет так называемую «полиморфную подпись», что означает, что вы можете ссылаться на нее с произвольными типами аргументов без автоматического бокса или создания массива. Конечно, неправильные типы аргументов создадут соответствующий RuntimeException
.
(Обновления) С Java 8, есть возможность использовать обратный конец функции языка лямбда-выражение и эталонного метод во время выполнения. Этот бэкэнд делает именно то, что описано в начале, генерируя класс динамически, который реализует interface
, который ваш код может вызывать непосредственно, когда он известен во время компиляции. Точная механика специфична для реализации, поэтому не определена, но вы можете предположить, что реализация попытается сделать так, чтобы сделать вызов как можно быстрее. Текущая реализация JRE от Oracle делает это отлично. Мало того, что это избавляет вас от бремени создания такого класса 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
, что наглядно показывает, что производительность отражения была улучшена до такой степени, что может оказаться ненужным, если вы не используете ее для выполнения лямбда-трюка, что явно превосходит все отражающие альтернативы, что не вызывает удивления, поскольку это просто прямой вызов (ну, почти: один уровень косвенности). Обратите внимание, что я сделал целевой метод private
, чтобы продемонстрировать возможность эффективного вызова методов private
.
Как всегда, я должен указать на простоту этого теста и как он искусственен. Но я думаю, что эта тенденция отчетливо видна и даже более важна, результаты убедительно объяснимы.
Основной альтернативой является _not_ манипулирование и изменение кода во время выполнения, что обычно довольно быстро. –
Довольно сложной альтернативой было бы прямое управление байт-кодом, см. Https://code.google.com/p/java-box-factory/, http://asm.ow2.org/, http: //commons.apache .org/proper/commons-bcel/ – harpun
Что вы делаете с отражением, и каково текущее влияние на производительность? –