Ian Robertson
Lambda Chops
Recipes for Simpler, More Expressive Code
Agenda
● Lambda review
● Lambda tricks
● Lack of exception transparency
● Turning patterns into libraries
● Adding type safety with lambdas
● API design best practices
About Me
● Application Architect at Myriad Genetics
● 17 years experience of working on large-scale
Java projects
● Twitter: @nainostrebor
● https://github.com/irobertson
● JavaOne Rockstar
Disclaimer
● Not all code shown in this talk is optimally
performant.
● Neither is it particularly non-performant.
● Most of the time, expressiveness trumps
performance.
Lambdas
● New in Java 8.
● More than just anon inner classes on steroids
● Represented by single method interface.
Function<String, Integer> length = (String s) -> { return s.length(); };
…................................................ = s -> s.length();
…................................................ = String::length;
Primary advertised use case: streams
● Much more expressive
● Multi-core friendly
Map<City, Double> engineerSalaryByCity = employees.stream()
.filter(e -> e.getJobType().equals("Engineer"))
.collect(Collectors.groupingBy(
Employee::getCity,
Collectors.averagingDouble(Employee::getSalary)));
Lambdas allow us to refer to an action
without actually doing the action.
Lambda trick: Null-Safe Navigation
● Groovy folks can write
employee?.getAddress()?.getCity()?.toString()
● If anything along the way is null, returns null
● Java equivalent:
(employee != null
&& employee.getAddress() != null
&& employee.getAddress().getCity() != null)
? employee.getAddress().getCity().toString()
: null;
Lambda trick: Null-Safe Navigation
● Alternative – (ab)use Optional
String cityName = Optional.ofNullable(employee)
.map(Employee::getAddress)
.map(Address::getCity)
.map(City::toString)
.orElse(null);
Lambda trick: first non-null object
● Commons-lang: ObjectUtils.firstNonNull
<T> T firstNonNull(T... values)
● Fine if values are cheap to provide
firstNonNull(
customer.getCart().getRecommendations(),
getRecommendationsFor(customer.history()),
getBestSellers());
Lambda trick: first non-null object
Recommendations recommendations = firstNonNull(
customer.getCart()::getRecommendations,
() -> getRecommendationsFor(customer.history()),
this::getBestSellers);
● Use of suppliers can avoid work, or parallelize it.
public static <T> T firstNonNull(Supplier<T>... values) {
return Stream.of(values) // .parallel()
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst().orElse(null);
}
Lambda Trick: Multi-method interfaces
public interface FileVisitor {
FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs);
FileVisitResult visitFile(Path file, BasicFileAttributes attrs);
FileVisitResult visitFileFailed(Path file, IOException exc);
FileVisitResult postVisitDirectory(Path dir, IOException exc);
}
● Helper methods can create instances from one
or more lambdas
Lambda Trick: Multi-method interfaces
public static FileVisitor visitor(
BiFunction<Path, BasicFileAttributes, FileVisitResult> preVisitDirectory,
BiFunction<Path, BasicFileAttributes, FileVisitResult> visitFile,
BiFunction<Path, IOException, FileVisitResult> visitFileFailed,
BiFunction<Path, IOException, FileVisitResult> postVisitDirectory) {
return new FileVisitor<Path>() {
@Override public FileVisitResult preVisitDirectory(
Path dir, BasicFileAttributes attrs) throws IOException {
return preVisitDirectory.apply(dir, attrs);
}
….
};
}
Lambda Trick: Multi-method interfaces
public static FileVisitor fileVisitor(
BiConsumer<Path, BasicFileAttributes> visitFile) {
return new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(T file, BasicFileAttributes attrs) {
visitFile.accept(file, attrs);
return FileVisitResult.CONTINUE;
}
};
}
Exception Transparency
public void createFiles(List<File> files) throws IOException {
files.forEach(File::createNewFile);
}
Error:
incompatible thrown types java.io.IOException in method reference
● Exception transparency was discussed, but...
type theory.
Catching Exceptions within Lambda
public void createFiles(List<File> files) { // no throws...
files.forEach(file -> {
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
Exception Transparency via Generics
public interface EConsumer<T, E extends Throwable> {
void accept(T t) throws E;
}
public interface Iterable<T> { // Bizzaro Java
...
default <E extends Throwable> void forEach(
EConsumer<? super T, E> action) throws E { ... }
}
Exception Transparency via Generics
● Type inference actually works OK with this
– Provided you only have one throw type
● Signatures get messy
● Generally not worth it
“Uncheck” Exceptions
● Use jOOλ library (https://github.com/jOOQ/jOOL)
@FunctionalInterface public interface CheckedConsumer<T> {
void accept(T t) throws Throwable;
}
public static <T, R> Consumer<T, R> consumer(
CheckedConsumer<T, R> function) { … }
public void createFiles(List<File> files) { // no throws at all...
files.forEach(Unchecked.consumer(File::createNewFile));
}
Design Patterns
● Wikipedia: A general repeatable solution to a
commonly occurring problem
● Stuart Halloway: A code smell
– Design patterns tend to point to language
deficiencies
● Many patterns are of the form
boilerPlate customCode moreBoilerPlate
Resource management in Java 6
String result;
InputStream stream = openStream();
try {
result = process(stream);
}
finally {
stream.close();
}
Resource management In Java 7
String result;
try (InputStream stream = openStream()) {
result = process(stream);
}
But what if I forget?...
String result = process(openStream()); // resource leak!!!
Resource management In Java 7
try(Lock lock = acquireLock()) {
Account account = getAccount();
debit(account);
}
Try-with-resources only works for AutoClosable
Lock cannot be converted to AutoCloseable
Resource Management with Lambdas
public class AccountManager {
private Lock lock;
private Account account;
public <T> T workWithAccount(Consumer<Account> work) {
lock.lock();
try { work.accept(account); }
finally { lock.unlock(); }
}
}
accountManager.workWithAccount(account -> debit(account));
Laziness
● Lambda's are great for lazy evaluation
● Vavr (formerly javaslang) has a good Lazy
● Note – evaluation may well happen on a
different thread!
Using Lambda's for Type Safety
● Many APIs rely on String representations of
method names
– “Stringly typed”
– Refactoring tools are generally unaware of this
– Typos not caught at compile time
● Lambda expressions are often a good
alternative.
Make a comparator
● Creating a comparator: commons-beanutils
ComparatorChain comparator = new ComparatorChain(
new BeanComparator<>("lastName"));
comparator.addComparator(
new BeanComparator<>("firstName"));
@SuppressWarnings("unchecked")
Comparator<Person> personComparator = comparator;
Make a comparator
● Creating a comparator: better API
● Better – but still only stringly typed.
Comparator<Person> comparator = comparatorChain(
Person.class, "lastName", "firstName");
Make a comparator – type safety
● Creating a comparator: Java 8
Comparator<Person> comparator =
Comparator.comparing(Person::getLastName)
.thenComparing(Person::getFirstName);
JUnit 5 Parameterized Tests
public static Stream<Arguments> stringIntValues() {
return Stream.of(
Arguments.of("1", 1), Arguments.of("2", 2));
}
@ParameterizedTest
@MethodSource("stringIntValues")
public void testIntValueOf(String s, int i) {
assertEquals(s, String.valueOf(i));
}
JUnit 5 Type-safe Parameterized Tests
@TestFactory
Stream<DynamicTest> testIntValueOf() {
return Stream.of(tuple("1", 1), tuple("2", 2))
.map(tuple -> tuple.map((s, i) -> dynamicTest(
"testing " + i,
() -> assertEquals(s, String.valueOf(i)))));
}
JUnit 5 Type-safe Parameterized Tests
@TestFactory
Stream<DynamicTest> testIntValueOf() {
return parameterized(
(s, i) -> dynamicTest(
"testing " + i,
() -> assertEquals(s, String.valueOf(i))),
tuple("1", 1), tuple("2", 2));
}
@SafeVarargs
public static <T1, T2> Stream<DynamicTest> parameterized(
Function2<T1, T2, DynamicTest> test, Tuple2<T1, T2>... data) {
return Stream.of(data).map(datum -> datum.map(test));
}
Data driven tests - LambdataRunner
@RunWith(LambdataRunner.class)
public class StringTest {
@Test
public TestSpecs testSubString() {
return specs(
(s, start, end, expected) ->
assertEquals(expected, s.substring(start, end)),
datum("hello", 0, 5, "hello"),
datum("flexible", 1, 4, "lex"),
datum("hello", 5, 5, ""));
}
}
Best practices
● Prefer multiple single-method interfaces to
larger interfaces
– Barring that, consider factory methods taking in
labmdas
Best practices
● Use standard java.util.function classes where
applicable, or corresponding org.jooq.lambda
classes if Exception transparency is an issue.
Best practices
● Avoid overloading method names to accept
different lambdas of the same “shape”.
● Forces clients to cast their lambdas
public void doWork(Runnable work) { ... }
public void doWork(CheckedRunnable work) { ... }
Thank you!
http://www.slideshare.net/nainostrebor/
Please fill out the session survey!

Lambda Chops - Recipes for Simpler, More Expressive Code

  • 1.
    Ian Robertson Lambda Chops Recipesfor Simpler, More Expressive Code
  • 2.
    Agenda ● Lambda review ●Lambda tricks ● Lack of exception transparency ● Turning patterns into libraries ● Adding type safety with lambdas ● API design best practices
  • 3.
    About Me ● ApplicationArchitect at Myriad Genetics ● 17 years experience of working on large-scale Java projects ● Twitter: @nainostrebor ● https://github.com/irobertson ● JavaOne Rockstar
  • 4.
    Disclaimer ● Not allcode shown in this talk is optimally performant. ● Neither is it particularly non-performant. ● Most of the time, expressiveness trumps performance.
  • 5.
    Lambdas ● New inJava 8. ● More than just anon inner classes on steroids ● Represented by single method interface. Function<String, Integer> length = (String s) -> { return s.length(); }; …................................................ = s -> s.length(); …................................................ = String::length;
  • 6.
    Primary advertised usecase: streams ● Much more expressive ● Multi-core friendly Map<City, Double> engineerSalaryByCity = employees.stream() .filter(e -> e.getJobType().equals("Engineer")) .collect(Collectors.groupingBy( Employee::getCity, Collectors.averagingDouble(Employee::getSalary)));
  • 7.
    Lambdas allow usto refer to an action without actually doing the action.
  • 8.
    Lambda trick: Null-SafeNavigation ● Groovy folks can write employee?.getAddress()?.getCity()?.toString() ● If anything along the way is null, returns null ● Java equivalent: (employee != null && employee.getAddress() != null && employee.getAddress().getCity() != null) ? employee.getAddress().getCity().toString() : null;
  • 9.
    Lambda trick: Null-SafeNavigation ● Alternative – (ab)use Optional String cityName = Optional.ofNullable(employee) .map(Employee::getAddress) .map(Address::getCity) .map(City::toString) .orElse(null);
  • 10.
    Lambda trick: firstnon-null object ● Commons-lang: ObjectUtils.firstNonNull <T> T firstNonNull(T... values) ● Fine if values are cheap to provide firstNonNull( customer.getCart().getRecommendations(), getRecommendationsFor(customer.history()), getBestSellers());
  • 11.
    Lambda trick: firstnon-null object Recommendations recommendations = firstNonNull( customer.getCart()::getRecommendations, () -> getRecommendationsFor(customer.history()), this::getBestSellers); ● Use of suppliers can avoid work, or parallelize it. public static <T> T firstNonNull(Supplier<T>... values) { return Stream.of(values) // .parallel() .map(Supplier::get) .filter(Objects::nonNull) .findFirst().orElse(null); }
  • 12.
    Lambda Trick: Multi-methodinterfaces public interface FileVisitor { FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs); FileVisitResult visitFile(Path file, BasicFileAttributes attrs); FileVisitResult visitFileFailed(Path file, IOException exc); FileVisitResult postVisitDirectory(Path dir, IOException exc); } ● Helper methods can create instances from one or more lambdas
  • 13.
    Lambda Trick: Multi-methodinterfaces public static FileVisitor visitor( BiFunction<Path, BasicFileAttributes, FileVisitResult> preVisitDirectory, BiFunction<Path, BasicFileAttributes, FileVisitResult> visitFile, BiFunction<Path, IOException, FileVisitResult> visitFileFailed, BiFunction<Path, IOException, FileVisitResult> postVisitDirectory) { return new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory( Path dir, BasicFileAttributes attrs) throws IOException { return preVisitDirectory.apply(dir, attrs); } …. }; }
  • 14.
    Lambda Trick: Multi-methodinterfaces public static FileVisitor fileVisitor( BiConsumer<Path, BasicFileAttributes> visitFile) { return new SimpleFileVisitor() { @Override public FileVisitResult visitFile(T file, BasicFileAttributes attrs) { visitFile.accept(file, attrs); return FileVisitResult.CONTINUE; } }; }
  • 15.
    Exception Transparency public voidcreateFiles(List<File> files) throws IOException { files.forEach(File::createNewFile); } Error: incompatible thrown types java.io.IOException in method reference ● Exception transparency was discussed, but... type theory.
  • 16.
    Catching Exceptions withinLambda public void createFiles(List<File> files) { // no throws... files.forEach(file -> { try { file.createNewFile(); } catch (IOException e) { throw new RuntimeException(e); } }); }
  • 17.
    Exception Transparency viaGenerics public interface EConsumer<T, E extends Throwable> { void accept(T t) throws E; } public interface Iterable<T> { // Bizzaro Java ... default <E extends Throwable> void forEach( EConsumer<? super T, E> action) throws E { ... } }
  • 18.
    Exception Transparency viaGenerics ● Type inference actually works OK with this – Provided you only have one throw type ● Signatures get messy ● Generally not worth it
  • 19.
    “Uncheck” Exceptions ● UsejOOλ library (https://github.com/jOOQ/jOOL) @FunctionalInterface public interface CheckedConsumer<T> { void accept(T t) throws Throwable; } public static <T, R> Consumer<T, R> consumer( CheckedConsumer<T, R> function) { … } public void createFiles(List<File> files) { // no throws at all... files.forEach(Unchecked.consumer(File::createNewFile)); }
  • 20.
    Design Patterns ● Wikipedia:A general repeatable solution to a commonly occurring problem ● Stuart Halloway: A code smell – Design patterns tend to point to language deficiencies ● Many patterns are of the form boilerPlate customCode moreBoilerPlate
  • 21.
    Resource management inJava 6 String result; InputStream stream = openStream(); try { result = process(stream); } finally { stream.close(); }
  • 22.
    Resource management InJava 7 String result; try (InputStream stream = openStream()) { result = process(stream); } But what if I forget?... String result = process(openStream()); // resource leak!!!
  • 23.
    Resource management InJava 7 try(Lock lock = acquireLock()) { Account account = getAccount(); debit(account); } Try-with-resources only works for AutoClosable Lock cannot be converted to AutoCloseable
  • 24.
    Resource Management withLambdas public class AccountManager { private Lock lock; private Account account; public <T> T workWithAccount(Consumer<Account> work) { lock.lock(); try { work.accept(account); } finally { lock.unlock(); } } } accountManager.workWithAccount(account -> debit(account));
  • 25.
    Laziness ● Lambda's aregreat for lazy evaluation ● Vavr (formerly javaslang) has a good Lazy ● Note – evaluation may well happen on a different thread!
  • 26.
    Using Lambda's forType Safety ● Many APIs rely on String representations of method names – “Stringly typed” – Refactoring tools are generally unaware of this – Typos not caught at compile time ● Lambda expressions are often a good alternative.
  • 27.
    Make a comparator ●Creating a comparator: commons-beanutils ComparatorChain comparator = new ComparatorChain( new BeanComparator<>("lastName")); comparator.addComparator( new BeanComparator<>("firstName")); @SuppressWarnings("unchecked") Comparator<Person> personComparator = comparator;
  • 28.
    Make a comparator ●Creating a comparator: better API ● Better – but still only stringly typed. Comparator<Person> comparator = comparatorChain( Person.class, "lastName", "firstName");
  • 29.
    Make a comparator– type safety ● Creating a comparator: Java 8 Comparator<Person> comparator = Comparator.comparing(Person::getLastName) .thenComparing(Person::getFirstName);
  • 30.
    JUnit 5 ParameterizedTests public static Stream<Arguments> stringIntValues() { return Stream.of( Arguments.of("1", 1), Arguments.of("2", 2)); } @ParameterizedTest @MethodSource("stringIntValues") public void testIntValueOf(String s, int i) { assertEquals(s, String.valueOf(i)); }
  • 31.
    JUnit 5 Type-safeParameterized Tests @TestFactory Stream<DynamicTest> testIntValueOf() { return Stream.of(tuple("1", 1), tuple("2", 2)) .map(tuple -> tuple.map((s, i) -> dynamicTest( "testing " + i, () -> assertEquals(s, String.valueOf(i))))); }
  • 32.
    JUnit 5 Type-safeParameterized Tests @TestFactory Stream<DynamicTest> testIntValueOf() { return parameterized( (s, i) -> dynamicTest( "testing " + i, () -> assertEquals(s, String.valueOf(i))), tuple("1", 1), tuple("2", 2)); } @SafeVarargs public static <T1, T2> Stream<DynamicTest> parameterized( Function2<T1, T2, DynamicTest> test, Tuple2<T1, T2>... data) { return Stream.of(data).map(datum -> datum.map(test)); }
  • 33.
    Data driven tests- LambdataRunner @RunWith(LambdataRunner.class) public class StringTest { @Test public TestSpecs testSubString() { return specs( (s, start, end, expected) -> assertEquals(expected, s.substring(start, end)), datum("hello", 0, 5, "hello"), datum("flexible", 1, 4, "lex"), datum("hello", 5, 5, "")); } }
  • 34.
    Best practices ● Prefermultiple single-method interfaces to larger interfaces – Barring that, consider factory methods taking in labmdas
  • 35.
    Best practices ● Usestandard java.util.function classes where applicable, or corresponding org.jooq.lambda classes if Exception transparency is an issue.
  • 36.
    Best practices ● Avoidoverloading method names to accept different lambdas of the same “shape”. ● Forces clients to cast their lambdas public void doWork(Runnable work) { ... } public void doWork(CheckedRunnable work) { ... }
  • 37.

Editor's Notes

  • #25 This is inversion of control