Saturday, March 25, 2023

Small Programs using Java

1. How to find the minimum value in a list?

Primitive

List<Integer> lst = Arrays.asList(1, 2, 3, 4, 56, 7, 89, 10);

System.out.println(Collections.min(lst));

Object:

List<Employee> lst = new ArrayList<Employee>();

lst.add(new Employee(10, "Raghu", 25000));

lst.add(new Employee(120, "Krish", 45000));

lst.add(new Employee(210, "John", 14000));

Employee minSal = Collections.min(lst, new EmpComp());

System.out.println(minSal);

 

class EmpComp implements Comparator<Employee>{ 

    public int compare(Employee e1, Employee e2) {

        return e1.getSalary().compareTo(e2.getSalary());

    }

}

Stream

lst.stream.min((e1, e2) -> e1.getSalary().compareTo(e2.getSalary())).get();


2. How to get a list sorted?

Primitive

Collections.sort(lst));

Object:

Collections.sort(lst, new EmpComp());

Stream:

lst.stream.sorted((e1, e2) -> e1.getSalary().compareTo(e2.getSalary())).collect(Collectors.toList()); 

Reverse Order:

Collections.sort(lst, Collections.reverseOrder());  

lst.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());


3. How to reverse a String?

Char Array:

Scanner scanner = new Scanner(System.in);

String str = scanner.nextLine();

char[] arr = str.toCharArray();

for (int i = arr.length - 1; i >= 0; i--)

System.out.print(arr[i]);

StringBuilder:

StringBuilder sb = new StringBuilder(str);

sb.reverse().toString();

Arraylist:

List<Character> lst = new ArrayList<>();

for (char c : str)

    lst.add(c);

Collections.reverse(lst);

Stream:

Stream.of(str)

      .map(word->new StringBuilder(word).reverse().toString())

      .collect(Collectors.joining(" ")).get(0);

Stream with index:

char[] arr = str.toCharArray();

IntStream.range(0, arr.length)

        .mapToObj(i -> arr[(arr.length - 1) - i])

        .forEach(System.out::print);

A String is a palindrome if the String is same as the Reversed String.


4. How to search an element?

In String:

if(str.indexOf("word") > -1)

In Arraylist of Primitives:

boolean found = lst.contains(2);

In Arraylist of Objects:

for (Employee emp : emplist) {

    if (emp.getName().equals(name)) {

        return emp;

    }

}

Using Stream:

Employee empl = emplist.stream()

  .filter(emp -> name.equals(emp.getName()))

  .findAny()

  .orElse(null);



5. How to count the occurrences? 

String:

Linear search:

for (int i=0; i<str.length(); i++){

    if (s.charAt(i) == c)

        count++;

    }

return count;

Binary Search:

while (low <= high) {

        mid = (low + high) / 2;

        if (a[mid].compareTo(x) < 0) {

            low = mid + 1;

        } else if (a[mid].compareTo(x) > 0) {

            high = mid - 1;

        } else {

            return mid;

        }

    }

Arraylist:

Collections.frequency(list, new Employee(321222, "John", 29)); 

public boolean equals(Employee e) {

    if(this.id.equals(e.getId()) && this.name.equals(e.getName())) {

        return true;

    }

    return false;

}

Stream:

lst.stream().filter(str -> str.indexOf("ch") > -1).count();

To get count of each item,

lst.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));



6. How to replace an element?

String:

str.replace('h', 'm');

Arraylist

Collections.replaceAll(lst, null, " ");

Stream:

List<String> newList = lst.stream()

                .map(s -> s != null ? s : "")

                .collect(Collectors.toList());


Friday, March 24, 2023

Design Patterns in Java

 Design Patterns provide an industry-standard approach to solving a recurring problem.


1. Creational Design Patterns

They define the way an object is created.

Factory Method: This ensures that the correct object is created according to a parameter received.

 Implementation: A Factory class is created which returns different Child objects based on an input String passed to its method/constructor and using a switch case. 

Eg:  A car company gets the request to create a car. This request is given to the factory which decides how the car should be created based on its model.

Abstract Factory method: This ensures that the correct object is created according to a parameter when there are multiple levels of subclasses.

Implementation: When there are two levels of overriding, an abstract factory class or an interface is used to identify the first level of child class and for identifying the second level, there are separate concrete factory classes. 

Eg: When a car factory handles different companies, it should first decide for which company and then decide which model to be manufactured.

Singleton: This ensures that only one object for a class is created. 

Implementation: A private constructor is created and a public synchronized static getInstance() method to call it. 

Eg: In an apartment, the residents decide that only one borewell should be dug for the entire community. 

Prototype: This ensures that a copy of the existing object is returned when a new object is requested. This is used when an object creation is costly due to some reason (like db calls).

Implementation: A Prototype class is created which implements Cloneable and defines clone method (shallow or deep). This also has a list of predefined objects. So getInstance() method simply clones the predefined objects when called.

Eg: A Xerox machine just takes multiple copies of the same content.

Builder: This ensures that an object is created step by step.

Implementation: Instead of the actual class using its setter methods, a builder class is populated with its setters step by step. Finally this builder object is passed as a parameter to the actual class' constructor to create the actual object. 

Eg: An excel/pdf report is generated after formatting it line by line.


2. Structural Design Patterns

They define the way different types of objects can have a connection.

Adapter: This ensures that a class gets some of the features from another class.

Implementation: If a class cannot use features from another class directly due to its hierarchy, another adapter class is created as a child of both the classes (implements interface of our class, extends other class) and this class' object is created for processing.

Eg: A phone charger has an adapter that converts AC to DC since phone cannot use AC directly from the socket.

Bridge: This ensures that two classes are loosely coupled.

Implementation: Since inheritance creates tight coupling, composition (has-a relationship) is used. A class has another class as its instance variable and methods to operate on that class. This helps in doing operations on both classes without affecting their hierarchies.

Eg: A switch can be used in a house to operate a fan without knowing the technical details of the fan. So switch acts as the bridge here.

Composite: This ensures that a hierarchy is defined in a tree model.

Implementation: An interface can have its implementations which in turn can have multiple child classes.

Eg: Employee hierarchy in a company. Each manager can have employees under him and managers are in turn coming under General Managers or Vice Presidents.

Decorator:  This ensures that the behaviour of all sub classes can get altered without actually modifying them.

Implementation: An interface has some subclasses. Another abstract class implements this interface and adds the extra behaviour. The abstract class has a constructor parameterized with an interface object. This way the extra behaviour can be added to the interface.

Eg: Pizza and its sizes come under one hierarchy. Decorations on the Pizza can be decided from a separate Decorator class.

Facade: This ensures that multiple classes can be accessed from only one central class without knowing their internal details.

Implementation: One Facade class has instance variables of multiple classes and a constructor in which other classes are instantiated.

Example: In a restaurant only the menu card is displayed to the customers hiding the background details.

Flyweight: This ensures that a lot of objects can be created without much memory requirement.

Implementation: This combines factory and prototype design patterns. A factory has a hashmap to save objects. When a new object request comes with its type passed as String, if the key (ie, type) is already present in the hashmap, it returns that object, else it creates new object using a switch case. Flyweight resources are immutable.

Example: String pool which reuses String literals.

Proxy: This ensures that an actual class is accessed only through its proxy.

Implementation: A Proxy class is created whose constructor actually creates objects for the Real class.

Example: UPI is a proxy for a Bank account.


2. Behavioural Design Patterns

They define the way objects talk to each other.

Chain of responsibility: This ensures that different classes are called one after the other. 

Implementation: Either we can call the classes in the order, or we can create objects inside constructor in the order.

Example: Supply chain of products

Iterator: This ensures that objects can be iterated through.

Implementation: An Iterator Class implements Iterator and overrides its next()/hasNext() methods. The actual class has iterator() method to create the object of the Iterator class, using which it can traverse through the lists in the Actual class.

Example: A video playlist

Mediator: This ensures that objects talk to each other only through the Mediator.

Implementation: A Mediator class has instance variables of other classes. The Mediator object is used to work on the other classes

Example: Travel Agencies work as Mediator for different air services.

Observer: This ensures that all Observers are updated when there is a change.

Implementation: A common class has instance variables of all Observer classes. It also has methods to update the Observers whenever its onchange() method is called.

Example: All travelers should be informed when there is a change in schedule.

Strategy: This ensures that different child objects can have different behaviours.

Implementation: An interface can add a behaviour to a particular class. But if it needs its implementation also, then that interface's concrete child is added as instance variable to the required child classes.

Example: An IT team may be looking for the skills Java, .Net etc. from the resources while all resources are part of the Employee hierarchy.




Wednesday, March 22, 2023

Functional Interface in Java

 Any interface with a SAM(Single Abstract Method) is a functional interface. It can have other static or default methods. 

public interface Testable {

    void printName(String name);

}

There are five ways of implementing this Single Abstract Method:

1. Using normal function inside the implementation class

class Child implements Testable{

    void printName(String name) {

        System.out.println("Hi " + name);

    }

}   

new Child().printName("Tester");

2. Using anonymous class to override

Anonymous class can extend or implement only one class/interface.

class Test {

    Testable obj = new Testable() {           

        void printName(String name) {

            System.out.println("Hi " + name);

        }

    };

    obj.printName("Tester");  // Parent object

 

3. Using lambda operator

class Test {

    Testable obj = (name) -> {System.out.println("Hi " + name)

// method name skipped since only one method

    obj.printName("Tester"); 

}   

4. Using method reference

Method reference can be used if lambda just calls one method.

class Test {

    Testable obj = System.out::println // Cannot print Hi // Skipping arguments

    obj.printName("Tester"); 

}

Method reference is of 3 types:

For calling Child's static method,

Testable obj = Child::print;      obj.printName("Tester"); 

For calling Child's non-static method,

Testable obj = new Child()::print;      obj.printName("Tester"); 

For calling Child's constructor,

Testable obj = Child::new;      obj.printName("Tester"); 


5. Using Built-in Functional Interfaces

Built-in Function interfaces are needed if the argument passed and the value returned are different, or if more than one argument needs to be passed.

Function

Function<Student,String> f = s->"Name:"+s.name +" and Age:"+s.age;

String str = f.apply(student) //Student object passed, String returned

BiFunction

BiFunction<Integer, Integer, Integer> bf = Arithmetic::add;  

int result = bf.apply(10, 20);  // Two integers passed, one Integer returned

Consumer

Consumer<String> c = System.out::println;

c.apply("Test"); // String passed, nothing returned.

There are also other variants of the Consumer — DoubleConsumer, IntConsumer, and LongConsumer. 

BiConsumer

Consumer<String,Integer> c = (name,age)->System.out.println("Name is "+name+" Age is "+age);

c.apply("Joe",12); // String and Integer passed, nothing returned.

Predicate

Predicate<String> p = (value) -> value != null;

p.apply("Test"); // String passed, boolean returned.

There are also other variants of the Predicate — DoublePredicate, IntPredicate, and LongPredicate. 

BiPredicate

BiPredicate<Integer,Integer> bp = (age,min)->age>=min;

bp.apply(22,18); // Two Integers passed, boolean returned.

Supplier

Supplier<String> s = ()->System.out.println("Hi");

s.apply("Test"); // Nothing passed, String returned.

There are also other variants of the Supplier — DoubleSupplier, IntSupplier, and LongSupplier. 


Other Built-in Java Functional Interfaces

Runnable –> This interface only contains the run() method.

Comparable –> This interface only contains the compareTo() method which can be overridden as 

Comparator<Book> byAuthor = (b1, b2) -> b1.getAuthor().compareTo(b2.getAuthor());

ActionListener –> This interface only contains the actionPerformed() method.

Callable -> This interface only contains the call() method.

Tuesday, March 21, 2023

Streams in Java

Streams are wrappers around a data source (Collection, array etc.) allowing us to operate on that data source. A stream does not store data and is not a data structure. It also never modifies the underlying data source.

Any operation involving Streams should have three sections - a source, one or more intermediate operations and a terminal operation

 

Source

1. An empty Stream is created as, 

Stream<String> stream = Stream.empty();

2. Any Collection object can be converted to stream using stream() method. 

List<String> lst = Arrays.asList("A", "B", "C", "D");

Stream<String> stream = lst.stream();

Set<String> set = new HashSet<>(list);

Stream<String> strem = set.stream();

3. A Stream can be created from an array in two ways as,

Stream<String> strm = Stream.of("a", "b", "c");

String[] arr = new String[] { "a", "b", "c" };

Stream<String> stream = Arrays.stream(arr);

4. By adding elements individually using the mutable Builder.

Stream.Builder<String> builder = Stream.builder();

Stream<String> stream = builder.add("a").add("b").add("c").build();

5. Create an infinite Stream using Stream.generate() method.

Stream<Double> stream = Stream.generate(Math::random).limit(4);

6. Create an infinite Stream using Stream.iterate()

Stream<Integer> IntegerStream = Stream.iterate(0, n -> n + 1).limit(10);

7. A File can be converted to stream as,

Stream<String> stream = Files.lines(filepath);

8. Using Primitive Streams

IntStream intStream = IntStream.range(1, 3); //1 2

LongStream longStream = LongStream.rangeClosed(1, 3); //1 2 3

9. Stream from a pattern match

Stream<String> stream = Pattern.compile("\\s").splitAsStream("A String");

 

Intermediate Operations

The Streams are further processed by zero or more intermediate methods combined together (pipelined). But actual processing doesn’t start till a terminal operation is invoked. This is called lazy evaluation.

1. filter() to exclude unwanted streams with a condition (known as Predicate)

stream.filter((Employee e)-> e.getAge() > 24)

2. sorted() to arrange in order. A comparator can be passed as parameter.

stream.sorted()

3. distinct() to get non-duplicate elements

stream.distinct()

4. map() to transform the stream using a Function.

stream.map(String::toUpperCase)

5. limit() to restrict the result count

stream.limit(5)

6. peek() to debug every intermediate operation

stream.filter(e -> e.length() > 3).peek(e -> System.out.println("Filtered value: " + e))

 

Terminal Operations

They give the final result. They don't return a stream.

toArray() : to get an array from the stream.

collect() : to convert stream into a Collection. eg: stream.collect(Collectors.toList())

count() : to get the count

reduce() : to get a single result from a sequence. eg: stream().reduce("", String::concat)

forEach() : to do something for each element. eg: stream.forEach(System.out::println)

min() : to get the minimum value based on comparator. Returns an Optional element. 

        eg: Optional<Address> mini = stream.min((i, j) -> i.compareTo(j))

              Address addy = mini.get();

              mini.ispresent(); // False if Optional is empty

max() : to get the maximum value based on comparator. Returns an Optional element.

anyMatch() : True if any element matches, else false. eg: stream.anyMatch(p -> p.getAge() < 20)

allMatch() : True if all elements matche, else false. eg: stream.allMatch(p -> p.getAge() < 20)

noneMatch() : True if no element matches, else false. eg: stream.noneMatch(p -> p.getAge() < 20)

findAny() : Returns Optional with any element.

findFirst() : Returns Optional with the first in order element.

 

Parallel Stream

Parallel Streams are executed parallelly. 

eg: List<Integer> lst = Arrays.asList(1, 2, 3, 4);

int sum = lst.parallelStream().reduce(5, Integer::sum);

Here the result is calculated as (5+1) + (5+2) + (5+3) + (5+4) where as in sequential it would have been 1+2+3+4+15. 

Another way of creating prarallel stream is, stream.parallel().forEach(System.out::println);


Monday, March 20, 2023

Serialization in Java

There are different ways in Java to read data from files and to write data to files.


1. Using Scanner

Scanner can be used to read input from the user. It can read all the primitive data types.

Scanner obj = new Scanner(System.in);

String input = obj.nextLine();


2. Using FileReader

FileReader can be used to read character data from a file. It returns the ASCII value for each character and -1 at the end.

FileReader fr=new FileReader("D:\\test.txt");    

int i;    

while((i=fr.read())!=-1)    

    System.out.print((char)i);    

fr.close();


3. Using FileInputStream

FileInputStream can be used to read raw data (non-character) from a file. It returns a byte stream.

File file = new File("D:\\test.jpg");

FileInputStream in = new FileInputStream(file); // convert image to byte stream

byte[] arr = new byte[(int)(file.length())];

in.read(arr);  // convert byte stream to byte array

in.close();  


Serialization

Combining the features of FileInputStream and ObjectInputStream gives a new possibility. FileInputStream converts raw file data to byte stream and ObjectInputStream converts byte stream to object. Similarly, FileOutputStream converts byte stream to raw file and ObjectOutputStream converts  object to byte stream. This process of converting an object into a stream of bytes is called Serialization. The main purpose is to transmit the object over a network and recreate the object later. Though JSON is the most preferred way of Serialization, Java provides its own default way.

The class needs to implement the Serializable marker interface. All of the fields in the class must be serializable. If a field is not serializable, it must be marked transient. When the class will be de-serialized, the transient variable will be initialized with a default value. The serialVersionUID attribute needs to be added in the class to verify that sender and receiver of the serialized object is the same. 

FileOutputStream out = new FileOutputStream(filePath);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);

objectOutputStream.writeObject(obj); // serialization

FileInputStream in = new FileInputStream(filePath);

ObjectInputStream objectInputStream = new ObjectInputStream(in);

obj = (Obj) objectInputStream.readObject(); //de-serialization


Externalizable is an Interface that extends Serializable Interface and sends data into Streams in Compressed Format.

Static in Java

Static keyword in java means 'belongs to class'. This applies to variables, methods, blocks and nested classes. 

Static variable

Static variables are loaded into the memory as soon as the JVM starts. The value of a static variable remains the same across the instances because a common static memory is allotted to it. They are accessed using Classname. The main purpose of a static variable is memory optimization when a common variable is used.

class Test{

    public static SCHOOL_NAME = "Horizon";

}

System.out.println(Test.SCHOOL_NAME);

The fields in an interface are implicitly public static final. If one object changes the value then the change gets reflected in all the objects. Static variables cannot be declared inside a method.

Static method

Static methods are also loaded when the application starts. They are mostly used as general purpose utility classes. The main() is a static method so that it can be called even before creating an object. A static method cannot be overridden.

public static void do() { }

Static block

Static block can be used to initialize static variables. Static blocks get executed when the class gets loaded into the memory. You cannot access a non-static variable from the static context in Java. If you try, it will give compile time error.

class A {

    static {

        System.out.print("hi");

    }

}

Static inner class

We can not declare top level class as static, but only inner class can be declared static.

public class Outer{

    static class Inner{

        public void my_method(){

            System.out.println("This is my nested class");

        }

    }

    public static void main(String args[]){

        Outer.Inner inner=new Outer.Inner();

        inner.my_method();

    }

}

Friday, March 17, 2023

Multithreading in Java

Thread is a single sequence stream within a process. Each thread uses different stack of memory, but the heap space is shared. In a single processor environment, threads just run concurrently. But in a multi-processor environment, threads are spread across processors, making them run in parallel. 

Thread creation

By default, JVM creates a single thread called main thread. The user can create more Threads to improve the speed of application in three ways:

  • By extending Thread class

class A extends Thread{

    public void run(){ }

}

new A().start();

  • By implementing Runnable

This has an advantage over the previous method that the class can extend another parent class.

class B implements Runnable{

    public void run(){ }

}

new Thread(new B()).start();

  • By using the Executor framework

This uses a Thread pool where threads are already available, saving the time of creating new.

class C implements Runnable{

    public void run(){ }

}

Executors.newFixedThreadPool(3).execute(new C());

Executor can also call a Callable implemented class if a return value is expected.

In this case, submit() is used instead of execute() method.

 

Thread states

A Thread can take the following states:

·       New : A New Thread is just created

·      Runnable : The New Thread is started and running.

·      Waiting : The Thread is waiting for another Thread to finish its execution or sleeping for the specified time.

·      Blocked : The thread tries to acquire lock on object, but some other thread is already holding the lock.

·      Terminated : The Thread finished its execution or got an error.

 

Synchronized 

Multiple Threads may try to access the same resources, causing error. To avoid this, those resources can be locked using synchronization.

synchronized(this) { } // synchronized block with lock on object.

synchronized void do() { } // synchronized method with lock on the entire method.

 

Other important points

  • Every thread has a priority of scheduling which ranges from 1 to 10.
  • Invoking the run() method directly instead of start() will make the execution to run on the current stack itself rather than a new stack.
  • Thread().join() makes other threads to wait for this thread to finish.
  • Daemon Thread provides services to user threads for background supporting tasks. eg: garbage collector
  • The wait() method causes the current thread to wait until another thread invokes the notify(). The notifyAll() method wakes up all the threads that called wait( ) on the same object. All three methods can be called only from within a synchronized context.
  • The code sleep(1000) puts thread aside for exactly one second. But the code wait(1000) causes a wait of up to one second until it receives the notify() or notifyAll()
  • Deadlock describes a situation where two or more threads are blocked forever, waiting for each other.