В 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) = 0f(1) = 1Slow calculation of 2f(2) = 1Slow calculation of 3f(3) = 2Slow calculation of 4f(4) = 3Slow calculation of 5f(5) = 5Slow calculation of 6f(6) = 8Slow calculation of 7f(7) = 13Slow calculation of 8f(8) = 21Slow calculation of 9f(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.