Java OOPs Concepts

  1. Abstraction
  2. Encapsulation
  3. Inheritance
  4. Polymorphism

Abstraction:

Abstraction is a process that hides the implementation and displays only the information needed. In other words “hide the internal implementation and shows the functionality".

We can achieve abstraction using abstract classes and interfaces.

Abstraction helps in reducing programming complexity and efforts.

In the below example users can choose payment option and amount, he doesn’t know the how the payment is done internally.

public class Application {
   
public static void main(String[] args) {
       
// pay option and pay amount choose by user
       
String payOption = "netbanking";
       
double payAmount = 1000.00;
       
if(payOption.equals("credit")){
            Payment payment =
new CreditCard();
            payment.payAmount(payAmount);
        }
else if(payOption.equals("netbanking")){
            Payment payment =
new NetBanking();
            payment.payAmount(payAmount);
        }

    }
}

/**
 * Output:
 * pay amount through net-banking : 1000.0
 */

public interface Payment {
   
void payAmount(double payAmount);
}

public class CreditCard implements Payment{
   
@Override
   
public void payAmount(double payAmount) {
        System.
out.println("pay amount using credit card : "+ payAmount);
    }
}

public class NetBanking implements Payment{
   
@Override
   
public void payAmount(double payAmount) {
        System.
out.println("pay amount through net-banking : "+payAmount);
    }
}

Encapsulation:

The encapsulation binds the data and code together into a single unit. Hence, it is also known as data hiding.

Encapsulation acts as a protective wrapper that prevents the code and data from being accessed by outsiders.

We can achieve this by Java bean is the fully encapsulated class because all the data members are private here and we can access by the setter and getter methods.

We can make the class read-only or write-only by the getter or setter methods.

Suppose if we don't provide setter methods then the outside person can't set the value.

public class Item {

   
private String item = "pen";
   
private int quantity = 10;

   
public String getItem() {
       
return item;
    }

   
public int getQuantity() {
       
return quantity;
    }
}

We can control the data by adding restrictions in the setter methods. In the below example outsiders can set the quantity if it is <= 10

public class Item {

   
private String item = "pen";
   
private int quantity = 10;

   
public String getItem() {
       
return item;
    }
   
public int getQuantity() {
       
return quantity;
    }
   
public void setItem(String item) {
       
this.item = item;
    }
   
public void setQuantity(int quantity) {
       
if(quantity <= 10){
           
this.quantity = quantity;
        }
    }
}

public class Application {
   
public static void main(String[] args) {
        Item item =
new Item();
        item.setQuantity(
5);
        System.
out.println(item.getQuantity());
    }
}

/**
 * Output:
 * 5
 */

 

Inheritance:

One object acquires/inherits another object’s properties and behavior.

We can create a new class by extending the parent class then we can reuse methods and fields of the parent class.

Inheritance represents hierarchical classification.

Loan.java is Abstract class, which extends by HomeLoan class and PersonalLoan class

public abstract class Loan {
   
abstract void loanType();
}

public class HomeLoan extends Loan{
    @Override
    void loanType() {
        System.out.println("Home loan");
    }
}
public class PersonalLoan extends Loan{
    @Override
    void loanType() {
        System.out.println("Personal Loan");
    }
}
public class Application {
    public static void main(String[] args) {
        Loan loan = new HomeLoan();
        loan.loanType();
    }
}
/**
 * Output:
 * Home loan
 */

 

Polymorphism:

Polymorphism means many forms, it performs a single action in different ways.

Two different types:

1. Compile-time polymorphism

It is resolved at compile-time which is achieved through Method Overloading.

public class Application {
   
public static void main(String[] args) {
        Employee employee =
new Employee();
        employee.getEmployeeDetails(
101);
        employee.getEmployeeDetails(
"narendar");
    }
}

/**
 * Output:
 * get details by employee Id : 101
 * get details by employee Name : narendar
 */

class Employee{
   
public void getEmployeeDetails(Integer employeeId){
        System.
out.println("get details by employee Id : "+employeeId);
    }
   
public void getEmployeeDetails(String employeeName){
        System.
out.println("get details by employee Name : "+employeeName);
    }
   
public void getEmployeeDetails(Integer employeeId, String employeeName){
        System.
out.println("get details by employee Id and Name : "+employeeId+" , "+employeeName);
    }
}

 

2. Runtime polymorphism.

It is resolved at run-time which is achieved through Method Overriding.

public class Application {
   
public static void main(String[] args) {
        DeveloperDept developerDept =
new DeveloperDept();
        developerDept.getEmployeeDetails();

        HrDept hrDept =
new HrDept();
        hrDept.getEmployeeDetails(
101);
        hrDept.getEmployeeDetails();
    }
}

/**
 * Output:
 * get developer details
 * get details by employee Id : 101
 * get HR details
 */

class Employee{
   
public void getEmployeeDetails(Integer employeeId){
        System.
out.println("get details : "+employeeId);
    }
   
public void getEmployeeDetails(){
        System.
out.println("get details");
    }
}
class DeveloperDept extends Employee{
   
@Override
   
public void getEmployeeDetails(){
        System.
out.println("get developer details");
    }
}
class HrDept extends Employee{
   
@Override
   
public void getEmployeeDetails(){
        System.
out.println("get HR details");
    }
   
@Override
   
public void getEmployeeDetails(Integer employeeId){
        System.
out.println("get details by employee Id : "+employeeId);
    }
}

 

Configure Multiple Database’s in SpringBoot application

We can customize the configurations in the .yml file as shown in the below

 YML File

#### DB2 Properties
db2:
 
datasource:
   
jdbc-url: jdbc:db2://SERVER:PORT/DBNAME
   
driver-class-name: com.ibm.db2.jcc.DB2Driver
   
testWhileIdle: true
   
validationQuery: SELECT 1
   
username:
   
passwrod:

#### Oracle properties
oracle:
 
datasource:
   
jdbc-url: jdbc:oracle:thin:@SERVER:PORT:DBNAME
   
driver-class-name: oracle.jdbc.OracleDriver
   
username:
   
password:

 

Map the above YML properties to DataSource programmatically for each Database

DB2DatabaseConfig.java

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import javax.sql.DataSource;
@Configuration @ConfigurationProperties(prefix = "db2.datasource") public class DB2DatabaseConfig extends HikariConfig {
   
@Bean     @Qualifier("db2")     @Primary
   
public DataSource db2DataSource() {         return new HikariDataSource(this);     }

   
@Bean     @Qualifier("db2")     public NamedParameterJdbcTemplate db2JdbcTemplate() {         return new NamedParameterJdbcTemplate(db2DataSource());     } }

 

OracleDatabaseConfig.java

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration @ConfigurationProperties(prefix = "oracle.datasource") public class OracleDatabaseConfig extends HikariConfig {
   
@Bean     @Qualifier("oracle")     public DataSource oracleDataSource() {         return new HikariDataSource(this);     }
   
@Bean     @Qualifier("oracle")     public JdbcTemplate oracleJdbcTemplate() throws SQLException {         return new JdbcTemplate(oracleDataSource());     } }

 

As usual, we can use the above configurations in the repository class as below for Oracle DB

@Repository
public class LocationRepository {

   
@Autowired     @Qualifier("oracle")     JdbcTemplate oracleJdbcTemplate;
    public void getLocation(){
// write code here     }
}

We can connect DB2 as below

@Mapper
public interface TestMapper {
   
// Write your code here
}

 

Singleton Design Pattern

Singleton Design Pattern says that a class should have only a single instance and that single object can be used by all other classes.

It Saves memory because the object is not created for each request. An only single instance is reused.

It is mostly used in thread pools, caching, configuration settings, etc.

We have to follow the below steps to create the singleton

Static member: It gets memory only

Private constructor: It will prevent instantiating the Singleton class from outside the class.

Static factory method: This provides the global point of access.

There are two ways of achieving singleton design pattern

Early Instantiation: instance create early if required or not

public class EagerSingleton {
   
private EagerSingleton(){}
   
private static final EagerSingleton singleton = new EagerSingleton();
   
public static EagerSingleton getSingleton(){
       
return singleton;
    }
}

Lazy Instantiation: instance create on demand

public class LazySingleton {
    private LazySingleton(){
    }
    private static LazySingleton singleton;
    public static LazySingleton getSingleton(){
        if(singleton == null){
            return singleton = new LazySingleton();
        }
        else{
            return singleton;
        }
    }
}

 

We can validate as below whether it is created single instance or not

public class SingletonTest {
    public static void main(String[] args) {
        LazySingleton obj1 = LazySingleton.getSingleton();
        LazySingleton obj2 = LazySingleton.getSingleton();
        System.out.println(obj1.hashCode()+", "+obj2.hashCode());
    }
}

 

Difference between SQL and NoSQL


 SQL(Structured query language)

 NoSQL

Supported databases are Relational Databases(RDBMS)


EX: MySQL, Oracle, PostgreSQL, Microsoft SQL Server

Supported databases are Non-Relational or distributed Databases


EX: MongoDB, Cassandra, and Amazon DynamoDB 

SQL databases represent data in the form of a table which consist of row and column

NoSQL databases represent data as document-based, JSON, key-value pair

SQL databases have a predefined schema, i.e., we define a schema before data insert into the system. We can’t change the schema.

This is inflexibility

NoSQL databases have a dynamic schema, we can insert data without predefined schema, and we can make changes easily on the schema.

We can increase the load on a single server to handle more traffic 

We can increase more servers to handle more traffic

We can write complex queries 

We can’t write complex queries

SQL databases follow ACID properties (Atomicity, Consistency, Isolation, and Durability)

NoSQL database follows the Brewers CAP theorem (Consistency, Availability, and Partition tolerance)



@Autowired, @Qualifier, @Resource

@Autowired

@Autowired is used to inject objects into other objects, Spring provided

  1. no,
  2. byName,
  3. byType,
  4. constructor,
  5. autodetect. 

@Autowired on the properties: By default it takes “byType

We have SavingAccount.java, this service implemented by AccountController.java by Injecting SavingAccount into AccountController using @Autowired.

@Service
@Slf4j
public class SavingAccount {
    public void accountType() {
        log.info("Saving Account");
    }
}
public class AccountController {
    @Autowired
    private AccountService accountService;
    void getAccount(){
        accountService.accountType();
    }
}

@Qualifier

@Qualifier annotation avoid ambiguity and inject the required bean.

We get the ambiguity when the injected Service implemented by two classes as show in the blow example.

public interface AccountService {
    void accountType();
}

 

AccountService is implemented by SavingAccount and CurrentAcoount classes.

@Service
@Slf4j
public class SavingAccount implements AccountService {
    @Override
    public void accountType() {
        log.info("Saving Account");
    }
}

 

@Service
@Slf4j
public class CurrentAccount implements AccountService {
    @Override
    public void accountType() {
        log.info("Current Account");
    }
}

Now We @Autowired and @Qualifier required Service in the AccountController

public class AccountController {
    @Autowired     @Qualifier("currentAccount")     private AccountService accountService;     void getAccount(){         accountService.accountType();     } } 

If we don’t use @Qualifier then we get the

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountController': 

@Resource

Instated of using @Autowired and @Qualifier we can use @Resource to inject bean and avoid ambiguity

public class AccountController {
    @Resource(name = "currentAccount")     private AccountService accountService;     void getAccount(){         accountService.accountType();     } }

 We get the bean name dynamic from the properties file using @Resource

public class AccountController {
    @Resource(name = "${beanName}")     private AccountService accountService;     void getAccount(){         accountService.accountType();     } }

In the properties file, we can mention as below

beanName = currentAccount

 

Fibonacci series in Java


Java Fibonacci series is a number series, the sum of the previous two numbers result is the next number.
e.g: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 etc.
we can start our series from 0 or 1, it is our choice.

 There are two ways to write the Fibonacci series in Java:


=> Fibonacci series using for & while loop / without method recursion
=> Fibonacci series using method recursion

Let’s check the first Fibonacci series using for loop



public class FibonacciTest {     
       public static void main(String[] args) {       
              int number=10;
              int f1=0,f2=1,f3=0;       
              //System.out.print(f1); // print zero          
              for(int i=1; i<=number; i++){
                    
                     f3=f1+f2;
                     f1=f2;
                     f2=f3;
                     f3=f1;
                     System.out.print(" "+f3);
              }            
       }
}


Output:


1 1 2 3 5 8 13 21 34 55


Understanding the flow of the program


int number=10;
int f1=0,f2=1,f3=0;       
for(int i=1; i<=number; i++)
Repeat loop until condition false 

f3=f1+f2;
f1=f2;
f2=f3;
f3=f1;
System.out.println( f3);
f3=0+1
f1=1
f2=1
f3=1
1
f3=1+1
f1=1
f2=2
f3=1
1
f3=1+2
f1=2
f2=3
f3=2
2
f3=2+3
f1=3
f2=5
f3=3
3
f3=3+5
f1=5
f2=8
f3=5
5
f3=5+8
f1=8
f2=13
f3=8
8
f3 store next print value


f3 store current print value
Continue like this……

I hope you guys understood the flow of this program J

Fibonacci series using method recursion


Method recursion is a method that calls itself continuously.


public class FibonacciTest {     
        static int f1=0,f2=1,f3=0;   
        static void printSeries(int count){   
           if(count<=10){   
                f3=f1+f2;   
                f1=f2;   
                f2=f3;
                f3=f1;
                System.out.print(" "+f3);
                count++;
                printSeries(count);   
            }   
        }   
        public static void main(String args[]){   
        
        // System.out.print(f1);//print 0   
               printSeries(1);
        } 
}


The Fibonacci program using method recursion is given the same output as above

Output:



1 1 2 3 5 8 13 21 34 55


Here we took static variables and methods because we can call it directly in the main method without creating the object.

The flow of this program is the same as the above program but here we used method recursion and repeat the if condition until false.