Лямбды
в Java 8
—
Чашников Николай
1
8 2
Что?
Зачем?
Как?
Когда?
Где?
3
Что?
Runnable r =
() -> System.out.println("Hello");
4
Нет функциональных типов
package java.util.function;
public interface Function<T, R> {
R apply(T t);
}
5
Специализации для примитивов
43 интерфейса в java.util.function: *Supplier, *Consumer, *Predicate, *Operator,
*Function.
Есть все варианты
(void|int|long|double|T) -> (void|boolean|int|long|double|T)
И ещё несколько для функций с двумя параметрами.
6
Приходится везде указывать вариантность
7
Ковариантная позиция
void foo(Supplier<PsiElement> factory) {
PsiElement element = factory.get();
...
}
void bar() {
Supplier<PsiClass> classes = ...
foo(classes); // <- не скомпилируется
}
8
Ковариантная позиция
void foo(Supplier<? extends PsiElement> factory) {
PsiElement element = factory.get();
...
}
void bar() {
Supplier<PsiClass> classes = ...
foo(classes); // <- теперь работает
}
9
Контравариантная позиция
void foo(Consumer<PsiClass> consumer) {
PsiClass e = ...;
consumer.accept(e);
}
void bar() {
Consumer<PsiElement> consumer = ...
foo(consumer); // <- не скомпилируется
}
10
Контравариантная позиция
void foo(Consumer<? super PsiClass> consumer) {
PsiClass e = ...;
consumer.accept(e);
}
void bar() {
Consumer<PsiElement> consumer = ...
foo(consumer); // <- теперь работает
}
11
Бывает сразу и то, и то
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction){
}
12
Правило PECS
Producer extends, consumer super.
13
Обычно легко исправить
Добавление ? extends и ? super в параметры вызываемых методов не
ломает ни binary-compatibility, ни source-compatibility. (Если у них нет
наследников.)
14
Зачем
Чтобы писать более простой для понимания код, скрывая низкоуровневые
детали.
15
Когда-то мы писали так
void print(List<String> strings) {
for (int i = 0; i < strings.size(); i++) {
String s = strings.get(i);
System.out.println(s);
}
}
16
Теперь пишем так
void print(List<String> strings) {
for (String s : strings) {
System.out.println(s);
}
}
17
А если что-то более сложное?
void move(List<PsiClass> classes) {
boolean containsAnonymous = false;
for (PsiClass aClass : classes) {
if (aClass instanceof PsiAnonymousClass) {
containsAnonymous = true;
break;
}
}
...
}
18
Теперь можно написать так
void move(List<PsiClass> classes) {
boolean containsAnonymous =
classes.stream().anyMatch(
c -> c instanceof PsiAnonymousClass
);
...
}
19
Код может быть и более сложным
void move(List<PsiClass> classes) {
int numberOfAnonymouses = 0;
for (PsiClass aClass : classes) {
if (aClass instanceof PsiAnonymousClass) {
numberOfAnonymouses++;
if (numberOfAnonymouses >= 2) {
break;
}
}
}
...
}
20
И его все равно можно упростить
void move(List<PsiClass> classes) {
long numberOfAnonymouses = classes.stream()
.filter(c -> c instanceof PsiAnonymousClass)
.limit(2)
.count();
...
}
21
Всегда ли Stream API упрощает?
void foo(List<PsiMethod> methods) {
List<PsiMethod> constructors =
methods.stream().filter(PsiMethod::isConstructor)
.collect(Collectors.toList());
}
void foo(List<PsiMethod> methods) {
List<PsiMethod> constructors =
ContainerUtil.filter(methods, PsiMethod::isConstructor);
}
22
Всегда ли Stream API упрощает?
void foo(List<PsiMethod> methods) {
List<PsiMethod> constructors =
methods.stream().filter(PsiMethod::isConstructor)
.collect(Collectors.toList());
}
void foo(List<PsiMethod> methods) {
List<PsiMethod> constructors =
ContainerUtil.filter(methods, PsiMethod::isConstructor);
}
23
Что делает этот метод?
Object bar(PsiElement element) {
return
Optional.ofNullable(element.getContainingFile())
.map(PsiFile::getVirtualFile)
.map(f -> ModuleUtilCore.findModuleForFile(f,
element.getProject()))
.orElse(null);
}
24
Это может быть не сразу очевидно
Module findModule(PsiElement element) {
return
Optional.ofNullable(element.getContainingFile())
.map(PsiFile::getVirtualFile)
.map(f -> ModuleUtilCore.findModuleForFile(f,
element.getProject()))
.orElse(null);
}
25
Тот же результат
Module findModule(PsiElement element) {
PsiFile file = element.getContainingFile();
if (file == null) return null;
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) return null;
return ModuleUtilCore.findModuleForFile(virtualFile,
element.getProject());
}
26
Другой пример
return Optional
.ofNullable(PyUtil.as(element,
PySubscriptionExpression.class))
.map(PySubscriptionExpression::getOperand)
.map(PyExpression::getText)
.filter(text -> text.equals(keywordContainerName))
.isPresent();
27
Сокращается ещё больше
return element instanceof PySubscriptionExpression
&& keywordContainerName.equals(
((PySubscriptionExpression)element).getOperand().getText());
28
Замены Optional
Вместо Optional можно использовать @Nullable типы, особенно в сигнатурах
методов.
29
Часто есть несколько вариантов
void foo(List<VirtualFile> files) {
...
files.stream().map(VirtualFile::getFileType)
.anyMatch(StdFileTypes.XML::equals)
files.stream().map(VirtualFile::getFileType)
.anyMatch(Predicate.isEqual(StdFileTypes.XML));
files.stream().anyMatch(file ->
file.getFileType().equals(StdFileTypes.XML));
...
}
30
Легко получить запутанный код
Arrays.stream(PhpLibraryRoot. EP_NAME.getExtensions()).
map(PhpLibraryRoot::getProvider).
filter(PhpLibraryRootProvider::isRuntime).
map(provider -> provider.getLibraryRoot(getProject())).
filter(Optional::isPresent).
map(Optional::get).
map(VirtualFile::getChildren).
map(Arrays:: asList).
flatMap(Collection::stream).
filter(VirtualFile::isDirectory).
filter(module -> ! ".idea".equals(module.getName())).
...
31
IDEA 2017.1 поможет его упростить
32
В результате получим
Arrays.stream(PhpLibraryRoot. EP_NAME.getExtensions())
.map(PhpLibraryRoot::getProvider)
.filter(PhpLibraryRootProvider::isRuntime)
.map(provider -> provider.getLibraryRoot(getProject()))
.filter(Objects:: nonNull)
.flatMap(root -> Arrays. stream(root.getChildren()))
.filter(file -> file.isDirectory() && ! ".idea".equals(file.getName()))
...
33
Где
Где ещё могут быть полезны лямбды?
34
Простая параметризация значением
TreeNode buildTree(PsiClass aClass) {
...
for (PsiMethod method : aClass.getMethods()) {
root.add(createMethodNode(method));
}
...
}
35
Убрать статические методы
TreeNode buildTree(PsiClass aClass,
boolean withStatic) {
...
for (PsiMethod method : aClass.getMethods()) {
if (withStatic || !method.getModifierList()
.hasModifierProperty(PsiModifier.STATIC)) {
root.add(createMethodNode(method));
}
}
...
}
36
Следующая ступень обобщения
abstract class TreeBuilder {
protected abstract boolean acceptMethod(PsiMethod method);
TreeNode buildTree(PsiClass aClass) {
...
for (PsiMethod method : aClass.getMethods()) {
if (acceptMethod(method)) {
root.add(createMethodNode(method));
}
}
...
}
}
37
Переусложнено и выглядит громоздко
new TreeBuilder() {
@Override
protected boolean acceptMethod(PsiMethod method) {
return !method.getModifierList()
.hasModifierProperty(PsiModifier.STATIC);
}
}.buildTree(aClass);
38
Параметризация поведением
39
Параметризация поведением
TreeNode buildTree(PsiClass aClass,
Predicate<PsiMethod> methodFilter) {
...
for (PsiMethod method : aClass.getMethods()) {
if (methodFilter.test(method)) {
root.add(createMethodNode(method));
}
}
...
}
40
Использовать тоже легко
buildTree(aClass,
method -> !method.getModifierList()
.hasModifierProperty(PsiModifier.STATIC));
41
Таких случаев много
public abstract class NotNullLazyValue<T> {
private T myValue;
@NotNull
protected abstract T compute();
...
}
42
Теперь можно создать без наследования
public abstract class NotNullLazyValue<T> {
...
@NotNull
public static <T> NotNullLazyValue<T>
createValue(@NotNull NotNullFactory<T> value) {
...
};
}
43
Есть более сложные случаи
Класс FileReferenceSet, больше 100 наследников, половина из них –
анонимные классы, 23 метода, которые переопределяются в наследниках.
44
Выглядят вот так
return new FileReferenceSet(...) {
protected boolean isSoft() { return soft; }
public boolean isAbsolutePathReference() { return true; }
public boolean couldBeConvertedTo(boolean relative) {...}
public boolean absoluteUrlNeedsStartSlash() {
String s = getPathString();
return s != null && !s.isEmpty() && s.charAt(0) == '/';
}
public Collection<PsiFileSystemItem>
computeDefaultContexts() {...}
}.getAllReferences();
45
Как
Как лямбды устроены внутри?
46
Есть ли здесь утечка памяти?
public class Foo {
void foo() {
Runnable r = new Runnable() {
public void run() {
System.out.println("Bye!");
}
};
Runtime.getRuntime()
.addShutdownHook(new Thread(r));
}
...
}
47
Да, есть ссылка на внешний объект
class Foo$1 implements Runnable {
final Foo this$0;
public Foo$1(Foo foo) {
this$0 = foo;
}
...
}
48
Экземпляр создаётся каждый раз
ContainerUtil.filter(methods,
new Condition<PsiMethod>() {
public boolean value(PsiMethod psiMethod) {
return psiMethod.isConstructor();
}
}
);
49
Во что компилировать лябмды?
public class Simple {
public static void main(String[] args) {
Runnable r =
() -> System.out.println("Hello");
new Thread(r).start();
}
}
50
Самая простая реализация – анонимус
public class Simple {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
System.out.println("Hello");
}
}
new Thread(r).start();
}
}
51
У такой реализации есть недостатки
● лишний class-файл на диске
● каждый раз создаётся новый экземпляр
● Class не будет собран в мусор, пока достижим его ClassLoader
52
Анонимны ли анонимусы?
Class.forName("Simple$1",
true, Simple.class.getClassLoader())
53
Нужен ли вообще класс для лямбды?
Да, ведь можно вызвать r.getClass().
r.getClass().getName()вернёт что-то вроде
"Simple$$Lambda$1/1149319664"
Unsafe.getUnsafe().defineAnonymousClass(...)
54
Классы лямбд – действительно анонимны
boolean isLambda(Runnable r) {
try {
Class.forName(r.getClass().getName(), false,
r.getClass().getClassLoader());
return false;
}
catch (ClassNotFoundException e) {
return true;
}
}
55
Кто будет генерировать код, создающий лямбду?
Записывать этот код в каждый класс неэффективно. Требуется общий метод
в стандартной библиотеке, который будет создавать все классы лямбд.
Назовём его LambdaMetafactory.metafactory.
Но в него надо как-то передавать информацию про то, какой метод какого
интерфейса должна реализовывать лямбда, и код её тела.
56
Для начала сделаем desugaring
public class Simple {
public static void main(String[] args) {
Runnable r = Simple::lambda$main$0;
new Thread(r).start();
}
private static void lambda$main$0() {
System.out.println("Hello");
}
}
57
MethodHandle
public abstract class MethodHandle {
@PolymorphicSignature
public final native Object invoke(Object... args)
throws Throwable;
public MethodType type() { ... }
...
}
public class MethodType {
public static MethodType methodType(Class<?> rtype) {...}
public static MethodType methodType(Class<?> rtype,
Class<?> ptype0) {...}
...
}
58
Отличия от java.lang.reflect.Method
● проверка доступа в момент создания, а не вызова
● работают не только с методами, но и с полями, конструкторами, …
● могут делать преобразования параметров и возвращаемого значения
● работают с примитивными типами напрямую, без заворачивания в
объекты
● не создаётся массив для передачи аргументов
59
Реализация лямбды, попытка 1
public class LambdaMetafactory {
public static Object metafactory0(
String samMethodName,
Class<?> samType,
MethodType samMethodType,
MethodHandle implMethod) {
...
Class cls = Unsafe.getUnsafe()
.defineAnonymousClass(...);
return cls.newInstance();
}
} 60
Реализация лямбды, попытка 1
public class Simple {
public static void main(String[] args) throws Throwable {
MethodHandle implMethod =
MethodHandles.lookup().findStatic(Simple.class,
"lambda$main$0", MethodType.methodType(void.class));
MethodType samMethodType = MethodType.methodType(void.class);
Runnable r = (Runnable) LambdaMetafactory.metafactory0(
"run", Runnable.class, samMethodType, implMethod);
new Thread(r).start();
}
private static void lambda$main$0() {
System.out.println("Hello");
}
}
61
Попытка 2: кэшируем лямбду
public class Simple {
private static Object[] lambdas = new Object[1];
public static void main(String[] args) throws Throwable {
if (lambdas[0] == null) {
...
lambdas[0] = LambdaMetafactory.metafactory0(
"run", Runnable.class, samMethodType, implMethod);
}
Runnable r = (Runnable) lambdas[0];
new Thread(r).start();
}
...
}
62
CallSite
public abstract class CallSite {
public abstract MethodHandle getTarget();
...
}
public class ConstantCallSite extends CallSite {
public ConstantCallSite(MethodHandle target) {...}
}
63
Попытка 3: invokeDynamic
public class Simple {
private static CallSite[] callSites = new CallSite[1];
public static void main(String[] args) throws Throwable {
if (callSites[0] == null) {
MethodHandle implMethod = MethodHandles. lookup().findStatic(
Simple. class, "lambda$main$0", MethodType.methodType(void.class));
MethodType samMethodType = MethodType. methodType(void.class);
callSites[0] = LambdaMetafactory. metafactory(
MethodHandles. lookup(),
"run", MethodType.methodType(Runnable.class),
samMethodType, implMethod, samMethodType);
}
Runnable r = (Runnable) callSites[0].getTarget().invoke();
...
}
...
64
Попытка 3: invokeDynamic
public class Simple {
private static CallSite[] callSites = new CallSite[1];
public static void main(String[] args) throws Throwable {
if (callSites[0] == null) {
MethodHandle implMethod = MethodHandles. lookup().findStatic(
Simple.class, "lambda$main$0", MethodType.methodType(void.class));
MethodType samMethodType = MethodType.methodType(void.class);
callSites[0] = LambdaMetafactory. metafactory(
MethodHandles. lookup(),
"run", MethodType.methodType(Runnable.class),
samMethodType, implMethod, samMethodType);
}
Runnable r = (Runnable) callSites[0].getTarget().invoke() ;
...
}
...
65
Попытка 3: реальная metafactory
static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName, MethodType invokedType,
MethodType samMethodType, MethodHandle implMethod,
MethodType instantiatedMethodType) {
Class cls = Unsafe.getUnsafe()
.defineAnonymousClass(...);
if (invokedType.parameterCount() == 0) {
Object instance = cls.newInstance();
return new ConstantCallSite(
MethodHandles.constant(samType, instance));
}
...
}
66
Замыкания (capturing lambdas)
public class CapturingLambda {
public void foo(String name) {
Runnable r = () ->
System.out.println("Hello, " + name);
new Thread(r).start();
}
}
67
Замыкания: desugaring
public class CapturingLambda {
public void foo(String name) {
Runnable r = () -> lambda$foo$0(name);
new Thread(r).start();
}
private static void lambda$foo$0(String name) {
System.out.println("Hello, " + name);
}
}
68
Замыкания: класс лямбды
final class CapturingLambda$$Lambda$1 implements Runnable {
private final String arg$1;
private CapturingLambda$$Lambda$1(String s) {
arg$1 = s;
}
private static Runnable get$Lambda(String s) {
return new CapturingLambda$$Lambda$1(s);
}
public void run() {
System.out.println("Hello, " + arg$1);
}
}
69
Замыкания: кэширование в metafactory
static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName, MethodType invokedType,
MethodType samMethodType, MethodHandle implMethod,
MethodType instantiatedMethodType) {
Class cls = Unsafe.getUnsafe()
.defineAnonymousClass(...);
if (invokedType.parameterCount() == 0) {...}
else {
return new ConstantCallSite(
MethodHandles.Lookup.IMPL_LOOKUP.findStatic(cls,"get$Lambda",
invokedType));
}
}
70
Замыкания: invokeDynamic
public class CapturingLambda {
private static CallSite[] callSites = new CallSite[1];
public void foo(String name) throws Throwable {
if (callSites[0] == null) {
MethodHandle implMethod =
MethodHandles. lookup().findStatic(CapturingLambda. class,
"lambda$foo$0", MethodType.methodType(void.class, String.class));
MethodType samMethodType = MethodType. methodType(void.class);
callSites[0] = LambdaMetafactory. metafactory(MethodHandles. lookup(),
"run", MethodType.methodType(Runnable.class, String.class),
samMethodType, implMethod, samMethodType);
}
Runnable r = (Runnable) callSites[0].getTarget().invoke(name);
...
71
Ответы
72
Утечки памяти нет
public class Foo {
void foo() {
Runnable r =() -> System.out.println("Bye!");
Runtime.getRuntime()
.addShutdownHook(new Thread(r));
}
...
}
73
Экземпляр для non-capturing переиспользуется
ContainerUtil.filter(methods,
method -> method.isConstructor();
);
ContainerUtil.filter(methods,PsiMethod::isConstructor)
74
Это может приводить к проблемам
public interface Disposable {
void dispose();
}
public class Disposer {
public static void register(Disposable parent,
Disposable child) {
...
map.put(parent, child);
}
}
75
Если лямбды сравниваются как объекты
private final Disposable parent = new Disposable() {
public void dispose() { }
};
void foo() {
Disposer.register(parent, ...);
}
private final Disposable parent = () -> { };
void foo() {
Disposer.register(parent, ...);
}
76
Лямбды работают достаточно эффективно
И создание, и вызовы хорошо инлайнятся.
77
Ссылки
Refactoring to Functional Style with Java 8 by Venkat Subramaniam
Stream API, часть 1, Сергей Куксенко
Stream API: Tagir Valeev
Translation of Lambda Expressions by Brian Goetz
Глубокое погружение в invokedynamic, Владимир Иванов
78
Итоги
● используйте лямбды и стримы, чтобы писать более высокоуровневый и
понятный код
● но не увлекайтесь, функциональный стиль – не самоцель, а лишь
средство
● изучайте, как это устроено; особенно если натыкаетесь на непонятное
поведение
79
Ещё вопросы?
80

Lambdas in java 8

  • 1.
  • 2.
  • 3.
  • 4.
    Что? Runnable r = ()-> System.out.println("Hello"); 4
  • 5.
    Нет функциональных типов packagejava.util.function; public interface Function<T, R> { R apply(T t); } 5
  • 6.
    Специализации для примитивов 43интерфейса в java.util.function: *Supplier, *Consumer, *Predicate, *Operator, *Function. Есть все варианты (void|int|long|double|T) -> (void|boolean|int|long|double|T) И ещё несколько для функций с двумя параметрами. 6
  • 7.
  • 8.
    Ковариантная позиция void foo(Supplier<PsiElement>factory) { PsiElement element = factory.get(); ... } void bar() { Supplier<PsiClass> classes = ... foo(classes); // <- не скомпилируется } 8
  • 9.
    Ковариантная позиция void foo(Supplier<?extends PsiElement> factory) { PsiElement element = factory.get(); ... } void bar() { Supplier<PsiClass> classes = ... foo(classes); // <- теперь работает } 9
  • 10.
    Контравариантная позиция void foo(Consumer<PsiClass>consumer) { PsiClass e = ...; consumer.accept(e); } void bar() { Consumer<PsiElement> consumer = ... foo(consumer); // <- не скомпилируется } 10
  • 11.
    Контравариантная позиция void foo(Consumer<?super PsiClass> consumer) { PsiClass e = ...; consumer.accept(e); } void bar() { Consumer<PsiElement> consumer = ... foo(consumer); // <- теперь работает } 11
  • 12.
    Бывает сразу ито, и то default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction){ } 12
  • 13.
  • 14.
    Обычно легко исправить Добавление? extends и ? super в параметры вызываемых методов не ломает ни binary-compatibility, ни source-compatibility. (Если у них нет наследников.) 14
  • 15.
    Зачем Чтобы писать болеепростой для понимания код, скрывая низкоуровневые детали. 15
  • 16.
    Когда-то мы писалитак void print(List<String> strings) { for (int i = 0; i < strings.size(); i++) { String s = strings.get(i); System.out.println(s); } } 16
  • 17.
    Теперь пишем так voidprint(List<String> strings) { for (String s : strings) { System.out.println(s); } } 17
  • 18.
    А если что-тоболее сложное? void move(List<PsiClass> classes) { boolean containsAnonymous = false; for (PsiClass aClass : classes) { if (aClass instanceof PsiAnonymousClass) { containsAnonymous = true; break; } } ... } 18
  • 19.
    Теперь можно написатьтак void move(List<PsiClass> classes) { boolean containsAnonymous = classes.stream().anyMatch( c -> c instanceof PsiAnonymousClass ); ... } 19
  • 20.
    Код может бытьи более сложным void move(List<PsiClass> classes) { int numberOfAnonymouses = 0; for (PsiClass aClass : classes) { if (aClass instanceof PsiAnonymousClass) { numberOfAnonymouses++; if (numberOfAnonymouses >= 2) { break; } } } ... } 20
  • 21.
    И его всеравно можно упростить void move(List<PsiClass> classes) { long numberOfAnonymouses = classes.stream() .filter(c -> c instanceof PsiAnonymousClass) .limit(2) .count(); ... } 21
  • 22.
    Всегда ли StreamAPI упрощает? void foo(List<PsiMethod> methods) { List<PsiMethod> constructors = methods.stream().filter(PsiMethod::isConstructor) .collect(Collectors.toList()); } void foo(List<PsiMethod> methods) { List<PsiMethod> constructors = ContainerUtil.filter(methods, PsiMethod::isConstructor); } 22
  • 23.
    Всегда ли StreamAPI упрощает? void foo(List<PsiMethod> methods) { List<PsiMethod> constructors = methods.stream().filter(PsiMethod::isConstructor) .collect(Collectors.toList()); } void foo(List<PsiMethod> methods) { List<PsiMethod> constructors = ContainerUtil.filter(methods, PsiMethod::isConstructor); } 23
  • 24.
    Что делает этотметод? Object bar(PsiElement element) { return Optional.ofNullable(element.getContainingFile()) .map(PsiFile::getVirtualFile) .map(f -> ModuleUtilCore.findModuleForFile(f, element.getProject())) .orElse(null); } 24
  • 25.
    Это может бытьне сразу очевидно Module findModule(PsiElement element) { return Optional.ofNullable(element.getContainingFile()) .map(PsiFile::getVirtualFile) .map(f -> ModuleUtilCore.findModuleForFile(f, element.getProject())) .orElse(null); } 25
  • 26.
    Тот же результат ModulefindModule(PsiElement element) { PsiFile file = element.getContainingFile(); if (file == null) return null; VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) return null; return ModuleUtilCore.findModuleForFile(virtualFile, element.getProject()); } 26
  • 27.
  • 28.
    Сокращается ещё больше returnelement instanceof PySubscriptionExpression && keywordContainerName.equals( ((PySubscriptionExpression)element).getOperand().getText()); 28
  • 29.
    Замены Optional Вместо Optionalможно использовать @Nullable типы, особенно в сигнатурах методов. 29
  • 30.
    Часто есть нескольковариантов void foo(List<VirtualFile> files) { ... files.stream().map(VirtualFile::getFileType) .anyMatch(StdFileTypes.XML::equals) files.stream().map(VirtualFile::getFileType) .anyMatch(Predicate.isEqual(StdFileTypes.XML)); files.stream().anyMatch(file -> file.getFileType().equals(StdFileTypes.XML)); ... } 30
  • 31.
    Легко получить запутанныйкод Arrays.stream(PhpLibraryRoot. EP_NAME.getExtensions()). map(PhpLibraryRoot::getProvider). filter(PhpLibraryRootProvider::isRuntime). map(provider -> provider.getLibraryRoot(getProject())). filter(Optional::isPresent). map(Optional::get). map(VirtualFile::getChildren). map(Arrays:: asList). flatMap(Collection::stream). filter(VirtualFile::isDirectory). filter(module -> ! ".idea".equals(module.getName())). ... 31
  • 32.
    IDEA 2017.1 поможетего упростить 32
  • 33.
    В результате получим Arrays.stream(PhpLibraryRoot.EP_NAME.getExtensions()) .map(PhpLibraryRoot::getProvider) .filter(PhpLibraryRootProvider::isRuntime) .map(provider -> provider.getLibraryRoot(getProject())) .filter(Objects:: nonNull) .flatMap(root -> Arrays. stream(root.getChildren())) .filter(file -> file.isDirectory() && ! ".idea".equals(file.getName())) ... 33
  • 34.
    Где Где ещё могутбыть полезны лямбды? 34
  • 35.
    Простая параметризация значением TreeNodebuildTree(PsiClass aClass) { ... for (PsiMethod method : aClass.getMethods()) { root.add(createMethodNode(method)); } ... } 35
  • 36.
    Убрать статические методы TreeNodebuildTree(PsiClass aClass, boolean withStatic) { ... for (PsiMethod method : aClass.getMethods()) { if (withStatic || !method.getModifierList() .hasModifierProperty(PsiModifier.STATIC)) { root.add(createMethodNode(method)); } } ... } 36
  • 37.
    Следующая ступень обобщения abstractclass TreeBuilder { protected abstract boolean acceptMethod(PsiMethod method); TreeNode buildTree(PsiClass aClass) { ... for (PsiMethod method : aClass.getMethods()) { if (acceptMethod(method)) { root.add(createMethodNode(method)); } } ... } } 37
  • 38.
    Переусложнено и выглядитгромоздко new TreeBuilder() { @Override protected boolean acceptMethod(PsiMethod method) { return !method.getModifierList() .hasModifierProperty(PsiModifier.STATIC); } }.buildTree(aClass); 38
  • 39.
  • 40.
    Параметризация поведением TreeNode buildTree(PsiClassaClass, Predicate<PsiMethod> methodFilter) { ... for (PsiMethod method : aClass.getMethods()) { if (methodFilter.test(method)) { root.add(createMethodNode(method)); } } ... } 40
  • 41.
    Использовать тоже легко buildTree(aClass, method-> !method.getModifierList() .hasModifierProperty(PsiModifier.STATIC)); 41
  • 42.
    Таких случаев много publicabstract class NotNullLazyValue<T> { private T myValue; @NotNull protected abstract T compute(); ... } 42
  • 43.
    Теперь можно создатьбез наследования public abstract class NotNullLazyValue<T> { ... @NotNull public static <T> NotNullLazyValue<T> createValue(@NotNull NotNullFactory<T> value) { ... }; } 43
  • 44.
    Есть более сложныеслучаи Класс FileReferenceSet, больше 100 наследников, половина из них – анонимные классы, 23 метода, которые переопределяются в наследниках. 44
  • 45.
    Выглядят вот так returnnew FileReferenceSet(...) { protected boolean isSoft() { return soft; } public boolean isAbsolutePathReference() { return true; } public boolean couldBeConvertedTo(boolean relative) {...} public boolean absoluteUrlNeedsStartSlash() { String s = getPathString(); return s != null && !s.isEmpty() && s.charAt(0) == '/'; } public Collection<PsiFileSystemItem> computeDefaultContexts() {...} }.getAllReferences(); 45
  • 46.
  • 47.
    Есть ли здесьутечка памяти? public class Foo { void foo() { Runnable r = new Runnable() { public void run() { System.out.println("Bye!"); } }; Runtime.getRuntime() .addShutdownHook(new Thread(r)); } ... } 47
  • 48.
    Да, есть ссылкана внешний объект class Foo$1 implements Runnable { final Foo this$0; public Foo$1(Foo foo) { this$0 = foo; } ... } 48
  • 49.
    Экземпляр создаётся каждыйраз ContainerUtil.filter(methods, new Condition<PsiMethod>() { public boolean value(PsiMethod psiMethod) { return psiMethod.isConstructor(); } } ); 49
  • 50.
    Во что компилироватьлябмды? public class Simple { public static void main(String[] args) { Runnable r = () -> System.out.println("Hello"); new Thread(r).start(); } } 50
  • 51.
    Самая простая реализация– анонимус public class Simple { public static void main(String[] args) { Runnable r = new Runnable() { public void run() { System.out.println("Hello"); } } new Thread(r).start(); } } 51
  • 52.
    У такой реализацииесть недостатки ● лишний class-файл на диске ● каждый раз создаётся новый экземпляр ● Class не будет собран в мусор, пока достижим его ClassLoader 52
  • 53.
  • 54.
    Нужен ли вообщекласс для лямбды? Да, ведь можно вызвать r.getClass(). r.getClass().getName()вернёт что-то вроде "Simple$$Lambda$1/1149319664" Unsafe.getUnsafe().defineAnonymousClass(...) 54
  • 55.
    Классы лямбд –действительно анонимны boolean isLambda(Runnable r) { try { Class.forName(r.getClass().getName(), false, r.getClass().getClassLoader()); return false; } catch (ClassNotFoundException e) { return true; } } 55
  • 56.
    Кто будет генерироватькод, создающий лямбду? Записывать этот код в каждый класс неэффективно. Требуется общий метод в стандартной библиотеке, который будет создавать все классы лямбд. Назовём его LambdaMetafactory.metafactory. Но в него надо как-то передавать информацию про то, какой метод какого интерфейса должна реализовывать лямбда, и код её тела. 56
  • 57.
    Для начала сделаемdesugaring public class Simple { public static void main(String[] args) { Runnable r = Simple::lambda$main$0; new Thread(r).start(); } private static void lambda$main$0() { System.out.println("Hello"); } } 57
  • 58.
    MethodHandle public abstract classMethodHandle { @PolymorphicSignature public final native Object invoke(Object... args) throws Throwable; public MethodType type() { ... } ... } public class MethodType { public static MethodType methodType(Class<?> rtype) {...} public static MethodType methodType(Class<?> rtype, Class<?> ptype0) {...} ... } 58
  • 59.
    Отличия от java.lang.reflect.Method ●проверка доступа в момент создания, а не вызова ● работают не только с методами, но и с полями, конструкторами, … ● могут делать преобразования параметров и возвращаемого значения ● работают с примитивными типами напрямую, без заворачивания в объекты ● не создаётся массив для передачи аргументов 59
  • 60.
    Реализация лямбды, попытка1 public class LambdaMetafactory { public static Object metafactory0( String samMethodName, Class<?> samType, MethodType samMethodType, MethodHandle implMethod) { ... Class cls = Unsafe.getUnsafe() .defineAnonymousClass(...); return cls.newInstance(); } } 60
  • 61.
    Реализация лямбды, попытка1 public class Simple { public static void main(String[] args) throws Throwable { MethodHandle implMethod = MethodHandles.lookup().findStatic(Simple.class, "lambda$main$0", MethodType.methodType(void.class)); MethodType samMethodType = MethodType.methodType(void.class); Runnable r = (Runnable) LambdaMetafactory.metafactory0( "run", Runnable.class, samMethodType, implMethod); new Thread(r).start(); } private static void lambda$main$0() { System.out.println("Hello"); } } 61
  • 62.
    Попытка 2: кэшируемлямбду public class Simple { private static Object[] lambdas = new Object[1]; public static void main(String[] args) throws Throwable { if (lambdas[0] == null) { ... lambdas[0] = LambdaMetafactory.metafactory0( "run", Runnable.class, samMethodType, implMethod); } Runnable r = (Runnable) lambdas[0]; new Thread(r).start(); } ... } 62
  • 63.
    CallSite public abstract classCallSite { public abstract MethodHandle getTarget(); ... } public class ConstantCallSite extends CallSite { public ConstantCallSite(MethodHandle target) {...} } 63
  • 64.
    Попытка 3: invokeDynamic publicclass Simple { private static CallSite[] callSites = new CallSite[1]; public static void main(String[] args) throws Throwable { if (callSites[0] == null) { MethodHandle implMethod = MethodHandles. lookup().findStatic( Simple. class, "lambda$main$0", MethodType.methodType(void.class)); MethodType samMethodType = MethodType. methodType(void.class); callSites[0] = LambdaMetafactory. metafactory( MethodHandles. lookup(), "run", MethodType.methodType(Runnable.class), samMethodType, implMethod, samMethodType); } Runnable r = (Runnable) callSites[0].getTarget().invoke(); ... } ... 64
  • 65.
    Попытка 3: invokeDynamic publicclass Simple { private static CallSite[] callSites = new CallSite[1]; public static void main(String[] args) throws Throwable { if (callSites[0] == null) { MethodHandle implMethod = MethodHandles. lookup().findStatic( Simple.class, "lambda$main$0", MethodType.methodType(void.class)); MethodType samMethodType = MethodType.methodType(void.class); callSites[0] = LambdaMetafactory. metafactory( MethodHandles. lookup(), "run", MethodType.methodType(Runnable.class), samMethodType, implMethod, samMethodType); } Runnable r = (Runnable) callSites[0].getTarget().invoke() ; ... } ... 65
  • 66.
    Попытка 3: реальнаяmetafactory static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) { Class cls = Unsafe.getUnsafe() .defineAnonymousClass(...); if (invokedType.parameterCount() == 0) { Object instance = cls.newInstance(); return new ConstantCallSite( MethodHandles.constant(samType, instance)); } ... } 66
  • 67.
    Замыкания (capturing lambdas) publicclass CapturingLambda { public void foo(String name) { Runnable r = () -> System.out.println("Hello, " + name); new Thread(r).start(); } } 67
  • 68.
    Замыкания: desugaring public classCapturingLambda { public void foo(String name) { Runnable r = () -> lambda$foo$0(name); new Thread(r).start(); } private static void lambda$foo$0(String name) { System.out.println("Hello, " + name); } } 68
  • 69.
    Замыкания: класс лямбды finalclass CapturingLambda$$Lambda$1 implements Runnable { private final String arg$1; private CapturingLambda$$Lambda$1(String s) { arg$1 = s; } private static Runnable get$Lambda(String s) { return new CapturingLambda$$Lambda$1(s); } public void run() { System.out.println("Hello, " + arg$1); } } 69
  • 70.
    Замыкания: кэширование вmetafactory static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) { Class cls = Unsafe.getUnsafe() .defineAnonymousClass(...); if (invokedType.parameterCount() == 0) {...} else { return new ConstantCallSite( MethodHandles.Lookup.IMPL_LOOKUP.findStatic(cls,"get$Lambda", invokedType)); } } 70
  • 71.
    Замыкания: invokeDynamic public classCapturingLambda { private static CallSite[] callSites = new CallSite[1]; public void foo(String name) throws Throwable { if (callSites[0] == null) { MethodHandle implMethod = MethodHandles. lookup().findStatic(CapturingLambda. class, "lambda$foo$0", MethodType.methodType(void.class, String.class)); MethodType samMethodType = MethodType. methodType(void.class); callSites[0] = LambdaMetafactory. metafactory(MethodHandles. lookup(), "run", MethodType.methodType(Runnable.class, String.class), samMethodType, implMethod, samMethodType); } Runnable r = (Runnable) callSites[0].getTarget().invoke(name); ... 71
  • 72.
  • 73.
    Утечки памяти нет publicclass Foo { void foo() { Runnable r =() -> System.out.println("Bye!"); Runtime.getRuntime() .addShutdownHook(new Thread(r)); } ... } 73
  • 74.
    Экземпляр для non-capturingпереиспользуется ContainerUtil.filter(methods, method -> method.isConstructor(); ); ContainerUtil.filter(methods,PsiMethod::isConstructor) 74
  • 75.
    Это может приводитьк проблемам public interface Disposable { void dispose(); } public class Disposer { public static void register(Disposable parent, Disposable child) { ... map.put(parent, child); } } 75
  • 76.
    Если лямбды сравниваютсякак объекты private final Disposable parent = new Disposable() { public void dispose() { } }; void foo() { Disposer.register(parent, ...); } private final Disposable parent = () -> { }; void foo() { Disposer.register(parent, ...); } 76
  • 77.
    Лямбды работают достаточноэффективно И создание, и вызовы хорошо инлайнятся. 77
  • 78.
    Ссылки Refactoring to FunctionalStyle with Java 8 by Venkat Subramaniam Stream API, часть 1, Сергей Куксенко Stream API: Tagir Valeev Translation of Lambda Expressions by Brian Goetz Глубокое погружение в invokedynamic, Владимир Иванов 78
  • 79.
    Итоги ● используйте лямбдыи стримы, чтобы писать более высокоуровневый и понятный код ● но не увлекайтесь, функциональный стиль – не самоцель, а лишь средство ● изучайте, как это устроено; особенно если натыкаетесь на непонятное поведение 79
  • 80.