вторник, 23 апреля 2024 г.

Date Time in Java

    Java LocalDate class is an immutable class that represents Date with a default format of yyyy-mm-dd. It inherits Object class and implements the ChronoLocalDate interface.

import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        // Creating a LocalDate object representing today's date
        LocalDate today = LocalDate.now();
        System.out.println("Today's date: " + today);

        // Creating a LocalDate object for a specific date
        LocalDate specificDate = LocalDate.of(2024, 4, 23);
        System.out.println("Specific date: " + specificDate);

        // Getting components of a LocalDate
        int year = specificDate.getYear();
        int month = specificDate.getMonthValue();
        int day = specificDate.getDayOfMonth();
        System.out.println("Year: " + year + ", Month: " + month + ", Day: " + day);

        // Manipulating LocalDate objects
        LocalDate futureDate = specificDate.plusDays(7); // Adding 7 days
        System.out.println("Date 7 days from now: " + futureDate);

        LocalDate pastDate = specificDate.minusMonths(3); // Subtracting 3 months
        System.out.println("Date 3 months ago: " + pastDate);

        // Comparing LocalDate objects
        LocalDate anotherDate = LocalDate.of(2024, 5, 15);
        if (specificDate.isBefore(anotherDate)) {
            System.out.println(specificDate + " is before " + anotherDate);
        } else {
            System.out.println(specificDate + " is after " + anotherDate);
        }
    }
}

Java LocalTime class is an immutable class that represents time with a default format of hour-minute-second. It inherits Object class and implements the Comparable interface.

import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        // Creating a LocalTime object representing the current time
        LocalTime currentTime = LocalTime.now();
        System.out.println("Current time: " + currentTime);

        // Creating a LocalTime object for a specific time
        LocalTime specificTime = LocalTime.of(14, 30, 45); // hour, minute, second
        System.out.println("Specific time: " + specificTime);

        // Getting components of a LocalTime
        int hour = specificTime.getHour();
        int minute = specificTime.getMinute();
        int second = specificTime.getSecond();
        int nano = specificTime.getNano(); // fraction of second
        System.out.println("Hour: " + hour + ", Minute: " + minute + ", Second: " + second + ", Nano: " + nano);

        // Manipulating LocalTime objects
        LocalTime futureTime = specificTime.plusHours(2); // Adding 2 hours
        System.out.println("Time 2 hours from now: " + futureTime);

        LocalTime pastTime = specificTime.minusMinutes(15); // Subtracting 15 minutes
        System.out.println("Time 15 minutes ago: " + pastTime);

        // Comparing LocalTime objects
        LocalTime anotherTime = LocalTime.of(10, 0, 0);
        if (specificTime.isBefore(anotherTime)) {
            System.out.println(specificTime + " is before " + anotherTime);
        } else {
            System.out.println(specificTime + " is after " + anotherTime);
        }
    }
}

Java LocalDateTime class is an immutable date-time object that represents a date-time, with the default format as yyyy-MM-dd HH-mm-ss.zzz. It inherits object class and implements the ChronoLocalDateTime interface.

import java.time.LocalDateTime;

public class Main {
    public static void main(String[] args) {
        // Creating a LocalDateTime object representing the current date and time
        LocalDateTime currentDateTime = LocalDateTime.now();
        System.out.println("Current date and time: " + currentDateTime);

        // Creating a LocalDateTime object for a specific date and time
        LocalDateTime specificDateTime = LocalDateTime.of(2024, 4, 23, 14, 30, 45); // year, month, day, hour, minute, second
        System.out.println("Specific date and time: " + specificDateTime);

        // Getting components of a LocalDateTime
        int year = specificDateTime.getYear();
        int month = specificDateTime.getMonthValue();
        int day = specificDateTime.getDayOfMonth();
        int hour = specificDateTime.getHour();
        int minute = specificDateTime.getMinute();
        int second = specificDateTime.getSecond();
        System.out.println("Year: " + year + ", Month: " + month + ", Day: " + day +
                           ", Hour: " + hour + ", Minute: " + minute + ", Second: " + second);

        // Manipulating LocalDateTime objects
        LocalDateTime futureDateTime = specificDateTime.plusDays(7); // Adding 7 days
        System.out.println("Date and time 7 days from now: " + futureDateTime);

        LocalDateTime pastDateTime = specificDateTime.minusMonths(3); // Subtracting 3 months
        System.out.println("Date and time 3 months ago: " + pastDateTime);

        // Comparing LocalDateTime objects
        LocalDateTime anotherDateTime = LocalDateTime.of(2024, 5, 15, 10, 0, 0);
        if (specificDateTime.isBefore(anotherDateTime)) {
            System.out.println(specificDateTime + " is before " + anotherDateTime);
        } else {
            System.out.println(specificDateTime + " is after " + anotherDateTime);
        }
    }
}

Java Period class is used to measures time in years, months and days. It inherits the Object class and implements the ChronoPeriod interface.

import java.time.LocalDate;
import java.time.Period;

public class Main {
    public static void main(String[] args) {
        // Creating LocalDate objects for two dates
        LocalDate startDate = LocalDate.of(2020, 1, 1);
        LocalDate endDate = LocalDate.of(2024, 4, 23);

        // Calculating the period between the two dates
        Period period = Period.between(startDate, endDate);

        // Accessing the components of the period
        int years = period.getYears();
        int months = period.getMonths();
        int days = period.getDays();

        System.out.println("Period between " + startDate + " and " + endDate + ":");
        System.out.println("Years: " + years);
        System.out.println("Months: " + months);
        System.out.println("Days: " + days);

        // Creating a Period using of() method
        Period additionalPeriod = Period.of(1, 2, 3); // 1 year, 2 months, and 3 days

        // Printing the additionalPeriod
        System.out.println("Additional Period: " + additionalPeriod); // Additional Period: P1Y2M3D
    }
}

Java Duration class is used to measures time in seconds and nanoseconds. It inherits the Object class and implements the Comparable interface.

import java.time.Duration;
import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        // Creating LocalTime objects for two times
        LocalTime startTime = LocalTime.of(10, 30, 0);
        LocalTime endTime = LocalTime.of(12, 45, 30);

        // Calculating the duration between the two times
        Duration duration = Duration.between(startTime, endTime);

        // Accessing the components of the duration
        long seconds = duration.getSeconds();
        long nano = duration.getNano();

        System.out.println("Duration between " + startTime + " and " + endTime + ":");
        System.out.println("Seconds: " + seconds);
        System.out.println("Nanoseconds: " + nano);

        // Manipulating a LocalTime using a Duration
        LocalTime newTime = startTime.plus(duration);
        System.out.println("New time after adding the duration: " + newTime);
    }
}

понедельник, 22 апреля 2024 г.

Generics in Java

    Generics in Java provide a way to create classes, interfaces, and methods that operate with types as parameters. They enable you to define classes and methods with placeholder types which are specified when the classes, interfaces, or methods are instantiated or called, respectively. 

There are mainly 3 advantages of generics. They are as follows:

1) Type-safety: We can hold only a single type of objects in generics.

import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// Type safety ensures that only strings can be added to the list
// stringList.add(10); // This would cause a compilation error
for (String str : stringList) {
System.out.println(str.toUpperCase());
}
}
}


2) Type casting is not required: There is no need to typecast the object.

import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// No need for type casting when retrieving elements from the list
for (String str : stringList) {
System.out.println(str.toUpperCase());
}
}
}


3) Compile-Time Checking: It is checked at compile time so problem will not occur at runtime. The good programming strategy says it is far better to handle the problem at compile time than runtime.

import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// Type safety: Compiler ensures only String objects are added to the list
// stringList.add(10); // This would cause a compilation error
// Compile-time checking: Correct usage of generics
for (String str : stringList) {
System.out.println(str.toUpperCase());
}
// Compile-time checking: Incorrect usage of generics
// List<Integer> integerList = new ArrayList<String>(); // Compilation error
}
}


Generic class

A class that can refer to any type is known as a generic class. Here, we are using the T type parameter to create the generic class of specific type.

public class Box<T> {
private T value;

public Box(T value) {
this.value = value;
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}

public static void main(String[] args) {
// Create a Box for Integer
Box<Integer> integerBox = new Box<>(10);
System.out.println("Integer Value: " + integerBox.getValue());

// Create a Box for String
Box<String> stringBox = new Box<>("Hello");
System.out.println("String Value: " + stringBox.getValue());
}
}

Type Parameters

The type parameters naming conventions are important to learn generics thoroughly. The common type parameters are as follows:

  1. T - Type
  2. E - Element
  3. K - Key
  4. N - Number
  5. V - Value
Generic Method

Like the generic class, we can create a generic method that can accept any type of arguments. Here, the scope of arguments is limited to the method where it is declared. It allows static as well as non-static methods.

public class TestGenerics4 {
// Generic method printArray with type parameter E
public static <E> void printArray(E[] elements) {
for (E element : elements) {
System.out.println(element);
}
System.out.println();
}
public static void main(String args[]) {
// Integer array
Integer[] intArray = {10, 20, 30, 40, 50};
// Character array
Character[] charArray = {'J', 'A', 'V', 'A', 'T', 'P', 'O', 'I', 'N', 'T'};
System.out.println("Printing Integer Array");
printArray(intArray); // Invoking printArray with Integer array
System.out.println("Printing Character Array");
printArray(charArray); // Invoking printArray with Character array
}
}

Wildcard in Generics

Wildcard is a special symbol used in type declarations to represent an unknown type. Wildcards are denoted by the ? character and are used to make generic types more flexible.

import java.util.ArrayList;
import java.util.List;

public class UnboundedWildcardExample {
// Method that prints elements of a list with unbounded wildcard
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}

public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");

System.out.println("Printing integerList:");
printList(integerList);

System.out.println("\nPrinting stringList:");
printList(stringList);
}
}


An upper bounded wildcard in Java generics, denoted by ? extends T, allows you to specify that a generic type can be any subtype of a particular type T or T itself. This means that the generic type can be any type that is a subclass of T. Upper bounded wildcards are useful when you want to make your code more flexible by accepting collections of any type that is a subtype of a particular class or interface.

import java.util.ArrayList;
import java.util.List;

public class UpperBoundedWildcardExample {
// Method that computes the sum of numbers in a list with an upper bounded wildcard
public static double sumOfNumbers(List<? extends Number> list) {
double sum = 0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}

public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

List<Double> doubleList = new ArrayList<>();
doubleList.add(2.5);
doubleList.add(3.7);
doubleList.add(4.2);

System.out.println("Sum of integers: " + sumOfNumbers(integerList));
System.out.println("Sum of doubles: " + sumOfNumbers(doubleList));
}
}

A lower bounded wildcard in Java generics, denoted by ? super T, allows you to specify that a generic type can be any supertype of a particular type T or T itself. This means that the generic type can be any type that is a superclass of T. Lower bounded wildcards are useful when you want to make your code more flexible by accepting collections of any type that is a supertype of a particular class or interface.

import java.util.ArrayList;
import java.util.List;

public class LowerBoundedWildcardExample {
// Method that adds elements to a list with a lower bounded wildcard
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}

public static void main(String[] args) {
List<Object> objectList = new ArrayList<>();
objectList.add("Hello");
objectList.add(3.14);

System.out.println("Before adding integers:");
for (Object obj : objectList) {
System.out.println(obj);
}

addNumbers(objectList);

System.out.println("\nAfter adding integers:");
for (Object obj : objectList) {
System.out.println(obj);
}
}
}


суббота, 6 апреля 2024 г.

Prototype Design Pattern

    Prototype pattern refers to creating duplicate object while keeping performance in mind. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

Prototype design pattern is used when the Object creation is a costly affair and requires a lot of time and resources and you have a similar object already existing. Prototype pattern provides a mechanism to copy the original object to a new object and then modify it according to our needs. Prototype design pattern uses java cloning to copy the object.

It would be easy to understand prototype design pattern with an example. Suppose we have an Object that loads data from database. Now we need to modify this data in our program multiple times, so it’s not a good idea to create the Object using new keyword and load all the data again from database. The better approach would be to clone the existing object into a new object and then do the data manipulation. Prototype design pattern mandates that the Object which you are copying should provide the copying feature.

package com.journaldev.design.prototype;

import java.util.ArrayList;
import java.util.List;

public class Employees implements Cloneable{

    private List<String> empList;
   
    public Employees(){
        empList = new ArrayList<String>();
    }
   
    public Employees(List<String> list){
        this.empList=list;
    }
    public void loadData(){
        //read all employees from database and put into the list
        empList.add("Pankaj");
        empList.add("Raj");
        empList.add("David");
        empList.add("Lisa");
    }
   
    public List<String> getEmpList() {
        return empList;
    }

    @Override
    public Object clone() throws CloneNotSupportedException{
            List<String> temp = new ArrayList<String>();
            for(String s : this.getEmpList()){
                temp.add(s);
            }
            return new Employees(temp);
    }
   
}

package com.journaldev.design.test;

import java.util.List;

import com.journaldev.design.prototype.Employees;

public class PrototypePatternTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Employees emps = new Employees();
        emps.loadData();
       
        //Use the clone method to get the Employee object
        Employees empsNew = (Employees) emps.clone();
        Employees empsNew1 = (Employees) emps.clone();
        List<String> list = empsNew.getEmpList();
        list.add("John");
        List<String> list1 = empsNew1.getEmpList();
        list1.remove("Pankaj");
       
        System.out.println("emps List: "+emps.getEmpList());
        System.out.println("empsNew List: "+list);
        System.out.println("empsNew1 List: "+list1);
    }

}

Output of the above prototype design pattern example program is:

emps List: [Pankaj, Raj, David, Lisa]
empsNew List: [Pankaj, Raj, David, Lisa, John]
empsNew1 List: [Raj, David, Lisa]

Builder Design Pattern

    Builder pattern builds a complex object using simple objects and using a step by step approach. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

    Builder pattern was introduced to solve some of the problems with Factory and Abstract Factory design patterns when the Object contains a lot of attributes. There are three major issues with Factory and Abstract Factory design patterns when the Object contains a lot of attributes.

  1. Too Many arguments to pass from client program to the Factory class that can be error prone because most of the time, the type of arguments are same and from client side its hard to maintain the order of the argument.
  2. Some of the parameters might be optional but in Factory pattern, we are forced to send all the parameters and optional parameters need to send as NULL.
  3. If the object is heavy and its creation is complex, then all that complexity will be part of Factory classes that is confusing.
We can solve the issues with large number of parameters by providing a constructor with required parameters and then different setter methods to set the optional parameters. The problem with this approach is that the Object state will be inconsistent until unless all the attributes are set explicitly. Builder pattern solves the issue with large number of optional parameters and inconsistent state by providing a way to build the object step-by-step and provide a method that will actually return the final Object.

package com.journaldev.design.builder;

public class Computer {
   
    //required parameters
    private String HDD;
    private String RAM;
   
    //optional parameters
    private boolean isGraphicsCardEnabled;
    private boolean isBluetoothEnabled;
   

    public String getHDD() {
        return HDD;
    }

    public String getRAM() {
        return RAM;
    }

    public boolean isGraphicsCardEnabled() {
        return isGraphicsCardEnabled;
    }

    public boolean isBluetoothEnabled() {
        return isBluetoothEnabled;
    }
   
    private Computer(ComputerBuilder builder) {
        this.HDD=builder.HDD;
        this.RAM=builder.RAM;
        this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
        this.isBluetoothEnabled=builder.isBluetoothEnabled;
    }
   
    //Builder Class
    public static class ComputerBuilder{

        // required parameters
        private String HDD;
        private String RAM;

        // optional parameters
        private boolean isGraphicsCardEnabled;
        private boolean isBluetoothEnabled;
       
        public ComputerBuilder(String hdd, String ram){
            this.HDD=hdd;
            this.RAM=ram;
        }

        public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
            this.isGraphicsCardEnabled = isGraphicsCardEnabled;
            return this;
        }

        public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
            this.isBluetoothEnabled = isBluetoothEnabled;
            return this;
        }
       
        public Computer build(){
            return new Computer(this);
        }

    }

}

package com.journaldev.design.test;

import com.journaldev.design.builder.Computer;

public class TestBuilderPattern {

    public static void main(String[] args) {
        //Using builder to get the object in a single line of code and
                //without any inconsistent state or arguments management issues    
        Computer comp = new Computer.ComputerBuilder(
                "500 GB", "2 GB").setBluetoothEnabled(true)
                .setGraphicsCardEnabled(true).build();
    }

}

пятница, 5 апреля 2024 г.

Abstract Factory Design Pattern

    Abstract Factory patterns work around a super-factory which creates other factories. This factory is also called as factory of factories. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

package com.journaldev.design.model;
 
public abstract class Computer {
     
    public abstract String getRAM();
    public abstract String getHDD();
    public abstract String getCPU();
     
    @Override
    public String toString(){
        return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
    }
}


package com.journaldev.design.model;
 
public class PC extends Computer {
 
    private String ram;
    private String hdd;
    private String cpu;
     
    public PC(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public String getRAM() {
        return this.ram;
    }
 
    @Override
    public String getHDD() {
        return this.hdd;
    }
 
    @Override
    public String getCPU() {
        return this.cpu;
    }
 
}


package com.journaldev.design.model;
 
 
public class Server extends Computer {
 
    private String ram;
    private String hdd;
    private String cpu;
     
    public Server(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public String getRAM() {
        return this.ram;
    }
 
    @Override
    public String getHDD() {
        return this.hdd;
    }
 
    @Override
    public String getCPU() {
        return this.cpu;
    }
 
}


package com.journaldev.design.abstractfactory;

import com.journaldev.design.model.Computer;

public interface ComputerAbstractFactory {

    public Computer createComputer();

}


package com.journaldev.design.abstractfactory;

import com.journaldev.design.model.Computer;
import com.journaldev.design.model.PC;

public class PCFactory implements ComputerAbstractFactory {

    private String ram;
    private String hdd;
    private String cpu;
   
    public PCFactory(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public Computer createComputer() {
        return new PC(ram,hdd,cpu);
    }

}


package com.journaldev.design.abstractfactory;

import com.journaldev.design.model.Computer;
import com.journaldev.design.model.Server;

public class ServerFactory implements ComputerAbstractFactory {

    private String ram;
    private String hdd;
    private String cpu;
   
    public ServerFactory(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
   
    @Override
    public Computer createComputer() {
        return new Server(ram,hdd,cpu);
    }

}


package com.journaldev.design.abstractfactory;

import com.journaldev.design.model.Computer;

public class ComputerFactory {

    public static Computer getComputer(ComputerAbstractFactory factory){
        return factory.createComputer();
    }
}


package com.journaldev.design.test;

import com.journaldev.design.abstractfactory.PCFactory;
import com.journaldev.design.abstractfactory.ServerFactory;
import com.journaldev.design.factory.ComputerFactory;
import com.journaldev.design.model.Computer;

public class TestDesignPatterns {

    public static void main(String[] args) {
        testAbstractFactory();
    }

    private static void testAbstractFactory() {
        Computer pc = ComputerFactory.getComputer(
        new PCFactory("2 GB","500 GB","2.4 GHz"));
        Computer server = ComputerFactory.getComputer(
        new ServerFactory("16 GB","1 TB","2.9 GHz"));
        System.out.println("AbstractFactory PC Config::"+pc);
        System.out.println("AbstractFactory Server Config::"+server);
    }
}