User Tools

Site Tools


programming:java:java8

Java8

New features:

  • Methods as first class citizens
  • Lambdas
    • Passing code to methods (behavior parametrization)
  • Default methods in Interfaces
  • Streams
  • Nashorn
  • New Date API
  • Optional

Glossar

Predicateboolean function, which takes some input
Functional interface Interface to pass behavior

Literature

New Concepts

Behaviour parametrization

Typing

Which type do new Java8 expressions have?

		
		// Function. Gets Ranking. Returns Integer.
		Function<Ranking, Integer> f = ranking -> ranking.value;
		
		// Consumer - accepts sinle parameter. Returns nothing
		Consumer<Ranking> c = Ranking:: echo; 
		
		// Predicate - boolean returning function
		Predicate<Ranking> isLarger2 = ranking -> ranking.value > 2;




	// MODELS
	
	static class Ranking{
		int value;

		public static void echo(Object value){
			System.out.println(String.format("Value is '%s'",value));
		}
	}

Behaviour parametrization

You can pass a method as a function-parameter. For that the method-signature must define a functional interface as a paramter.

  • functional Interfaces are located in package java.util.function
    • e.g. Consumer<A>
    • Biconsumer<A,B> …
Functional Interface

An interface with exactly one single abstract method.
The interface may have many additional default methods.
Overriden abstract methods of Object.class - are not included in abstract interfacew, which count Some functional inerfaces

Comparator is a functional interface
Comparator is a functional interface, since it has two abstract methods:

  • equals
  • compare

and equals is a method from Object.class.

Object is NOT a functional interface
It does not have a single abstract method.
Lambdas can't be assigned to Object variables

Object o = () -> {return 2;} // invalid Object in not a functional interface
@FunctionalInterface

Functional Interfaces may be annotated with @FunctionalInterface to clarify what they are!

Exisiting generic functional interfaces

The generic functions have following names:

..Consumer provides a method with input. No output
..Supplier provides output. No input.
..Function provides method with input. And with output.
..Predicate provides a boolean function
..Operation method with input and output of same type
Behaviour parametrization examples

class Test{
public static void main(String[] args){

// passing myMethod, which may be used inside fun
Test.function(Test::myMethod);

}


// As defined by class FunctionalInterface - you can pass any function in here, 
// which takes String as parameter AND
// which returns boolean
public void function(FunctionalInterface interface){
}

static interface FunctionalInterface{
 boolean function(String parameter); // defines a single function operating on String, returning boolean
}

static class MyClass{
 static boolean myMethod(String input){
  return true;
 }

}

Lambdas

Glossar
Identifier

Identifiers are used as names variables, functions. Identifiert

abcd123
Literals

Notations for constant values, build in types

123.33
Operators
  • Mathematical operators,
  • call operators ()
  • array operator []
  • increment operator ++

The full operator list is here: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/opsummary.html

Not operators:

print 
return
Expression

It contains:

  • identifiers,
  • literals,
  • operators

Expressions:

"String is an expression too"
"Alan" + i
System.out.print("ho")
Statement
  • Statements are made up of expressions.
  • Form a complete unit of execution
  • End with ;

More here: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/expressions.html

Statements:

System.out.print("ho");
return "Alan" + i;
Lamda Syntax

1.

(parameters) -> expression

2.

(parameters) -> { statements; }

Lamda Examples:

() -> {} // valid 1
() -> "Raoul" // valid 1
() -> {return "Mario";} // valid 1
(Integer i) -> return "Alan" + i; // invalid. Valid version:  (Integer i) -> {return "Alan" + i;}
(String s) -> {"Iron Man";} // invalid. Valid version:  (String s) -> {return "Iron Man";} 

More valid examples:

(TYPE name) -> FUNCTION(name)
(String s) -> s.toUpperCase() + " !!!"
(Apple a) -> a.getWeigth() > 150

Type inference

Compiler can deduce the input type from the functional interface.
So you can omit the type in lambdas.

legal usages of lamdas:

List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor())); // a has no type. It becomes an Apple automagically
Method reference

Methods can be references, to use them as lambdas:

Integer::parseInt // local method on class
Apple::getWeight // static method on class
new Apple()::getWeight // on instance
Constructor reference
Supplier<Apple> c1 = Apple::new; // supplier takes no pramas, creates Apple. COnstructor is a supplier
Apple a1 = c1.get(); // produce an apple by calling a suplier

Streams

Summary

Streams introduce internal collection navigation where you don't need to.
As a contra to extrnal navigation, where you explicitely iterate a collection.

Streams describe computations of Data sets,
whereas collections describe the storage and access.

Operations on Streams are parallelized automagically, when using parallelStream()

inventory.stream().filter...  // not parallelized
inventory.parallelStream().filter... // parallelized
Methods

The stream methods are separated into:

intermediate

returns a stream. Which allows to execute the next operation, as defined in chain pattern.

 .stream().filter(...).distinct().limit(3)... 
terminal

operation which returns a non string. FInalizes the chain of stream commands.

 
.stream()...count();
.stream()...collect(toList());
.stream()....forEach(System.out::println);

Intermesiate methods

method argument result mapping
filter T→ boolean Stream<T>
map T → R Stream<R>
limit int Stream<T>
skip int Stream<T>
sorted T,T - int Stream<T>
distinct Stream<T>
flatMap Stream<T[]> → Stream<T>

terminal methods

method argument result
forEach
count int
collect
allMatch boolean
anyMatch boolean
noneMatch boolean
findFirst Optional<T>
findAny Optional<T>
reduceT, T → T Optional<T>
single pass

All the stream-methods chained one after another are executed in a single, internal iteration.

When you apply a sequence of operations to a stream, these are merely saved up. Only when you apply a terminal operation to a stream is anything actually computed. This has the great advantage when you apply several operations (perhaps a filter and a map followed by a terminal operation reduce) to a stream, then the stream has to be traversed only once instead of for each operation.

THe following code would produce the floowing output:

code

        dishes.stream().filter(a -> {
            System.out.println(a.name);
            return true;
        }).distinct().map(a -> {
            System.out.println(a.type);
            return a.name;
        });

output

pork // name
flesh  // type
pumkin // name
vegetables // type

Name and type come one after another or each element.

This means that steps during parallel execution - methods that can block the parallel execution should not be executed inside the Stream-pipeline.

Assume you have two versions of getting prices from futures:

// directly chaining map methods to create Future, get Result is wrong. Because f.get() will be called after every future creation and block further Future creation (Single Pass)
stream.map(Helper::createFuture).map((Future f) -> g.get()).asList()

// collecting futures into collection first is the right parallel way. When we do the first "get" all Futures already exist and computing
List<Future> futures = stream.map(Helper::toFuture).asList();
List<Integer> prices = futures.asStream().map((Future f) -> g.get())

On the Image the upper Situation is depicted

Filling the Stream

		// filling the stream with content using "iterate"
		Stream.iterate(0l, i -> i + 1)
				.limit(100);

Collect and Partition methods - the better reduce

There are plently of collectors, which are availalbe via java.util.stream.Collectors. Just import all static collector methods and vars to use them by name.

import static java.util.stream.Collectors.*;

Glossar

multilevel reduction groupingBy collector creates substreams, for different grouping criteria. Substreams may be transfered/grouped by another collector, called a downstream collector
downstream collector collector, to transfer substreams (substreams created by groupingBy collector)
reduce-accumulator method B, A → B , which converts an STREAM-ENTRY of type A to B. It is applied on EVERY Stream Element. It is passed the accumulated reduce value B so far and the next stream value A. It returns the value, resulting from accumulation of the next stream-element A
reduce-combiner when stream is reduced in a parallel way - combiner will combine every result, accumulated on different Streams.
Method Returns Describtion Example
toList List<T> Gather all the stream’s items in a List.
 List<Dish> dishes = menuStream.collect(toList()); 
toSet Set<T> Gather all the stream’s items in a Set, eliminating duplicates.
 Set<Dish> dishes = menuStream.collect(toSet()); 
toCollection Collection<T> Gather all the stream’s items in the collection created by the provided supplier.
 Collection<Dish> dishes = menuStream.collect(toCollection(), ArrayList::new) 
counting Long
 long howManyDishes = menuStream.collect(counting()); 
summingInt Integer
 int totalCalories = menuStream.collect(summingInt(Dish::getCalories));  
averagingInt Double
 double avgCalories = menuStream.collect(averagingInt(Dish::getCalories)); 
summarizingInt IntSummary-Statistics Collect statistics regarding an Integer property of the items in the stream, such as the maximum, minimum, total, and average.
 IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories));  
joining String
 String shortMenu = menuStream.map(Dish::getName).collect(joining(", ")); 
maxBy / minBy
 Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories))); 
reducing The type produced by the reduction operation Reduce the stream to a single value starting from an initial value used as accumulator and iteratively combining it with each item of the stream using a BinaryOperator.
 int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));  
collectingAndThen The type returned by the transforming function
 int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));   
groupingBy Map<K, List<T» Maps the stream to groups (as hashmap keys)
 Map<Dish.Type, List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType)); 
partitioningBy Map<Boolean, List<T»
 Map<Boolean, List<Dish>> vegetarianDishes =menuStream.collect(partitioningBy(Dish::isVegetarian)); 
Collector Interface - implementing own collectors
	
		/* COLLECTOR
		 * 
		 * Collector<T,A,R>
		 * 
		 *  T - Type of input item in stream
		 *  A - intermediate Type of Objects, during calculation
		 *  R - Result Type
		 *  
		 *  e.g. Collector which collects Stream of Strings to List 
		 *  T - String
		 *  A - List<String>
		 *  R - List<String>
		 *  
		 *  Supplier<A> supplier() - provides initial value of intermediate Type
		 *  BiConsumer<A,T> accumulator() -  input -> the A value calculated so far. T is the next value in stream
		 *  Function<A,R> finisher() - function is executed after iteration over all elements is done
		 *  BinaryOperator<A> combiner() - function which combined two intermediate Objects. This one makes multithreading possible
		 *  Set<Characteristics> characteristics - some optimization hints. UNORDERED, CONCURRENT, IDENTITY_FINISH
		 *  
		 *  
		 *  supplier - ArrayList::new
		 *  accumulator - (List<String> a, String b) -> {a.add(b)}
		 *  finisher - Functions::identity
		 *  combiner - (List<String> a, List<String> b) -> {a.addAll(b)}
		 *  characteristics - return Collections.unmodifyableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT ))
		 */
Parallelism

Boxed Values are a performace killer. Using a Stream with primitives - improves performance by factor x6

		long max = 90000000;

// LongStream uses primitives (long)
		start = System.currentTimeMillis();
		summ = LongStream.iterate(0l, i -> i + 1)
		.limit(max)
		.reduce(0l, Long::sum);
		System.out.println("Sequential LongStream Ready filling after: "+ (System.currentTimeMillis()-start) +"ms");
		// 220ms

// Stream generates Boxed Values (Long)
		start = System.currentTimeMillis();
		summ = Stream.iterate(0l, i -> i + 1)
		.limit(max)
		.reduce(0l, Long::sum);
		System.out.println("Sequential Stream Ready filling after: "+ (System.currentTimeMillis()-start) +"ms");
		// 1228 ms

Reducing DIshes to Strings in a parallel way:

		// parallel streams of Dishes, reduced to String, which is a concatenated list of names
		String res = getExampleStream().parallel().reduce("", 
				new BiFunction<String, Dish, String>() { 
			@Override
			public String apply(String t, Dish u) {
				return t+u.name;
			} //accumulator - can convert the Stream content to something else (String)

		}, 
			(a,b) -> {
				return a+b;
			} // combiner - used in the case of using parallel streams
		);

Default methods in Interfaces

It is possible to implement default methods in interfaces.
So multiple inheritance is some sort of allowed now in java!!!

The default interface methods are marked by keyword default

default void methodName(){...

	public class Pazak implements HasDefaultMethodKu, HasDefaultMethodKuToo{
		
		// have to override method #ku() to explicetly say which interface's method to execute 
		@Override
		public void ku() {
			HasDefaultMethodKu.super.ku();
		}
	}
	
	public interface HasDefaultMethodKu{
		default void ku(){
			System.out.println("Say Ku!");
		}
	}
	
	public interface HasDefaultMethodKuToo{
		default void ku(){
			System.out.println("Say Ku Too!");
		}
	}

Optional<T> class

Return that instead of null to avoid NPE

  • explicitely define where the var may be null

Methods

isPresent
ifPresent(Consumer<T> block)
get value or NoSuchMethod-Exception
T orElse(T other)

public class Optionals {

	String name = "Сапожник козоед";
	
	public static void main(String[] args) {
		Optionals optionals = new Optionals();
		System.out.println(optionals.getName().get());
				
		// add defaults
		System.out.println(optionals.getName().orElse("defaultName"));
		System.out.println(optionals.getEmptyName().orElse("defaultName"));
	}
	
	
	Optional<String> getName(){
		return Optional.of(name);
	}
	
	Optional<String> getEmptyName(){
		// explicitly say that this var is nullable
		return Optional.ofNullable(null);
//		return Optional.of(null); //		would produce a NullPointerException
	}

}

CompletableFuture<T> class

Like a future with more functional-style interface.

CompletableFuture<Double> f = CompletableFuture.supplyAsync(() -> calculatePrice());

occured exceptions rethrown on get()

The occured Exceptions are rethrown on get()


			CompletableFuture<Double> f = ...

			try {
				double calculatedPrice = f.get();
				System.out.println(String.format("Everything was fine. Price calculated: "+calculatedPrice));
				return calculatedPrice;
				
			} catch (Exception e1) {
				System.out.println(String.format("I know - an Exception occured earlier on another thread: %s.", e1.getCause()));
				return (double) 0;
			}

Chainable

The CompletableFuture can be chained, to execute some code when the future has completed the computation.

CompletableFuture<Integer> c = null;
CompletableFuture<String> c2 = null;

CompletableFuture cc = c.thenApply((Integer i) -> "Done");
/* thenApply((FutureType) -> T) -> CompletableFuture<T>
 * Executed, when previous Future is done.
 * Modifies the value "INSIDE" the completableFuture container to T		 */

/* thenRun(() -> Void) -> CompletableFuture<Void>
 * Executes some code (no params are passed), when previous Future is done		 */

/* thenAccept(FutureType -> Void) -> CompletableFuture<Void>
 * Executed, when the previous Future is done
 * VOID will be inside the Future after this call. To preserve some Type T in FUture - use thenApply()
 */

CompletableFuture ccc = c.thenCompose((Integer inte) -> CompletableFuture.runAsync(() -> System.out.println("Done")));
/* thenCompose(FutureType) -> CompletableFuture) -> CompletableFuture
 * Executes a SECOND CompletableFuture, when the first is done    */

// thenCombine
CompletableFuture<Double> aaa = c.thenCombine(c2, (Integer ii, String ss) -> 2.2);
/* thenCombine(CompletableFuture, Function(FutureType1, FutureType2) -> T) -> CompletableFuture<T>
 * Executes FIRST and SECOND ComputableFutures. Produces the results in a Function which itselfe produces a ComputableFunction 
 */

Chaining can be used in streams, to implement real parallel (not only concurrent) evaluation.

						// List<Picture> -> List<Futures<Picture>> 
						(List<Picture> l) -> {
							List<CompletableFuture<Picture>> listFuturesFIlledPrice = l
									.stream()
									
									// Picture -> Futures<Picture>
									// echo result of future when ready
									.map((Picture picturePriceless) -> {
										CompletableFuture<Picture> picFutureWithPrice;
										
										// ASYNC DELAYED: get price
										picFutureWithPrice = CompletableFutures.getFutureFillingPriceInPictureDelayed(picturePriceless);

										// react immediately after calculation is done
										picFutureWithPrice = picFutureWithPrice.thenApply(CompletableFutures::echoPictureWhenFutureReady); 
										return picFutureWithPrice;
										}
									)
									.collect(Collectors.toList());
							return listFuturesFIlledPrice;
						}

Date API

Basic classes

Instant

		// INSTANT - to be used by machines
		Instant instant = Instant.ofEpochSecond(1454523011);
		// nanosecond adjustment - 1sec=1.000.000.000nanosec
		Instant.ofEpochSecond(0); 					// 1.January.1970 UTC
		Instant.ofEpochSecond(2, 1000_000_000); 	// 2sec + 1sec
		Instant.ofEpochSecond(3, -1000_000_000); 	// 4sec - 1sec
		
		
		// Instant -> LocalDate, LocalTime
		LocalDate dateOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate();
		LocalTime timeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalTime();
		

LocalDate

		LocalDate localDateNow = LocalDate.now();
		LocalDate localDateNowLondon = LocalDate.now(ZoneId.of("UTC+0"));
		LocalDate localDate = LocalDate.of(2016, 2, 16);

		int fieldYear = localDate.get(ChronoField.YEAR);

LocalTime

		LocalTime time = LocalTime.of(14, 55, 54, 11 ); // hour, minute, second, milli
		LocalTime time2 = LocalTime.of(14, 55, 54 ); // hour, minute, second
		LocalTime time3 = LocalTime.of(14, 55 ); // hour, minute
		int hourTime = time.getHour();
		int minuteTime = time.getMinute();

LocalDateTime

		// 1415-07-6T6:55:20
		LocalDate dateOne = LocalDate.of(1415, Month.JULY, 6);
		LocalTime timeOne = LocalTime.of(6, 55, 20);
		LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
		LocalDateTime dt2 = LocalDateTime.of(dateOne, timeOne);
		LocalDateTime dt3 = dateOne.atTime(13, 45, 20);
		LocalDateTime dt4 = dateOne.atTime(timeOne);
		LocalDateTime dt5 = timeOne.atDate(dateOne);

ZoneId

		ZoneId romeZone = ZoneId.of("Europe/Rome");
		ZoneId berlinZone = ZoneId.of("Europe/Berlin");
		ZoneId myZone = TimeZone.getDefault().toZoneId();

ZonedDateTime

		ZoneId berlinZone = ZoneId.of("Europe/Berlin");
		
		ZonedDateTime zonedDateTimeBerlin1 = dateOne.atStartOfDay(berlinZone);
		ZonedDateTime zonedDateTimeBerlin2 = localDateTimeOfGuss.atZone(berlinZone);

DateTimeFormatter

		LocalDate parsedDate;
		parsedDate = LocalDate.parse("2014-03-18");
		parsedDate = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_DATE);
		parsedDate = LocalDate.parse("18/03/2014", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
		parsedDate = LocalDate.parse("18.03.2014", DateTimeFormatter.ofPattern("dd.MM.yyyy"));
		
		parsedDate = LocalDate.parse("18 März 2014", DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.GERMAN));
		
		DateTimeFormatter germanFormatter = new DateTimeFormatterBuilder()
				.appendText(ChronoField.DAY_OF_MONTH)   // 6
				.appendLiteral(".")
				.appendValue(ChronoField.MONTH_OF_YEAR) // 7
				.appendLiteral("( alias ")
				.appendText(ChronoField.MONTH_OF_YEAR) // July
				.appendLiteral(")")
				.appendLiteral(".")
				.appendValue(ChronoField.YEAR)			// 1941
				.parseCaseInsensitive()
				.toFormatter(Locale.GERMAN);
		System.out.println(germanFormatter.format(dateOne));

Duration

		/*
		 * Duration is CRAP, because based on Seconds / Nanos.
		 * - you only can compute a duration on "time based units" - something what can handle seconds like LocalTime. 
		 *   The compiler still allows computing Duration between LocalDate, or getting YEARS from Duration,
		 *   which results in an Exception.
		 *   WHY OR WHY CAN'T I JUST CONVERT SECONDS IN ANY UNIT WITHOUT EXCEPTIONS, AS EXPECTED?!
		 */
		// Duration only works with Seconds-supporting types. Using LocaDate - throws an exception
		// Duration durationSinceGuss = Duration.between(dateOne, LocalDate.now());
		Duration durationSinceGuss1 = Duration.between(LocalDateTime.of(dateOne, LocalTime.MIDNIGHT),LocalDateTime.now());
		Duration durationSinceGuss2 = Duration.between(timeOne,LocalTime.now());
//		Duration durationSinceGuss3 = Duration.between(dateOne,LocalDate.now());   		// exception. It trys to convert LocalDate to Seconds, which is not allowed

Period

		Period periodBetweenGuss = Period.between(dateOne, LocalDate.now());
		long yearsSinceGuss7 = periodBetweenGuss.getYears();

TemporalAdjuster

LocalDate dateNextSunay = dateOne.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));

The Time Units are contained by ChronoUnit.class and separated in timeBased and dateBased:

It is FUCKING UNCLEAR, why DAYS ARE NOT TIMEBASED?! Still it is important, some operations are only legal for time- or dateBased units: like getting the amount of years from a Duration.

TimeBased DateBased
Nanos Days
Micros Weeks
Millis Months
Seconds Years
Minutes Decades
Hours Centuries
HalfDays Millennia
Eras

The same separation exists with Classes:

TimeBased DateBased
LocalDateTime LocalDateTime
LocalDate LocalTime
Period Duration

mixing them up produces an UnsupportedTemporalTypeException.

localDate.plus(1, CHronoUnit.SECOND) // UnsupportedTemporalTypeException 
localTime.plus(1, CHronoUnit.DAY)   // UnsupportedTemporalTypeException 

// LocalDateTime supports both
localDateTime.plus(1, ChronoUnit.SECONDS);
localDateTime.plus(1, ChronoUnit.DAYS);

CHronofield - contains diffrent kinds of data info names, which may be requested from diffrent TemporalFields.

		int fieldYear = localDate.get(ChronoField.YEAR);
		/*
                   ChronoField.
		        NanoOfSecond
			NanoOfDay
			MicroOfSecond
			MicroOfDay
			MilliOfSecond
			MilliOfDay
			SecondOfMinute
			SecondOfDay
			MinuteOfHour
			MinuteOfDay
			HourOfAmPm
			ClockHourOfAmPm
			HourOfDay
			ClockHourOfDay
			AmPmOfDay
			DayOfWeek
			AlignedDayOfWeekInMonth
			AlignedDayOfWeekInYear
			DayOfMonth
			DayOfYear
			EpochDay
			AlignedWeekOfMonth
			AlignedWeekOfYear
			MonthOfYear
			ProlepticMonth
			YearOfEra
			Year
			Era
			InstantSeconds
			OffsetSeconds
		 */

Code - Java8 Style

How to write code in Java8?

Lambdas instead of anonymous Classes

You can replace many anonymous classes with method calls. Important:

theme anonymous class lambdas
this means the anonymous class means the wrapping class
overriding vars is allowed is forbidden
typing is unambigous may become ambigous - needs explicit typing



Runnable runnable = new Runnable(){
 void execute(){
  System.out.println("Go!")
 }
}

Runnable runnable = () -> System.out.println("Go!"); // ok

int mVar = 20;
Runnable runnable = () -> {
  int mVar = 30; // not ok - overriding vars not allowed
  System.out.println("Go!");
}


public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ r.execute(); }
doSomething(() -> System.out.println("Go!")); // not ok -  call doSOmething for Task or Runnable?
doSomething((Task)() -> System.out.println("Go!")); // ok

Method Reference with helper static methods

Stream<String> stream = ...
stream.map(String::length);
stream.map(Helpers::quote);

String quote(String str){
 return String.format("'%s'",str);
}

Use native collectors

Native collectors are known by others. Are better readable.

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

Replace loops by streams API

Better readability, optimization behind the scenes.

// collecting names of dishes, which have > 300 calories
		List<String> dishNames = new ArrayList<>();
		for (Dish dish : menu) {
			if (dish.getCalories() > 300) {
				dishNames.add(dish.getName());
			}
		}
// intent is better readable.
menu.parallelStream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.collect(toList());

Deferred conditional execution

// message is evaluated EVERY TIME, even if the level does not fit
logger.log(Level.FINER, "Problem: " + generateDiagnostic());

// to avoid - pass a lambda, which will be called ONLY IF LEVEL FITS
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());

Execute around

// same code, which is executed around some variable logic 
void method1(){
 doPreparation();
 //variable method here
 varMethod2();
 cleanUp();
}
void method2(){
 doPreparation();
 //variable method here
 varMethod2();
 cleanUp();
}


// better
void method(Runnable r){
 doPreparation();
 r.execute();
 cleanUp();
}
method(()->varMethod1());
method(()->varMethod2());

Functional programming style

With Java8 the programming style is pushing towards the functional edge.
Some rules for writing functional code:

Functions must have

Requirenments to functions

have no side Effects

Functions may only change private variables.

have referential transparency

Calling same function always returns the same result.
E.g. readNextLine() is NOT referential transparent. It alwas returns another line.

Do

What to do, when programming:

get Function, return Function

pass functions to methods. Return new functions with functionality around it.

 
Function<String, String> transformationPipeline
= addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);

use Carriying

create FUnctions producing FUnctions,
which will prefill some constants.

 
// carrying
Function createConvertingFunction(double course){
 return (double x) -> course * x;
}

Function eurToChf = createConvertingFunction(0.9);
Function bintcoinToEur = createConvertingFunction(421);

When function reuses another function and
when preselecting some arguments (but not all) have been passed -
then the function is partially applied

Function echoColoredObject(Object object, String color){
 (object, color) -> System.out.println("The "+object+" has the color "+color);
}

// partially apply function echoColoredObject
Function echoColoredBall(String color){
 (color) -> echoColoredObject("Ball", color)
}

Interfaces relevant for Lambdas

If you would like to create a method accepting a lamda use the Argument, typed with one of those methods: java.util.function.IntBinaryOperator.

Examples:


Consumer<Double> c = aDouble -> System.out.println(aDouble);  // no output

Function<Double, String> f = aDouble -> String.valueOf(f);  // function returns what you define
DoubleFunction<String> d = doubleValue -> String.valueOf(doubleValue); // same as previous

IntBinaryOperator plusOperation = (a, b) -> a + b; // operator on two int numbers a and b

LongPredicate l = longValue -> longValue < 0l; // boolean function

Terminal Methods

findAny / findFirst


            List<Integer> list = List.of(1, 2, 3, 4, 9, 8, 7, 6, 1, 2, 3);

            // gives maybe 7, 8 or 9, cause findAny doesnt respect order
            int res = list
                    .stream()
                    .parallel()
                    .filter(integer -> integer > 6)
                    .findAny()
                    .orElseThrow();
                    
            assertThat(res, anyOf(is(7), is(8), is(9)));



            // gives 9, cause its the first in the row match to filter
            int res2 = list
                    .stream()
                    .parallel()
                    .filter(integer -> integer > 6)
                    .findFirst()
                    .orElseThrow();

            assertThat(res, is(9));

reduce

        // cruel method to concat numbers, just to demonstrate reduce
        List<Integer> list = List.of(1, 2, 3, 4, 9, 8, 7, 6, 1, 2);

        Integer res = list
                .stream()
                .mapToInt(value -> value)
                .reduce(0,
                        (integer, integer2) -> {
                            return Integer.parseInt(String.format("%s%s", integer, integer2));
                        }
                );
        System.out.println(res); // 1234987612

        // better way would be with java8
        String res2 = list
                .stream()
                .map(String::valueOf)
                .collect(Collectors.joining());
        System.out.println(res2); // 1234987612

programming/java/java8.txt · Last modified: 2023/11/01 07:31 by skipidar