Last active
January 17, 2019 12:09
-
-
Save Sudip7/148e089e0679189d5db4337b4d461255 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
What's lambda expression ? | |
* Lambda experession is java's way introducing functional programming into the language. | |
* Lambda removes the bulkiness inner class with more compact solution. | |
This simple horizontal solution solves the "vertical problem" presented by inner classes. | |
* Lambdas treat functionality as a method argument or code as data. | |
* Lambda works with functional interface i.e. an interface with single abstract method(SAM) | |
* Typical structure any lambda experession | |
(int x, int y) -> x + y // takes two argument, returns x+y | |
() -> 42 // takes no argument and returns an integer | |
(String s) -> { System.out.println(s); } // takes a String argument and returns nothing | |
* Some common use cases like Runnable, Comparator | |
** Runnable | |
Runnable r2 = () -> {System.out.println("hey java8");} | |
** Comparator | |
Comparing person by name | |
Collections.sort(persionList, (p1,p2) -> p1.getName().compareTo(p2.getName())) | |
How lambda works in java? | |
* pre-read to understand underlying concepts | |
** https://blog.takipi.com/compiling-lambda-expressions-scala-vs-java-8/ | |
** https://www.javaworld.com/article/2860079/learn-java/invokedynamic-101.html | |
** https://www.infoq.com/articles/Invokedynamic-Javas-secret-weapon | |
** https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood | |
* Default methods | |
** what's the default method? | |
** Default methods enable us to add new functionalities to interfaces without breaking the classes that implements that interface. | |
** Introduced to provide backward compatibility so that existing interface can use lambda expression without implementing the new methods in implementation class. | |
** Also known as *virtual extension method*. | |
** A class can implement two or more interface having same default method implementation which will result in compile time error. | |
This can be fixed either by implementing the default method in the class or calling the default method of a specific interface explicitely. | |
Please find the example below | |
https://blog.idrsolutions.com/2015/01/java-8-default-methods-explained-5-minutes/ | |
* Static method | |
** java 8 extend the static method behaviour to interface. | |
** Similar to static behaviour these methods can't be overriden in the implementation classess. It can be called using the interface name. | |
* Date and Time API | |
* Why we need new date and time API? | |
https://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html | |
** Existing classes (such as java.util.Date and SimpleDateFormatter) aren’t thread-safe, leading to potential concurrency issues for users | |
** Some of the date and time classes also exhibit quite poor API design. For example, years in java.util.Date start at 1900, | |
months start at 1, and days start at 0—not very intuitive. | |
** In order to address these problems and provide better support in the JDK core, a new date and time API has been designed for Java SE 8. | |
** https://dzone.com/articles/introducing-new-date-and-time | |
** https://www.geeksforgeeks.org/new-date-time-api-java8/ | |
** Java 8 comes with a new date-time API under the package java.time.(JSR -310) | |
** The new API is *thread safe*. | |
[source,java] | |
// Current Date | |
LocalDate today = LocalDate.now(); | |
System.out.println("Current Date = " + today); | |
LocalDate todayNewYork = LocalDate.now(ZoneId.of("America/New_York")); | |
System.out.println("Current Date in America = " + todayNewYork); | |
// Current Time | |
LocalTime time = LocalTime.now(); | |
System.out.println("Current Time = " + time); | |
LocalTime timeNewYork = LocalTime.now(ZoneId.of("America/New_York")); | |
System.out.println("Current Time in America = " + timeNewYork); | |
// Current Date | |
LocalDateTime now = LocalDateTime.now(); | |
System.out.println("Current DateTime = " + now); | |
System.out.println(now.format(DateTimeFormatter.ofPattern("dd/MM/YYYY HH:mm:ss"))); | |
* why didn’t Java 8 adopt Joda as its native time framework? | |
** https://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html | |
* Stream API | |
https://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html | |
https://stackify.com/streams-guide-java-8/ | |
https://www.baeldung.com/java-8-streams | |
** A sequence of elements from a source that supports aggregate operations. Streams are wrappers around a data source, allowing us to operate with that | |
data source and making bulk processing convenient and fast. | |
** A stream does not store data and, in that sense, is not a data structure. It also never modifies the underlying data source. | |
* *Streams Versus Collections* | |
** In a nutshell, collections are about data and streams are about computations. | |
** Collection is an in-memory data structure, which holds all the values that the data structure currently has—every element in the collection | |
has to be computed before it can be added to the collection. In contrast, a stream is a conceptually fixed data structure in which | |
elements are computed on demand. | |
** Collection interface requires iteration to be done by the user called external iteration(i.e. forEach, for loop) | |
In contrast, the Streams library uses internal iteration—it does the iteration for you and takes care of storing the resulting stream value somewhere. | |
[source,java] | |
List<String> transactionIds = new ArrayList<>(); | |
for(Transaction t: transactions){ | |
transactionIds.add(t.getId()); | |
} | |
Vs | |
List<Integer> transactionIds = | |
transactions.stream() | |
.map(Transaction::getId) | |
.collect(toList()); | |
** *Creating Streams* | |
** There are many ways to create a stream instance of different sources. | |
** The instance *will not modify its source*, therefore allowing the creation of multiple instances from a single source. | |
** Below are some of way how streams can be created from various data sources. | |
** Stream need to be consumed using terminal operator otherwise it may lead to memory leak in application. | |
[source,java] | |
// creating empty stream | |
Stream<String> emptyStream = Stream.empty(); | |
System.out.println(emptyStream); | |
//Stream of collection | |
Collection<String> collectionList = Arrays.asList("a","b","c"); | |
Stream<String> streamOfList = collectionList.parallelStream(); | |
//Stream of array | |
Stream<String> streamArr = Stream.of("a","b","c"); | |
//using builder() | |
Stream<String> streamBuilder = Stream.<String>builder() | |
.add("a") | |
.add("b") | |
.add("c") | |
.build(); | |
// to generate infinite streams | |
Stream<String> generatedStrm = Stream.generate(() -> "element").limit(5); | |
System.out.println("infinite stream with limit "+generatedStrm.collect(Collectors.toList())); | |
// infinite stream using iterate() | |
Stream<Integer> iterateStream = Stream.iterate(40, n -> n+2).limit(7); | |
System.out.println("infinity stream using iterate() "+ iterateStream.count()); | |
//creating primitive streams | |
IntStream intStream = IntStream.range(1,3); | |
System.out.println("creating primitive intstream "+ intStream); | |
LongStream longStream = LongStream.rangeClosed(1, 3); | |
System.out.println("creating long stream "+longStream); | |
// creating double stream | |
Random random = new Random(); | |
DoubleStream doubleStream = random.doubles(3); | |
System.out.println("printing double stream "+doubleStream); | |
// Stream of files | |
Path path = Paths.get("C:\\files.txt"); | |
// each line of text will be elements of stream | |
try { | |
Stream<String> streamOfStringWithCharset = Files.lines(path, Charset.forName("UTF-8")); | |
} catch (IOException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
// java 8 stream can be reused | |
Stream<String> stream = | |
Stream.of("a", "b", "c").filter(element -> element.contains("b")); | |
Optional<String> anyElement = stream.findAny(); | |
// any attempt to reuse the above stream reference will result runtime exception : IllegalStateException | |
Optional<String> firstElement = stream.findFirst(); // runtime exception IllegalStateException | |
** *Intermediate operators* are lazy in nature only execute if there is any terminal operator available in stream pipeline. | |
** Also order of intermediate operation also important : *intermediate operations which reduce the size of the stream should be placed | |
before operations which are applying to each element*. So, keep such methods as *skip(), filter(), distinct() at the top* of your stream pipeline. | |
** *Stream reduction:* | |
*** Reduction operation are part of Stream terminal operators. | |
*** Stream also provides provision for custom implementation of reduction mechanism in the form of *reduce()* and *collect()* methods | |
*** There are three variation of reduce() method : *identity, accumulator, combiner* | |
[source,java] | |
// identity: initial value for an accumulator or a default value if a stream is empty and there is nothing to accumulate | |
OptionalInt reduced = IntStream.range(1,4).reduce((a,b) -> a+b) // reduced = 6(1+2+3) | |
// accumulator : specifies the logic for aggregation of elements. create a new value for every step of reducing | |
// not good for perfomance | |
int reducedTwoParam = IntStream.range(1,4).reduce(10,(a,b) -> a+b); // reducedTwoParam = 16(10+(1+2+3)) | |
// combiner : Aggregates result of the accumulator. | |
// called only in parallel mode to reduce result of accumulation from different threads. | |
int reducedParallel = Array.asList(1,2,3).parallelStream().reduce(10,(a,b) -> a+b, (a,b) -> { log.info("combiner was called"); return a+b ;}); | |
*Note* : Here the reduction works by the following algorithm: accumulator ran three times by adding every element of the stream to identity to every element of the stream. | |
These actions are being done in parallel. As a result, they have (10 + 1 = 11; 10 + 2 = 12; 10 + 3 = 13;). | |
Now combiner can merge these three results. It needs two iterations for that (12 + 13 = 25; 25 + 11 = 36). | |
[source,java] | |
// the reduce() method | |
OptionalInt reduced = IntStream.range(1,4) | |
.reduce((a,b) -> a+b); | |
System.out.println("reduced identity "+ reduced); | |
int reducedTwoParams = IntStream.range(1, 4) | |
.reduce(10, (a,b) -> a+b); | |
System.out.println("reduced accumulator "+ reducedTwoParams); | |
// combiner won't work without parallel stream | |
int reducedStream = Stream.of(1,2,3) | |
.reduce(10, (a,b) -> a+b, (a,b) -> { | |
System.out.println("combiner called");; | |
return a+b; | |
}); | |
System.out.println("testing combiner in sequential stream "+reducedStream); | |
// testing combiner using paraller stream | |
int reducedParallelStream = Arrays.asList(1,2,3).parallelStream() | |
.reduce(10, (a,b) -> a+b, (a,b) -> { | |
System.out.println("parallel combiner called "); | |
return a+b; | |
}); | |
System.out.println("testing combiner in paraller stream "+ reducedParallelStream); | |
*** The *collect()* | |
*** It accepts an argument of type collector, which specifies the reduction mechanism. | |
*** Predefined collectors for most common types can be accessed with the help of *Collectors* type. | |
[source,java] | |
// the collect() | |
List<Product> prodList = Arrays.asList(new Product(21,"patatos"), | |
new Product(34,"oranges"),new Product(45,"eggs"), new Product(99,"milk")); | |
// converting stream to collection(list/set) | |
List<String> collectorList = prodList.stream().map(Product::getName).collect(Collectors.toList()); | |
System.out.println("converting stream to list "+ collectorList); | |
// reducing to String | |
String listToString = prodList.stream().map(Product::getName).collect(Collectors.joining(",", "[", "]")); | |
System.out.println("converting stream to string "+ listToString); | |
// processing average value of numeric value of Stream | |
double avgProdVal = prodList.stream().collect(Collectors.averagingInt(Product::getPrice)); | |
System.out.println("average product value "+ avgProdVal); | |
// processing sumation of all integer value in stream | |
int allProdPrice = prodList.stream().collect(Collectors.summingInt(Product::getPrice)); | |
System.out.println("summation of all prod value "+allProdPrice); | |
// collecting statistical info about Stream's elements | |
IntSummaryStatistics statistics = prodList.stream().collect(Collectors.summarizingInt(Product::getPrice)); | |
System.out.println("Statistical info of a stream "+ statistics); | |
// Grouping stream's elements according to specified function | |
Map<Integer,List<Product>> groupByPrice = prodList.stream().collect(Collectors.groupingBy(Product::getPrice)); | |
System.out.println("grouping by prod price "+ groupByPrice); | |
// above stream was reduced to the Map which groups all products by their price. | |
** *Parallel Stream* | |
*** Java 8 introduced a way of accomplishing parallelism in a functional style through parallel stream. | |
*** If source of stream is different than Collection or an array, the parallel() method should be used. | |
*** Under the hood Stream autometically uses the *ForkJoin* framework to execute operation in parallel using common thread pool. | |
*** In parallel mode blocking operation should be avoided and all the tasks need similar time to execute. | |
*** Stream in parallel mode can be converted back to sequential mode using *sequential()* method. | |
[source,java] | |
Stream<Product> streamOfCollection = prodList.parallelStream(); | |
System.out.println("is collection parallel ? "+streamOfCollection.isParallel()); | |
boolean costlyProd = streamOfCollection.map(prod -> prod.getPrice() * 12).anyMatch(price -> price > 200); | |
System.out.println("costly prod available "+costlyProd); | |
** *Parallel Stream Vs Sequential stream* | |
*** https://www.logicbig.com/tutorials/core-java-tutorial/java-util-stream/sequential-vs-parallel.html | |
*** https://dzone.com/articles/think-twice-using-java-8 | |
** *Compare And Swap* | |
*** It's a technique used for designing concurrent algorithms. Basically CAS compares an expected value to the concrete value of a variable, | |
and if concrete value of variable is equal to expected value, swap the value of variable for new variable. | |
*** CAS is intended to support *check and act* pattern. This pattern occurs when code first checks value of variable and | |
then acts based on value of variable. | |
[source,java] | |
// the lock() method first checks value of variable locked is equal to false(check) | |
// then set value of variable to true(act) | |
class MyLock { | |
private boolean locked = false; | |
public boolean lock() { | |
if(!locked) { | |
locked = true; | |
return true; | |
} | |
return false; | |
} | |
} | |
*** To work properly in multithreaded env, "check and act" must operations must be *atomic* i.e. can be executed in atomic(non-divisible) | |
block of code. No two threads can access atomic block at same time. | |
*** One of the way to achieve atomic behavour is *synchronized* keyword. | |
*** The other way to achieve same is using java 5 atomic classes | |
[source,java] | |
// the compareAndSet() returns true if value was swapped else false. | |
public static class MyLock { | |
private AtomicBoolean locked = new AtomicBoolean(false); | |
public boolean lock() { | |
return locked.compareAndSet(false, true); | |
} | |
} | |
** *Concurrent Accumulators:* | |
*** http://fahdshariff.blogspot.com/2016/09/java-8-concurrency-longadder-and.html | |
*** | |
** *Bit Shifting operators* | |
*** Bit shift operators do exactly what their name implies. | |
*** >>(arithmetic i.e. signed right shift operator), >>>(logical i.e. unsigned right shift operator, <<(works both for logical and arithmetic shift) | |
*** All there operators can be applied to integer values(int, long, possibly short and byte or char) | |
*** *Left shift(<<)* : | |
**** Shifting left is equal to multiplication by power of 2. | |
**** Any number of shift of left results in filling the position by 0. | |
**** These shifts are *non-circular* meaning digits that get shifted "off the end"(left most position) is lost. It doesn't not wrap around. | |
*** *Logical right shift(>>>):* | |
**** Here bits are moved to right which is equivalant to division by power of 2. All shifted values are filled with 0. | |
**** Here too a shift can't reclaim the lost value, once value shifted to right most bit place, that bit is lost forever. | |
*** *Arithmetic right shift(>>):* | |
**** Arithmetic right shift is similar to logical right shift except instead of padding with 0, it pads the shifted bit with most | |
significant bit(bit present in top left position) | |
**** By padding with most significant bit right shift perserves the sign value of number. Negetive/positive value doesn't change the sign after shift. | |
more java 8 reading : | |
https://www.opencodez.com/java/java-8-using-examples.htm | |
https://stackify.com/streams-guide-java-8/ | |
https://www.infoworld.com/article/3113752/java/the-top-5-java-8-features-for-developers.html | |
https://blog.takipi.com/5-features-in-java-8-that-will-change-how-you-code/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment