
Mastering Java Lambda Puzzlers
"Explore the world of Java lambda expressions with Peter Lawrey, CEO of Higher Frequency Trading, as he delves into lambda intricacies, offering insights on capturing vs. non-capturing lambdas, transforming imperative code into the Stream API, and a deep dive into Functional vs. Imperative coding approaches."
Download Presentation

Please find below an Image/Link to download the presentation.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author. If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.
You are allowed to download the files provided on this website for personal or commercial use, subject to the condition that they are used lawfully. All files are the property of their respective owners.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author.
E N D
Presentation Transcript
Java Lambda Puzzlers Peter Lawrey CEO of Higher Frequency Trading JAX London 2015
Peter Lawrey Java Developer/Consultant for investment banks, hedge fund and trading firms for 7 years. Most answers for Java and JVM on stackoverflow.com Founder of the Performance Java User s Group. Architect of Chronicle Software
Chronicle Software Help companies migrate to high performance Java code. First large Java 8 project for client in production Dec 2014. Sponsor open source projects https://github.com/OpenHFT Licensed solutions Chronicle-Enterprise and Chronicle-Fix Offer one week proof of concept workshops, advanced Java training, consulting and bespoke development.
Chronicle Software FIX sub-micro second FIX Engine. Enterprise Monitoring, Traffic Shaping, Security. Journal Custom Data Store, Key-Queue Engine Customisable Data Fabric, Reactive Live Queries. Queue Persist every event Map Persisted Key-Value Network Remote access Wire YAML, Binary YAML, JSON, CSV, Raw data. Threads Low latency Bytes 64-bit off heap native + memory mapped files Core Low level access to OS and JVM
Agenda Reading and Writing lambdas. How many objects does this create? Capturing vs non-capturing lambdas. How do you transform imperative code into the Stream API? Mixing Imperative & Functional Q & A.
Reading and Writing lambdas Lambdas use type inference. This means the context alters the type of the expression used. It even alters the implied type of a lambda. In Java we are used to types being very verbose, but with lambdas we can get more magic.
Puzzler: Summing a field Imperative Style. double sum = 0.0; for (Trade t : getResults()) sum += t.getRequirement(); Functional Style double sum = getResults().stream() .mapToDouble(Trade::getRequirement) .sum(); But what about summing BigDecimal?
Summing a field with BigDecimal Reduce has three parts. The starting value e.g. empty list, 0 for add or 1 for multiplying. How to accumulate each input with the previous value. How to accumulate those results. BigDecimal sum = getResults().parallelStream() .reduce(BigDecimal.ZERO, // starting point for each thread But what about summing BigDecimal? (bd, t) -> bd.add(t.getRequirement()), // in each thread. BigDecimal::add); // combine the results from each thread.
Summing a field with BigDecimal (simplified) Even though you can use parallel thread easily, you probably shouldn t. Also try not to do too much in each step. BigDecimal sum = getResults().stream() .map(Trade::getRequirement) .reduce(BigDecimal.ZERO, BigDecimal::add, BigDecimal::add);
Puzzler: lambda factories Lambdas make it easy to create factories. Even factories for constants. IntSupplier num = () -> 1; DoubleSupplier rnd = Math::random; LongSupplier now = System::currentTimeMillis; Supplier<String> status = () -> "Users: " + getUserCount(); Supplier<Map<String,String>> mapSupplier = TreeMap::new; This can be great way to inject code into your library. But can you work out a type for this lambda? greater = a -> b -> -a >- b
Puzzler: lambda factories In this example, a lambda returns a lambda. IntFunction<IntPredicate> lessThan = a -> b -> -a >- b; System.out.println(lessThan.apply(1).test(2)); While you can curry functions this way in Java, it s more likely to be confusing than useful. Where ever I have used this I have ended up refactoring this out.
Puzzler: Type magic ExecutorService es = Executors.newCachedThreadPool(); es.submit(() -> { // Unhandled Exception: java.lang.IOException Files.lines(Paths.get("data.text")).forEach(System.out::println); }); } ExecutorService es = Executors.newCachedThreadPool(); es.submit(() -> { // No error Files.lines(Paths.get("data.text")).forEach(System.out::println); return null; }); Why does adding return null; suppress an error about a checked exception?
Puzzler: Type magic ExecutorService es = Executors.newCachedThreadPool(); es.submit(() -> { // Unhandled Exception: java.lang.IOException Files.lines(Paths.get("data.text")).forEach(System.out::println); }); } ExecutorService es = Executors.newCachedThreadPool(); es.submit(() -> { // No error Files.lines(Paths.get("data.text")).forEach(System.out::println); return null; }); Why does adding return null; suppress an error about a checked exception?
Anti Puzzler: How many object does this create? A common question is; How many Objects/Strings does the following program create? This sort of question has limited value as Your JVM is likely to create far more object than you imagine possible the first time. You JVM can optimise your code and create far less objects than you might imagine once it is optimised.
Anti Puzzler: How many object does this create? Let us consider this simple program and see how many objects it creates. public class HowManyObjects { public static void main(String... args) throws IOException { IntStream.of(1, 2, 3, 4) .forEach(System.out::println); // so we can get a heap dump. System.in.read(); } } a) 20 objects b) 200 objects c) 2,000 objects d) 20,000 objects
jmap -histo 4600 | head -20 num #instances #bytes class name ---------------------------------------------- 1: 1328 1623296 [I 2: 7016 578904 [C 3: 803 279424 [B 4: 2538 142128 jdk.internal.org.objectweb.asm.Item 5: 4684 112416 java.lang.String 6: 147 82800 [Ljdk.internal.org.objectweb.asm.Item; 7: 692 79024 java.lang.Class 8: 1249 58976 [Ljava.lang.Object; 9: 1290 39768 [Ljava.lang.Class; 10: 776 31040 java.lang.invoke.MethodType 11: 493 27608 java.lang.invoke.MemberName 12: 776 24832 java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry 13: 106 23744 jdk.internal.org.objectweb.asm.MethodWriter 14: 676 16224 java.lang.StringBuilder 15: 167 14696 java.lang.reflect.Method 16: 209 13376 jdk.internal.org.objectweb.asm.Label 17: 405 12960 jdk.internal.org.objectweb.asm.Type 18: 74 12432 jdk.internal.org.objectweb.asm.ClassWriter 19: 211 11816 jdk.internal.org.objectweb.asm.AnnotationWriter 20: 465 11160 jdk.internal.org.objectweb.asm.ByteVector 21: 446 10704 java.lang.StringBuffer
How do Lambdas help? Lambdas are like anonymous inner classes, however they are assigned to static variables if they don t capture anything. public static Runnable helloWorld() { return () -> System.out.println("Hello World"); } public static Consumer<String> printMe() { // may create a new object each time = Garbage. return System.out::println; } public static Consumer<String> printMe2() { return x -> System.out.println(x); }
How does Java 8 help? Lambdas When you call new on an anonymous inner classes, a new object is always created. Non capturing lambdas can be cached. Runnable r1 = helloWorld(); Runnable r2 = helloWorld(); System.out.println(r1 == r2); // prints true Consumer<String> c1 = printMe(); Consumer<String> c2 = printMe(); System.out.println(c1 == c2); // prints false Consumer<String> c3 = printMe2(); Consumer<String> c4 = printMe2(); System.out.println(c3 == c4); // prints true
Serialization Lambdas capture less scope. This means it doesn t capture this unless it has to, but it can capture things you don t expect. Lambdas capture less scope. If you use this.a the value of a is copied. Note: if you use the :: notation, it will capture the left operand if it is a variable.
interface SerializableConsumer<T> extends Consumer<T>, Serializable { } // throws java.io.NotSerializableException: java.io.PrintStream public SerializableConsumer<String> printMe() { return System.out::println; } public SerializableConsumer<String> printMe2() { return x -> System.out.println(x); } public SerializableConsumer<String> printMe3() { // throws java.io.NotSerializableException: A return new SerializableConsumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; }
Why Serialize a Lambda? Lambdas are designed to reduce boiler plate, and when you have a distributed system, they can be a powerful addition. public static long incrby(MapView<String, Long> map, String key, long toAdd) { return map.syncUpdateKey(key, v -> v + toAdd, v -> v); } The two lambdas are serialized on the client to be executed on the server. This example is from the RedisEmulator in Chronicle-Engine.
Why Serialize a Lambda? The lambda m-> { is serialized and executed on the server. public static Set<String> keys(MapView<String, ?> map, String pattern) { return map.applyTo(m -> { Pattern compile = Pattern.compile(pattern); return m.keySet().stream() .filter(k -> compile.matcher(k).matches()) .collect(Collectors.toSet()); }); }
Why Serialize a Lambda? The filter/map lambdas are serialized. The subscribe lambda is executed asynchronously on the client. // print userId which have a usageCounter > 10 // each time it is incremented (asynchronously) userMap.entrySet().query() .filter(e -> e.getValue().usageCounter > 10) .map(e -> e.getKey()) .subscribe(System.out::println);
Escape Analysis Escape Analysis can - Determine an object doesn t escape a method so it can be placed on the stack. - Determine an object doesn t escape a method so it doesn t need to be synchronized.
Escape Analysis Escape Analysis works with inlining. After inlining, the JIT can see all the places an object is used. If it doesn t escape the method it doesn t need to be created and can be unpacked on the stack. This works for class objects, but not arrays currently. After unpacking on to the stack the object might be optimised away. E.g. say all the fields are set using local variables anyway.
Escape Analysis As of Java 8 update 60, the JITed code generated is still not as efficient as code written to no need these optimisation, however the JIT is getting closer to optimal.
How does Java 8 help? Escape Analysis To parameters which control in-lining and the maximum method size for performing escape analysis are -XX:MaxBCEAEstimateSize=150 -XX:FreqInlineSize=325 For our software I favour -XX:MaxBCEAEstimateSize=450 -XX:FreqInlineSize=425
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Puzzler: What type is this? greater = a -> b -> a > b
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Puzzler: What type is this? IntFunction<IntPredicate> greater = a -> b -> a > b; System.out.println(greater.apply(1).test(2)); // false
A low latency API which uses Lambdas Chronicle Wire is a single API which supports multiple formats. You decide what data you want to read/write and independently you can chose the format. E.g. YAML, JSON, Binary YAML, XML. Using lambdas helped to simplify the API.
A low latency API which uses Lambdas Wire Format Bytes 99.9 %tile 99.99 %tile 99.999 %tile worst JSONWire 100* 3.11 5.56 10.6 36.9 Jackson 100 4.95 8.3 1,400 1,500 Jackson + Chronicle-Bytes 100* 2.87 10.1 1,300 1,400 BSON 96 19.8 1,430 1,400 1,600 BSON + Chronicle-Bytes 96* 7.47 15.1 1,400 11,600 BOON Json 100 20.7 32.5 11,000 69,000 Timings are in micro-seconds with JMH. "price":1234,"longInt":1234567890,"smallInt":123,"flag":true,"text":"Hello World!","side":"Sell" * Data was read/written to native memory.
A resizable buffer and a Wire format // Bytes which wraps a ByteBuffer which is resized as needed. Bytes<ByteBuffer> bytes = Bytes.elasticByteBuffer(); // YAML based wire format Wire wire = new TextWire(bytes); // or a binary YAML based wire format Bytes<ByteBuffer> bytes2 = Bytes.elasticByteBuffer(); Wire wire2 = new BinaryWire(bytes2); // or just data, no meta data. Bytes<ByteBuffer> bytes3 = Bytes.elasticByteBuffer(); Wire wire3 = new RawWire(bytes3);
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Low latency API using Lambdas (Wire) To write a message wire.write(() -> "message").text(message) .write(() -> "number").int64(number) .write(() -> "timeUnit").asEnum(timeUnit) .write(() -> "price").float64(price); To read a message wire.read(() -> "message").text(this, (o, s) -> o.message = s) .read(() -> "number").int64(this, (o, i) -> o.number = i) .read(() -> "timeUnit").asEnum(TimeUnit.class, this, (o, e) -> o.timeUnit = e) .read(() -> "price").float64(this, (o, d) -> o.price = d);
message: Hello World number: 1234567890 code: SECONDS price: 10.5 A resizable buffer and a Wire format In the YAML based TextWire message: Hello World number: 1234567890 code: SECONDS price: 10.5 Binary YAML Wire 00000000 C7 6D 65 73 73 61 67 65 EB 48 65 6C 6C 6F 20 57 message Hello W 00000010 6F 72 6C 64 C6 6E 75 6D 62 65 72 A3 D2 02 96 49 orld num ber I 00000020 C4 63 6F 64 65 E7 53 45 43 4F 4E 44 53 C5 70 72 code SE CONDS pr 00000030 69 63 65 90 00 00 28 41 ice (A
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Lambdas and Junit tests To read the data wire.read(() -> "message").text(this, (o, s) -> o.message = s) .read(() -> "number").int64(this, (o, i) -> o.number = i) .read(() -> "timeUnit").asEnum(TimeUnit.class, this, (o, e) -> o.timeUnit = e) .read(() -> "price").float64(this, (o, d) -> o.price = d); To check the data without a data structure wire.read(() -> "message").text("Hello World", Assert::assertEquals) .read(() -> "number").int64(1234567890L, Assert::assertEquals) .read(() -> "timeUnit").asEnum(TimeUnit.class, TimeUnit.SECONDS,Assert::assertEquals) .read(() -> "price").float64(10.5, (o, d) -> assertEquals(o, d, 0));
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Interchanging Enums and Lambdas Enums and lambdas can both implement an interface. Wherever you have used a non capturing lambda you can also use an enum. enum Field implements WireKey { message, number, timeUnit, price; } @Override public void writeMarshallable(WireOut wire) { wire.write(Field.message).text(message) .write(Field.number).int64(number) .write(Field.timeUnit).asEnum(timeUnit) .write(Field.price).float64(price); }
message: Hello World number: 1234567890 code: SECONDS price: 10.5 When to use Enums Enums have a number of benefits. They are easier to debug. The serialize much more efficiently. Its easier to manage a class of pre-defined enums to implement your code, than lambdas which could be any where Under https://github.com/OpenHFT/Chronicle-Engine search for MapFunction and MapUpdater
message: Hello World number: 1234567890 code: SECONDS price: 10.5 When to use Lambdas Lambdas have a number of benefits. They are simpler to write They support generics better They can capture values.
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Anonymous inner classes to lambdas Use your IDE. I can transform from IntFunction<IntPredicate> greater = new IntFunction<IntPredicate>() { @Override public IntPredicate apply(int a) { return new IntPredicate() { @Override public boolean test(int b) { return a > b; } }; } }; to IntFunction<IntPredicate> greater = a -> b -> a > b;
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Where can I try this out? The source for these micro-benchmarks are test are available https://github.com/OpenHFT/Chronicle-Wire Chronicle Engine with live subscriptions https://github.com/OpenHFT/Chronicle-Engine
Mixing Imperative and Functional code Imperative Style. double sum = 0.0; for (Trade t : getResults()) sum += t.getRequirement(); Functional Style double sum = getResults().stream() .mapToDouble(Trade::getRequirement) .sum();
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Mixing Imperative and Functional code Reasons to not mix imperative and functional code. - Oracle says you should do it. - Most developers of functional languages will tell you not to do it. - When we reviewed cases where code was confusion or possibly in error, most of the time this was due to mixing imperative and functional coding. list.stream() .filter(x -> x.isBad()) .forEach(list.remove());
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Mixing Imperative and Functional code Reasons you will end up doing this anyway. - Java has no language support for pure function, nor are they planned. - There is no code inspection tool I know of to help you inforce this. - There are times when using pure functional code is just far more complicated, possibly less efficient. In short, avoid mixing styles if you can, and expecially avoid mixing styles by accident.
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Mixing Imperative and Functional code Reasons you will end up doing this anyway. - Java has no language support for identifying pure function or enforcing pure functional coding, nor is any planned. - There is no code inspection tool I know of to help you inforce this, though there probably should be and will be. - There are times when using pure functional code is just far more complicated, possibly less efficient. In short, avoid mixing styles if you can, and expecially avoid mixing styles by accident.
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Puzzler: compute two map.computeIfAbsent(Key.Hello, s -> { map.computeIfAbsent(Key.Hello, t -> 1); return 2; }); enum Key {Hello}
message: Hello World number: 1234567890 code: SECONDS price: 10.5 Puzzler: compute two HashMap: {Hello=2} WeakHashMap: {Hello=2} TreeMap: {Hello=2} IdentityHashMap: {Hello=2} EnumMap: {Hello=2} Hashtable: {Hello=2, Hello=1} LinkedHashMap: {Hello=1, Hello=2} ConcurrentSkipListMap: {Hello=1} ConcurrentHashMap: (Never returns)
Q & A Peter Lawrey @ChronicleUG http://chronicle.software @PeterLawrey http://vanillajava.blogspot.com IntStream.range(0, 128).parallel().forEach(System::exit);