2016-07-29 6 views
1

Я понимаю, что для захвата лямбда должен быть выделен объект (будь то Object[] или abc$Lambda$xyz). Можно ли так или иначе настроить этот процесс? Скажем, у меня есть этот код:Pool capture lambdas

private void test() { 
    int x = 5; 
    Supplier<Integer> supplier =() -> x; 
    foo(supplier); // potentially passes the supplier to another thread etc. 
} 

, и я не хочу, чтобы выделить объект захвата x, но вместо того, чтобы просто получить его из пула и заполнить значения; Я также знаю, что в какой-то момент я могу вернуть объект в пул.

Я мог бы написать

Supplier<Integer> supplier = pool.get(x, v -> v); 

и я мог бы иметь специализированные версии для различных типов аргументов (как использование Object... будет делать выделение (в порядке, есть шанс, что выделение будет устранено путем анализа побега ...), но это сделало бы код совершенно нечитаемым. Поэтому я искал более аспект, как способ.

ли такое возможно?


EDIT: сделать функциональность пула более очевидна, get может быть реализован как

class IntHolderSupplier implements Supplier<Integer> { 
    int value; 
    IntFunction<Integer> func; 
    @Override public Integer get() { 
     return func.apply(value); 
    }   
} 

class Pool { 
    Supplier<Integer> get(int arg, IntFunction<Integer> func) { 
     IntHolderSupplier holder = ...; 
     holder.value = arg; 
     holder.func = func; 
     return holder; 
    } 
} 

и я должен был бы такой держатель с конкретными подписями для всех возможных типов лямбды я хочу использовать.

Возможно, я немного упростил пример, предоставив функцию, но я хотел бы зафиксировать тот факт, что может быть добавлено дополнительное вычисление для захваченного аргумента в момент вызова Supplier.get().

И, пожалуйста, проигнорируйте тот факт, что int помещается в коробку, что может привести к распределению.

+0

Вы имеете в виду что-то вроде 'поставщик =() -> pool.getInteger();'? – bradimus

+0

@bradimus Извините, я не понимаю вопроса. Цель состоит в том, чтобы получить объединенный объект, который по мере возврата бизнес-логики. Пул не может знать об этой бизнес-логике. –

+0

Реализации этого держателя должны помочь вам предотвратить создание объектов, если функция также не захватывает. Мне очень любопытно, где вы должны оптимизировать эту часть, не могли бы вы дать контекст, где он будет использоваться? –

ответ

1

To "pool capture lambdas" является неправильным. Лямбда-выражения - это техническое решение для получения экземпляра функционального интерфейса. Поскольку вы не объединяете лямбда-выражения, а экземпляры интерфейса, бросая каждый технический аспект лямбда-выражений, например неизменность или тот факт, что JRE/JVM контролирует их время жизни, вы должны называть его «экземплярами функционального интерфейса пула».

Таким образом, вы можете реализовать пул для этого экземпляра, так же как вы можете реализовать пул для любого объекта. Маловероятно, что такой пул работает лучше, чем управляемые объекты JVM, созданные для лямбда-выражений, но хорошо, вы можете попробовать.

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

class SupplierCache { 
    static final int SIZE = 100; 
    static LinkedHashMap<Object,Supplier<Object>> CACHE = 
     new LinkedHashMap<Object, Supplier<Object>>(SIZE, 1f, true) { 
     @Override 
     protected boolean removeEldestEntry(Map.Entry<Object, Supplier<Object>> eldest) { 
      return size() > SIZE; 
     } 
    }; 
    @SuppressWarnings("unchecked") 
    static <T> Supplier<T> getSupplier(T t) { 
     return (Supplier<T>)CACHE.computeIfAbsent(t, key ->() -> key); 
    } 
} 

(добавить безопасность потока, если вам это нужно). Таким образом, заменив Supplier<Integer> supplier =() -> x; на Supplier<Integer> supplier = SupplierCache.getSupplier(x);, вы получите функциональность кеша, и, поскольку вам не нужно их выпускать, вам не нужно делать допущенные ошибки в отношении своего жизненного цикла.

Создание пула объектов, реализующих Supplier и возвращая значение изменяемого поля, так что вы можете вручную вернуть экземпляры, не слишком сложно, если вы просто создаете обычный класс, реализующий Supplier, но хорошо, вы открываете в целом может червей с ручным управлением памятью, включая риск восстановления объекта, который все еще используется. Эти объекты не могут быть разделены, как неизменяемый объект, как в приведенном выше примере. И вы заменяете выделение объектов действием обнаружения возвращаемого пула экземпляра плюс действие явного возврата экземпляра после использования - нет причин, по которым это должно быть быстрее.

+0

Замена '() -> x' на' SupplierCache.getSupplier (x) '- это то, чего я хотел избежать. –

+0

Ну, если вы хотите сохранить форму исходного кода лямбда-выражений, нет встроенного решения, как объяснено, из-за сомнительного результата. Тем не менее, реализовать его поверх компилированных файлов классов было бы возможно.Как правило, JRE является ответственным, поэтому, если вы хотите ввести другое поведение без изменения JRE, вам придется использовать манипуляцию с байтовым кодом/Instrumentation для перенаправления метода начальной загрузки на альтернативную реализацию, имеющую эту функцию кэширования. – Holger

+0

@ Хороший трюк о кеше LRU .. определенно один плюс! – Eugene