В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему. Мы пару раз писали о приятных вкусностях Java 8 , и теперь мы чувствуем, что пришло время начать новую серию блогов,…
Ява 8 Пятница
Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .
Java 8 Goodie: простое как пирог локальное кэширование
Теперь приготовьтесь к одному из самых удивительных открытий в этой серии. Мы покажем вам простой способ реализовать локальный кеш, используя старые добрые выражения ConcurrentHashMap
и лямбда-выражения. Потому что в Map
теперь есть новый способ атомарного вычисления нового значения в случае отсутствия ключа . Идеально подходит для кэшей. Давайте углубимся в код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public static void main(String[] args) { for ( int i = 0 ; i < 10 ; i++) System.out.println( "f(" + i + ") = " + fibonacci(i)); } static int fibonacci( int i) { if (i == 0 ) return i; if (i == 1 ) return 1 ; System.out.println( "Calculating f(" + i + ")" ); return fibonacci(i - 2 ) + fibonacci(i - 1 ); } |
Да. Это наивный способ делать вещи. Даже для небольших чисел, таких как fibonacci(5)
, вышеприведенный алгоритм выведет огромное количество строк, поскольку мы повторяем те же вычисления в геометрической прогрессии:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Calculating f(6) Calculating f(4) Calculating f(2) Calculating f(3) Calculating f(2) Calculating f(5) Calculating f(3) Calculating f(2) Calculating f(4) Calculating f(2) Calculating f(3) Calculating f(2) f(6) = 8 |
То, что мы хотим сделать, это создать кеш из предварительно рассчитанных чисел Фибоначчи. Самый простой способ — запоминать все значения в кэше . Вот как мы строим кеш:
1
2
|
static Map<Integer, Integer> cache = new ConcurrentHashMap<>(); |
Готово! Как упоминалось ранее, мы используем недавно добавленный Map.computeIfAbsent()
для вычисления нового значения из source
функции, только если у нас еще нет значения для данного ключа. Кэширование! И поскольку этот метод гарантированно выполняется атомарно, и поскольку мы используем ConcurrentHashMap
, этот кеш даже является поточно-ориентированным, не прибегая к ручному применению synchronized
любом месте. И это может быть использовано для других целей, кроме вычисления числа Фибоначчи. Но давайте сначала применим этот кеш к нашей функции fibonacci()
.
01
02
03
04
05
06
07
08
09
10
11
|
static int fibonacci( int i) { if (i == 0 ) return i; if (i == 1 ) return 1 ; return cache.computeIfAbsent(i, (key) -> fibonacci(i - 2 ) + fibonacci(i - 1 )); } |
Вот и все. Это не может быть проще, чем это! Хотите доказательства? Мы будем регистрировать сообщение на консоли каждый раз, когда мы на самом деле оцениваем новое значение:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
static int fibonacci( int i) { if (i == 0 ) return i; if (i == 1 ) return 1 ; return cache.computeIfAbsent(i, (key) -> { System.out.println( "Slow calculation of " + key); return fibonacci(i - 2 ) + fibonacci(i - 1 ); }); } |
Вышеуказанная программа напечатает
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
f(0) = 0 f(1) = 1 Slow calculation of 2 f(2) = 1 Slow calculation of 3 f(3) = 2 Slow calculation of 4 f(4) = 3 Slow calculation of 5 f(5) = 5 Slow calculation of 6 f(6) = 8 Slow calculation of 7 f(7) = 13 Slow calculation of 8 f(8) = 21 Slow calculation of 9 f(9) = 34 |
Как бы мы сделали это в Java 7?
Хороший вопрос. С большим количеством кода. Мы, вероятно, напишем что-то подобное, используя двойную проверку блокировки :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
static int fibonacciJava7( int i) { if (i == 0 ) return i; if (i == 1 ) return 1 ; Integer result = cache.get(i); if (result == null ) { synchronized (cache) { result = cache.get(i); if (result == null ) { System.out.println( "Slow calculation of " + i); result = fibonacci(i - 2 ) + fibonacci(i - 1 ); cache.put(i, result); } } } return result; } |
Будучи убеждена?
Обратите внимание, что ваше реальное решение, вероятно, будет использовать кэши Guava .
Вывод
Лямбды — это только одна часть Java 8. Важная часть, но давайте не будем забывать обо всех новых функциях, которые были добавлены в библиотеки и которые теперь можно использовать с лямбдами!
Это действительно захватывающе и …
Мы можем значительно улучшить наши кодовые базы, не прибегая к новым библиотекам. Все вышеперечисленное может быть запущено только с библиотеками JDK.
На следующей неделе в этой серии блогов мы рассмотрим, как Java 8 улучшит существующие и новые API параллелизма, так что следите за обновлениями!
Подробнее о Java 8
А пока взгляните на удивительную страницу ресурсов Java 8 от Eugen Paraschiv.