BY
Deepak Mali
1
RIGI PUBLICATION All right reserved No part of this book may be reproduced in any form, by Photostat, Microfilm, xerography, or any other means or incorporated into any information retrieval system, Electronic or Mechanical, Without the Written Permission of the copyright owner.
Versatile Java By
Deepak Mali Copyright Deepak Mali 2016 Originally published in India Published by RIGI PUBLICATION
777, Street no.9, Krishna Nagar Khanna-141401 (Punjab), India Website: www.rigipublication.com Email:
[email protected] Phone: +91-9357710014, +91-9465468291
2
PREFACE Working in the technology field for around a decade, I gained significant experience working with market leaders such as IBM, SAP, and Oracle. Recently I started to work in the Investment Banking Domain managing large scale projects and ensuring a smooth delivery. Throughout the last decade, I have seen Java evolving like anything. From the early days of Java 1.4 to the increased focus on Machine Learning, Big Data Analytics, Functional Programming,
Concurrency,
Microservices,
Modularity,
Cryptography, and the list goes on. I have made an attempt to accumulate my learnings in the diverse or rather versatile Java Technology areas at one place. I hope this will be a ready reference for professionals working on these technologies. This book would‘nt have seen the light of the day without the blessings of my parents Prof. S.L. Mali and Mrs. Sita and a constant support and encouragement of my wife Mrs. Shikha who is herself a computer enthusiast and helped me a lot with the proofreading and corrections to make the book better. My sisters Dr. Sunita, Er. Vinita and Dr. Chetna have been my constant source of inspiration throughout the journey. I also pay my warm regards to Rigi Publication for their cooperation in bringing up this book at a fast pace. 3
I will be happy to learn about any errors which could have crept in by mistake and suggestions for further improvements of the book .My email ID is
[email protected] Thanks and happy reading...
4
TABLE OF CONTENTS 1.
Functional Programming using lambda expressions
6
2.
Concurrency in Java
22
3.
Network Programming using java
52
4.
Microservices Using Spring Cloud
95
5.
Big Data Technology (Hadoop)
128
6.
Crypto- Algorithms using Java
164
7.
Modularity in Java
185
8.
Machine Learning using Spark
213
9.
Genetic Algorithms using Java
282
10.
Natural Language Processing
325
11.
Performance Enhancements in Java
340
5
CHAPTER 1 - FUNCTIONAL PROGRAMMING USING LAMBDA EXPRESSIONS Getting Started From the inception of Java to recent times, there were thoughts around the functional programming aspect of Java .This has become more evident in the recent times with the introduction of lambda expressions .Functional Programming helps us achieve an abstraction over the behavior. Needless to say, the idea brings many goodies in the bag in form of writing logic which is more readable to get the business intent behind the program, rather than writing tons of boiler plate code to define the mechanics behind the same. Functional Programming is more about writing clean code to solve a specific business domain problem statement in terms of immutable values and functions. To get a flavor of Lambda expressions let‘s take an example of a Class implementing Runnable interface in the below snippet. classTest_Thread implements Runnable { @Override public void run() { // TODO Auto-generated method stub System.out.println("Hello There"); } }
6
A thread invoking the above Runnable target can be spawned using the below line of Program. Thread t = new Thread (new Test_Thread()); t.start(); Console Output is: Hello There This is an example where we try to send code as data .However; we have ended up writing a lot of boiler plate code. Additionally, the intent here was to pass on the behavior which the thread would execute.A much more concise and intent – conveying way would be one where we could pass on behavior in a precise way – Here Lambda Expressions come to our rescue! The above program could be modified as below: Thread t = new Thread (() ->System.out.println ("Hello Lambda Runnable")); t.start(); Console Output is: Hello Lambda Runnable Highlighted is the Lambda expression implementing Runnable .It takes no arguments and its return type is void. Here, the implementation can also be a block of code as per the below: Thread t = new Thread(() -> { System.out.println("Hello Lambda"); System.out.println("Runnable"); }); 7
Console Output is: Hello Lambda Runnable
Let‘s take another example of a Swing based program to understand the concept further. In the below code snippet, an event listener is registered on a button. button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("button clicked"); } }); Here, the anonymous inner class, Action Listener provides implementation of the single methodof interface, actionPerformed. Instead of passing the object implementing the interface ActionListener, we could pass the function to the event listener in the form of Lambda Expression. button.addActionListener(event clicked"));
->System.out.println("button
The event is the same parameter as passed to the actionPerformed method and is separated by the body of the expression –the resultant of the event trigger which is to print ―button clicked‖ on the console. Lambda expressions can also be used to represent methods .For example - BinaryOperator is a functional interface (we will look
8
into functional interfaces later) defined to represent an add method. BinaryOperator add = (x, y) -> x + y; We will explore later that functional interfaces have functional methods .For Binary Operator, the functional method is apply(Object, Object).What this means is ,in case we need to add two integers 4 and 5 , we can invoke the add operation here by writing add.apply (4,5). The type of the arguments is inferred by the compiler in the above case, however at times we need to explicitly define the types as below: BinaryOperator add = (Long u,Long v )->u+v; final or effectively final variables Variables used in Lambda Expressions should either be marked explicitly final or they are effectively final. Thus, in case we assign a variable from a POJO getter call and utilize the variable in the lambda expression we have written earlier, the value assigned to the variable gets printed to the console .However, in case we try reassigning the variable ‗name‘ between lines (1) and (2) , the compiler will not allow –saying ―Local variable name defined in an enclosing scope must be final or effectively final‖ String name = getName();
(1)
button.addActionListener(event->System.out.println(name)); (2) Here, the variable, although not declared final explicitly still behaves as final or in other words it is ―effectively final‖ 9
Since the unassigned variables are in a way closed by the lambda expressions over the surrounding state, they are at times referred to as closures as well. Functional Interfaces Functional interfaces are the interfaces with a single (abstract) method and we can create an instance of a functional interface using lambda expressions. It does not matter what is the exact name of the method, it will be matched with the lambda expression as long as the signature is compatible. Functional interfaces can act as assignment targets for lambda expressions. The below table lists the more frequently used functional interfaces bundled in JDK. (T - the type of the input to the function result of the function)
R - the type of the
Functional Interface
Functional method
@FunctionalInterface
accept(Object)
public interface Consumer It accepts a single input argument and returns no result @FunctionalInterface
test(Object)
public interface Predicate Represents a predicate (boolean-valued function) of one argument.
10
@FunctionalInterface
apply(Object)
public interface Function Represents a function that accepts one argument and produces a result @FunctionalInterface
get()
public interface Supplier Represents results
a
supplier
of
@FunctionalInterface public UnaryOperator
Function.apply(Object) interface
extends Function Represents an operation on a single operand that produces a result of the same type as its operand. @FunctionalInterface
BiFunction.apply(Object, Object)
public interface BinaryOperator extends BiFunction Represents an operation upon two operands of the same 11
type, producing a result of the same type as the operands
Type inference in lambdas Let‘s take an example of Predicate functional interface which checks if the expression is true or not. Predicate isGreaterThan10 = x -> x > 10; The Predicate has a single generic type Integer, thus the variable used in the body of lambda is inferred by the compiler to be an Integer. Here the base of compiler inferring the type is the generic to which the Predicate is tied to .In case we omit this information, the compiler will have tough time indentifying the types .The compiler will consider the operands to be of the base type i.e. java.lang.Object , resulting in an error message ―The operator + is undefined for the argument type(s) java.lang.Object, java.lang.Object‖.
The below snippet demonstrates the compiler
error situation. BinaryOperator add = (x,y)->x+y; Internal and external iteration – usage of streams Until now, the way we used to iterate over the collection is to make use of the iterator method .In case there are many occurrences of iterating over the collection, not only we end up writing a lot of boiler plate code but the way it executes is serial in nature. A snippet for external iteration is shown below where we try to iterate over the ArrayList of trades .For simplicity, each trade has two attributes – trade reference and trade region. The intent of the below code is to get the count of all the trades which have region as ―EMEA‖. 12
ArrayList trades = new ArrayList(); trades.add(new Trade(100, "EMEA")); trades.add(new Trade(101, "Americas")); trades.add(new Trade(102, "APAC")); trades.add(new Trade(103, "EMEA")); trades.add(new Trade(104, "EMEA")); trades.add(new Trade(105, "APAC")); trades.add(new Trade(106, "EMEA")); long count = 0; String trade_region = null; Iteratortrades_iterator = trades.iterator(); while (trades_iterator.hasNext()) { trade_region = trades_iterator.next().getTrade_region(); if (trade_region.equals("EMEA")) count++; } When we print the count on the console, it should give us 4 as there are 4 trades with ―EMEA‖ as the region.As you have correctly noticed, its also tough to predict the behavioral operation looking at the code.A better and more efficient way of achieving the same objective would be to use streams. Streams are used for collection processing adhering to the functional 13
programming paradigm.Thus, the above code can be modified as below long l = trades.stream().filter(trade >trade.isTradeRegion("EMEA"))
-
.count(); Here, the filter method returns us the filtered stream. Count counts the number of elements in the filtered stream. Another notable difference is that the filter method does not return a new value at the end – it just returns a filtered stream sequence .Such methods are called as lazy method .But the count method does return as final value and hence termed as eager methods. The preferred way is to have a chain of lazy operations with an eager operation at the end of the chain. This reduces the number of times we have to iterate over the collection. A few of uses of eager and terminal operations are given below: collect(toList()) Listtrades_collected=Stream.of("Trade1","Trade2","Tr ade3"). collect(Collectors.toList()); The of() is the factory method to return the sequential stream and collect performs a mutable reduction using Collector. Collectors is the implementation of Collector utilities. map map is applied over a stream sequence to transform give stream to a stream containing new values .Since there is an input to the stream and a result from the stream, the lambda expression used here is an instance of Function functional interface . 14
Listtrades_mapped=Stream.of("trade1","trade2","trade 3").map(String->String.toUpperCase()).collect(Collectors.toList()); The mapped stream is [TRADE1, TRADE2, TRADE3]. Additionally, flatMaplets us replace value with stream and combines the streams together. max and min are used to find the max and in element of the stream using comparing method of Comparator. reduce operation is used when we want a collection of values upon we need to perform an operation defined by the lambda expression yielding the final result. At the end, the stack_up variable contains the result. intreduce_operation_value = Stream.of(1, 2, 3) .reduce(0, (stack_up, element) ->stack_up + element); Console Output: 6 As a thump rule, always write the stream based code in a chained fashion to avoid creating intermediate collection objects. This will also help taking the benefits of API to automatically parallelize the operations. In the above examples, we have noticed that a function is either used an an argument or as a return type-such functions are also at times referred as higher order functions. Method References We can make a method reference using Classname::methodname .We have not used the parenthesis here since this is the equivalent of the method of the lambda
15
expression .We can also use the abbreviated notation for new Object in the following way . TreeSet trades_collected_tree_Set = Stream.of("xyz","pqr","abc").collect(Collectors.toCollection(Tr eeSet::new)); trades.stream().collect(Collectors.averagingLong(Trade::getTra saction_amount)); System.out.println(reduce_operation_value); Console Output :[abc, pqr, xyz] 6
Element Ordering When we create a Stream using a defined order from a collection , the Stream has a defined encounter order .If there is no defined order to begin , the Stream produced by that source has no defined order .Certain operations on the Stream creates an encounter order like Sort operations . // encounter order is maintained Listnumbers=asList(1,2,3,4); ListsameOrder=numbers.stream().collect(toList()); // encounter order is different Setnumbers=newHashSet(asList(4,3,2,1)); ListsameOrder=numbers.stream() .collect(toList()); 16
// sorting ensures encounter order Set numbers = new HashSet(asList(4, 3, 2, 1)); List sameOrder = numbers.stream() .sorted() .collect(toList()); At times post the Stream operations we want to produce Collection as a final value .This may be required when we need to get a TreeSet as a resultant collection .It is also possible to collect into a single value from the collector . TreeSet trades_collected_tree_Set = Stream.of("xyz","pqr","abc").collect(Collectors.toCollection(Tr eeSet::new)); Console Output :[abc, pqr, xyz] // Fetching the average transaction amount of the trades trades.stream().collect(Collectors.averagingLong(Trade::getTra saction_amount)); We can also group the data which the Stream has given us using the groupingBy() method . // the below code group the Stream data based on the Trader. trades.stream().collect(Collectors.groupingBy(Trade::getTrader )); We can build up String using Streams using joining() method as follows.
17
String result=Stream.of("Trade1","Trade2","Trade3").collect(Collecto rs.joining(",","[","]")); Console Output: [Trade1,Trade2,Trade3] Static methods on Interfaces We‘ve seen a lot of calling of Stream.of but haven‘t gotten into its details yet. You may recall that Stream is an interface, but this is a static method on an interface. This is another new language change that has made its way into Java 8, primarily in order to help library developers, but with benefits for day-today application developers as well. Optional is a new core library data type that is designed to provide a better alternative to null. Below is the snippet demonstrating the usage of Optional Optional a = Optional.of("a"); System.out.println(a.get()); Optional b =Optional.empty(); System.out.println(b.isPresent()); Optional c = Optional.ofNullable(null); if(c.isPresent()) System.out.println(c.get()); Console Output : a false
18
Parallel Stream Operations Making an operation execute in parallel using the streams library is a matter of changing a single method call. If you already have a Stream object, then you can call its parallel method in order to make it parallel. If you‘re creating a Stream from a Collection, you can call the parallelStream method in order to create a parallel stream from the get-go.The following Snippet gives us a good idea about the same . publicstaticvoid main (String args[]) { ArrayListlist_of_trades ArrayList();
=
new
for(inti=0;i { for (Integer i =1 ; i { Integer j = 0; do { try { j=blocking_queue.take(); System.out.println (j + " Consumed By Consumer"); } catch (InterruptedException e) { e.printStackTrace(); } } while (j != 100); service.shutdown(); }; service.execute(consumer); } } Completion Service 45
A completion service is an implementation of the java.util.concurrent.CompletionService interface that decouples the production of new asynchronous tasks (a producer) from the consumption of the results of completed tasks (a consumer). V is the type of a task result. A producer submits a task for execution (via a worker thread) by calling one of the submit() methods: one method accepts a callable argument and the other method accepts a runnable argument along with a result to return upon task completion. Each method returns a Future instance that represents the pending completion of the task. You can then call a poll() method to poll for the task‘s completion or call the blocking take() method. A consumer takes a completed task by calling the take() method. This method blocks until a task has completed. It then returns a Future object that represents the completed task. You would call Future‘s get() method to obtain this result. Along with CompletionService, Java 7 introduced the java.util.concurrent.ExecutorCompletionService class t o support task execution via a provided executor. This class ensures that, when submitted tasks are complete, they are placed on a queue that‘s accessible to take ().A snippet for completion service is below. package com.java8.tutorial.threads; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; 46
import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CompletionServiceTest { public static void main (String args []) throws InterruptedException, ExecutionException { ExecutorService es = Executors.newFixedThreadPool(10); CompletionService cs = new ExecutorCompletionService(es); cs.submit(new CalculateE(17)); cs.submit(new CalculateE(170)); Future result = cs.take(); System.out.println(result.get()); System.out.println(); result = cs.take(); 47
System.out.println(result.get()); es.shutdown(); } } class CalculateE implements Callable { final int lastIter; public CalculateE(int lastIter) { this.lastIter = lastIter; } @Override public BigDecimal call() { MathContext mc RoundingMode.HALF_UP);
=
new
MathContext(100,
BigDecimal result = BigDecimal.ZERO; for (int i = 0; i t.toUpperCase()).map(l >l.split(";")) 50
.collect(Collectors.toList()); Iteratoriter = records.iterator(); while (iter.hasNext()){ String[] str=(String[]) iter.next(); for(inti=0;i