Ответ 1
Вы должны провести различие между частыми выполнениями одного и того же сайта-звонка, без учета состояния lambda или полного состояния lambdas и частого использования ссылки на метод с тем же методом (с помощью разных сайтов-звонков).
Посмотрите на следующие примеры:
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=System::gc;
if(r1==null) r1=r2;
else System.out.println(r1==r2? "shared": "unshared");
}
Здесь один и тот же call-сайт выполняется два раза, создавая безстоящую лямбду и текущая реализация печатает "shared"
.
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=Runtime.getRuntime()::gc;
if(r1==null) r1=r2;
else {
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
}
}
В этом втором примере один и тот же сайт вызова выполняется два раза, создавая лямбда, содержащую ссылку на экземпляр Runtime
, и текущая реализация будет печатать "unshared"
, но "shared class"
.
Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
В отличие от этого, в последнем примере два разных узла вызова, создающих эквивалентную ссылку на метод, но с 1.8.0_05
будут печатать "unshared"
и "unshared class"
.
Для каждого выражения лямбда или ссылки на метод компилятор выдает команду invokedynamic
, которая ссылается на JRE, предоставленный метод начальной загрузки в классе LambdaMetafactory
, и статический аргументы, необходимые для создания желаемого класса реализации лямбда. Остается фактическая JRE, что создает meta factory, но это указание поведения команды invokedynamic
для запоминания и повторного использования экземпляра CallSite
, созданного при первом вызове.
В текущем JRE создается ConstantCallSite
, содержащий MethodHandle
постоянный объект для апатридов лямбда (и у них нет мыслимой причины сделать это по-другому). И ссылки методов на метод static
всегда являются апатридами. Таким образом, для безъядерных lambdas и одиночных сайтов-вызовов ответ должен быть: dont cache, JVM будет делать, и если это не так, у него должны быть веские причины, по которым вы не должны противодействовать.
Для lambdas, имеющего параметры, и this::func
является лямбдой, которая имеет ссылку на экземпляр this
, все немного отличается. JRE разрешено кэшировать их, но это подразумевает сохранение некоторого вида Map
между фактическими значениями параметров и полученной лямбда, которая может быть более дорогостоящей, чем просто создание этого простого структурированного экземпляра лямбда снова. Текущая JRE не кэширует экземпляры lambda, имеющие состояние.
Но это не означает, что класс лямбда создается каждый раз. Это просто означает, что разрешенный вызывающий сайт будет вести себя как обычная конструкция объекта, создающая экземпляр класса лямбда, который был сгенерирован при первом вызове.
Аналогичные вещи применяются к ссылкам метода к одному и тому же методу назначения, создаваемому разными сайтами-контактами. JRE разрешено использовать один экземпляр лямбды между ними, но в текущей версии это не так, скорее всего, потому, что неясно, будет ли обслуживание кэша окупиться. Здесь даже сгенерированные классы могут отличаться.
Таким образом, кэширование, как в вашем примере, может привести к тому, что ваша программа выполняет разные действия, чем без. Но не обязательно более эффективно. Кэшированный объект не всегда более эффективен, чем временный объект. Если вы действительно не измеряете влияние производительности, вызванное созданием лямбда, вы не должны добавлять кеширование.
Я думаю, что есть только некоторые особые случаи, когда кэширование может быть полезно:
- мы говорим о множестве разных call-сайтов, ссылающихся на тот же метод
- lambda создается в инициализации конструктора/класса, потому что позже на сайте
- вызывается несколькими потоками одновременно
- страдают от более низкой производительности первого вызова