Java 8 language capabilities, What's in it for you? The notes
I just watch pretty good talk about Java 8 features and I made the notes. Sharing them with you.
It is preatty old Youtube video recorded at SpringOne2GX 2014.
Speaker: Venkat Subramaniam. Talk: Java 8 Language Capabilities, What's in it for you?
Here you can read notes quickly. And I suggest to watching the interesting parts.
I made all the examples distinct and noted main things.
The code presented here is for reading. If you want to play around it, you can download it from the speaker's webpage here.
Example 1. Lambdas
Starting point. Lets have a List to iterate through.
List<Integer> values = Arrays.asList(1,2,3,4,5,6); for (int i = 0; i < values.size(); i++) { System.out.println()values.get(i); }
Lets make is simpler:
for(int e : values) { System.out.println(e) }
These loops are called external iterators. The iteration is controlled outside manually.
You can do the same with internal iterators. Delegating the responsibility to the language itself.
values.forEach(new Consumer<Integer>() { // java.util.function.Consumer; public void accept(Integer value) { System.out.println(value) }; })
The example above is called Anonymous inner class.
Until now there was no new Java 8 syntax involved. Lets finally simplify it to use Lambda expression.
values.forEach((Integer value) -> System.out.println(value));
Type definition is not needed here. Java 8 can detect it.
values.forEach((value) -> System.out.println(value));
Since we use only one parameter we can remove the parentheses
values.forEach(value -> System.out.println(value));
If we just need to pass the same value to the function without any modification we can do a simple passthrough:
values.forEach(System.out::println);
Reasons to use Lambda expressions
- Less code to write
- Improved code readability
- Possibly better-compiled code optimizations
Fun fact
Lambdas are optimized at Jit level. The same repeated Lambda function is one function in the compiled code. Reference - invokedynamic
.
That type of reference was added in Java 7 for other languages as the act of kindness.
In Java 8 this because as first citizen feature used for Lambdas.
Example 2. Multiply the values in the List and get the total
List<Integer> values = Arrays.asList(1,2,3,4,5,6); int total = 0; for(int e: values) { total += e * 2; } System.out.println(total);
Imperative code. Smells:
- do mutation.
total
variable in each iteration.
System.out.println( values.stream() .map(e -> e * 2) .reduce(0, (c, e) -> c + e));
Declarative code.
- code becomes more expressive.
- no mutations allows us to parallelize if we choose to.
Functional interfaces here:
- Map accepts one parameter
- Map reduce accepts two parameters.
c
first time is 0, second time becomes the result of the first call and etc...e
is the iterated element.
Example 3. Default methods
import java.util.*; interface Fly { default public void takeOff() { System.out.println("Fly::takeoff"); }; default public void turn() { System.out.println("Fly::takeoff"); }; default public void cruise() { System.out.println("Fly::cruise"); }; default public void land() { System.out.println("Fly::land"); }; } interface FactFly extends Fly { default public void takeOff() { System.out.println("FastFly::takeoff"); }; } class Vehicle { public void land() { System.out.println("Vehicle::land"); } } interface Sail { default public void cruise() { System.out.println("Sail::cruise"); } } class SeaPlane extends Vehicle implement FastFly, Sail { public void cruise() { System.out.println("SeaPlane::cruise"); // Use super in order to access parent default method, // not the static interface method. FastFly.super.cruise(); } } public class Sample { public void use( SeaPlane seaplane = new SeaPlane(); seaPlane.takeOff(); // FastFly::takeoff seaPlane.turn(); // seaPlane.cruise(); // seaPlane.land(); // Vehicle::land ); public static void main(String[] args) { new Sample().use(); } }
Default methods rules
- Default methods gets inherited automatically
- You can override the default methods
- The Method has a class hierarchy rule. See
seaPlane.land(); // Vehicle::land
- If there are method collision implementing several interfaces, it is developer problem to resolve it. See
class SeaPlane
.
Example 4. Predicates
Let's have an example when you need to:
- sum values
- sum even values
- sum odd values
The usual way it would look like:
import java.util.*; public static int totalValues(List<Integer> numbers){ int total = 0; for(int e : numbers) { total += e; } return total; } public static int totalEvenValues(List<Integer> numbers){ int total = 0; for(int e : numbers) { if (e % 2 == 0) total += e; } return total; } public static int totalOddValues(List<Integer> numbers){ int total = 0; for(int e : numbers) { if (e % 2 != 0) total += e; } return total; } public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,4,5,6); System.out.println(totalValues(values)); System.out.println(totalEvenValues(values)); System.out.println(totalOddValues(values)); } }
Lets add Predicate:
import java.util.*; import java.util.function.Predicate; public static int totalValues(List<Integer> numbers, Predicate<Integer> selector){ int total = 0; for(int e : numbers) { if(selector.test(e)) total += e; } return total; } public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,4,5,6); System.out.println(totalValues(values, e -> true)); System.out.println(totalValues(values, e -> e % 2 == 0)); System.out.println(totalValues(values, e -> e % 2 != 0)); } }
This example uses Strategy pattern and Predicates to solve the problem. This way we don't need all 3 functions doing very similar stuff. And the code is extensible.
Lets improve the code more with Lambdas
import java.util.*; import java.util.function.Predicate; public static int totalValues(List<Integer> numbers, Predicate<Integer> selector){ return values.stream() .filter(selector) .reduce(0, (c, e) -> (c + e) } public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,4,5,6); System.out.println(totalValues(values, e -> true)); System.out.println(totalValues(values, e -> e % 2 == 0)); System.out.println(totalValues(values, e -> e % 2 != 0)); } }
Now it became even more readable.
Example 5. The double of the first even number in the list
Imperative code:
import java.util.*; public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,5,4,6,7,8,9,10); int result = 0; for(int e: values) { if(e > 3 && e % 2 == 0) { result = e * 2; break; } } System.out.println(result); } }
Declarative code:
import java.util.*; public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,5,4,6,7,8,9,10); System.out.println( values.stream() .filter(e -> e > 3) .filter(e -> e % 3) .map(e -> e * 2) .findFirst()); } }
The improved version returns Optional object. Since it actually can fail finding a match.
Lets add default value:
import java.util.*; public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,5,4,6,7,8,9,10); System.out.println( values.stream() .filter(e -> e > 3) .filter(e -> e % 3) .map(e -> e * 2) .findFirst()) .orElse(0); } }
Here we use function orElse
to set the default value, if the needed values are not found.
For readability we can name everything:
import java.util.*; public class Sample { public static void main(String[] args) { List<Integer> values = Arrays.asList(1,2,3,5,4,6,7,8,9,10); System.out.println( values.stream() .filter(Sample::isGT3) .filter(Sample::isEven) .map(Sample::doubleIt) .findFirst()) .orElse(0); } public static boolean isEven(int number) { System.out.println("isEven for " + number); return number % 2 == 0; } public static boolean isGT3(int number) { System.out.println("isGT3 for " + number); return number > 3; } public static int doubleIt(int number) { System.out.println("doubleIt for " + number); return number * 2; } }
This way code becomes much more readable.
Good to know
- functions like
findFirst
are called as Terminal functions. They trigger the computation. - functions like
map
andfilter
they are intermediate functions. They are lazy. They try not to perform anything until terminal functions make them.
So the declarative code is:
- more beautiful
- performs as fast as the imperative code.
Example 6. Streams. Highest stock price less than $500
Imperative code example.
import java.util.*; import java.util.function.Predicate; public class Sample { public static StockInfo dummy = new StockInfo("", 0.0); public static void findImperative(List<String> symbols){ List<StockInfo< stocks = new ArrayList<>(); for(String symbol : symbols) { stocks.add(StockUtil.getPrice(symbol)) } List<StockInfo> stocksLessThan500 = new ArrayList<>(); for(StockInfo stocks : stock) { if(StockUtil.isPriceLessThan(500).test(stock)) { stocksLessThan500.add(stock); } } StockInfo highPriced = dummy; for(StockInfo stock: stocksLessThan500) { highPriced = StockUtil.pickHigh(highPriced, stock) } System.out.println(highPriced); } public static void main(String[] args) { Timeit.code(() -> findImperative(Tickers.symbols)) } } public class StockInfo { public final String ticker; public final double price; public StockInfo(final String symbol, final double thePrice) { ticker = symbol; price = thePrice; } public String toString() { return String.format("ticker: %s price: %g", ticker, price); } } public class StockUtil { public static StockInfo getPrice(final String ticker) { return new StockInfo(ticker, YahooFinance.getPrice(ticker, false)); } public static Predicate<StockInfo> isPriceLessThan(final double price) { return stockInfo -> stockInfo.price < price } public static StockInfo pickHigh( final StockInfo stockInfo1, final StockInfo2) { return stockInfo1.price > stockInfo2.price ? stockInfo1 : stockInfo2; } }
In the video, the example above did run 24 seconds. And about 20 lines of code :)
Lets change Sample class to have Functional code style.
import java.util.*; import java.util.stream.Stream; public class Sample { public static StockInfo dummy = new StockInfo("", 0.0); public static void findFunctional(Stream<String> symbols){ System.out.println( symbols.map(StockUtil::getPrice) .filter(StockUtil.isPriceLessThan(500)) .reduce(dummy, StockUtil::pickHigh)); }; public static void main(String[] args) { Timeit.code(() -> findFunctional(Tickers.symbols.stream())) } }
Execute time is about the same. No performance benefits.
Lets make this code concurrent. Use Tickers.symbols.parallelStream()
.
It will be based on the amount of the CPU cores.
Object oriented languages encapsulating the moving parts. In the functional programming languages, you are eliminating moving parts.
Hope these notes are at least somehow useful for you.