Introduction To Java

→ Short Introduction/History of Java

Java is a high-level programming language designed for the aim to develop the distributed system and for easy web/internet applications based on C/C++ developed by Sun Microsystems (James Gosling & Patrick Naughton) in 1990.

• It is class based, object oriented, platform independent programming language.

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

‣ Key word-meanings to be known

  • Class = Class is like an object constructor or blueprint that defines some properties and behaviors of that respective class. It contains the collection of objects, attributes, instances, methods ,etc in the form of a single unit.
  • Object = Objects are the real-world entities that can represent any person, place, or thing and can be distinguished from others. In other words, it is the instance of the class that has the properties and behaviors attached to it respective to its class.
  • JVM = JVM is a java virtual machine that enables a computer to run any java program by converting it into bytecode.
  • JRE = The Java Runtime Environment is a package that includes the JVM along with libraries and other resources and provides the runtime environment required for executing Java programs.
  • Bytecode: Bytecode refers to the intermediate representation of compiled Java source code.

→ Java buzzwords/features/characteristics

‣ Simple:
Java has a concise, integrated set of features that makes it easy to learn and use.

‣ Object-oriented:
Java is an object-oriented programming language which means everything is written in the form of classes and objects.

‣ Platform Independent:
The “Write Once, Run Anywhere” principle is a hallmark of Java. Programs are compiled to bytecode that can be executed on any platform with a compatible Java Virtual Machine (JVM).

‣ Portable:
Java programs can execute in any environment for which there is a Java run-time system.

‣ Robust:
Java’s strong type-checking and exception handling contribute to its robustness. It helps catch errors at compile time and handles runtime exceptions gracefully, minimizing the risk of crashes.

‣ Secure:
Java provides features for building secure applications. It includes a robust security model with built-in mechanisms to prevent unauthorized access, memory leaks, and other security vulnerabilities.

‣ Multi-Threaded:
Java has built-in support for creating and managing threads, enabling concurrent execution and efficient utilization of multi-core processors.

Object Oriented Programming(OOP) and its principle:

Object-Oriented Programming (OOP) is a programming paradigm that organizes code based on the concept of “objects“. It is a methodology for designing, structuring, and organizing code by modeling real-world entities and their interactions.

OOP is built upon several fundamental principles, often referred to as the “Four Pillars of OOP.”

The Four Pillars of Object-Oriented Programming:

  • Encapsulation
  • Polymorphism
  • Inheritance
  • Abstraction

Encapsulation:

Encapsulation is a programming mechanism that binds together code and the data it manipulates, and keeps both safe from outside interference and misuse.

In other word, the process of combining data member and member function into a single entity like class is called Encapsulation.

It is used to prevent the direct accessibility of data members and member function and this is done by using access specifier publicprivate, and protected.

Polymorphism:

Polymorphism is the quality that allows one interface to accesss a general class of actions.

It can be achieved through method overriding and interfaces.
It fosters code extensibility and adaptability.

Inheritance:

The process of getting property of one class into another class is called Inheritance.

In other word, we can say the process of deriving a new class from an old class is called inheritance. Here, new class is called derived or child or sub classs and old class is called base or super or parent class.

Note: When a class inherits the property of a another class it means it can access all the data member and function of that class except private element.

Abstraction:

Abstraction involves simplifying complex reality by modeling classes based on their essential characteristics.

It focuses on what an object does rather than how it does it.
It helps manage complexity by hiding unnecessary implementation details and emphasizing the essential features of an object.

Command Line Argument:

Command-line arguments are values that are provided to a Java program when it is executed from the command line or terminal.

These arguments allow you to pass data to your program without modifying its source code.

image 60

Simple Java programs:

1. Hello World

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

2. Use of Scanner

import java.util.Scanner;

public class AddTwoNumbers {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter the first number: ");
        int num1 = scanner.nextInt();
        System.out.print("Enter the second number: ");
        int num2 = scanner.nextInt();
        int sum = num1 + num2;
        System.out.println("Sum: " + sum);
        scanner.close();
    }
}
Unit 2: Fundamental Programming Structure

→ Introduction to Data Type

• Data types are the specifiers of what type of data the user wants to enter or store in the variables declared.

• In other words, data type is the form of a variable to which a value can be assigned.

• It is necessary to define data type before variable declaration for ensuring the computer that what memory it has work on and how much memory it needs to reserve. Every programming language has its own set of predefined data types so do java. Here are some of the data types available in java programming language.

image 3

→ In java, data types are divided into two groups i.e.

  1. Primitive Data Type= These types of data type allow user to store only single data values of only one type. It usually starts with lowercase. For example, integer, float, double, character, etc. Its size depends on the type of the data structure/type. It can used to call methods.
  2. Non-Primitive Data Type= These types of data types allow user to store the data of more than one type. It usually starts with uppercase. For example, string, set, list, stack, queue, array, all user defined classes, etc. Its size is mostly not fixed. It cannot be used to call methods.

→ Operators In Java

Java operators are the symbols that are used to perform different operations on variables and manipulate the values of the operands. Each operator performs different operations on the basis of their specification.

Types of Operators in Java

image 2

1.Arithmetic Operator= Arithmetic operators are used to perform mathematical operations on the operands like addition, subtraction, multiplication, etc.

image 15

Program for implementation of Arithmetic Operator:

image 14

Output:

image 13

2. Unary Operator= unary operator is used with only single operand to perform any operation like increment, decrement, negation, etc. some the unary operations available in java are:

image 9

Program for implementation of Unary operator:

image 11

Output:

image 12

3. Assignment operator= The Java Assignment Operators are used when you want to assign a value to the expression. The assignment operator is denoted by the single equal sign i.e., =.

image 13

Program for implementation of Assignment Operator:

image 15

Output:

image 16

4. Relational Operators= The relational operators are used to compare two available entities or values to understand what relationship the pair of values share. for example, equal to, greater than, etc.

image 17

Program for implementation of Relational Operator:

image 18

Output:

image 20

5. Logical Operator= Logical operator are used to perform logical operations on boolean values such as AND, OR, and NOT. They are the symbols or words that are used to connect to or more expressions and check relationship between them.

image 21

Program for implementation of Logical Operator:

image 22

Output:

image 23

6. Ternary Operator= Java ternary operatory is the only conditional operator that takes three operands. It is the replacement for the if-then-else statement and is one-liner conditional statement. It is denoted by (?:) symbol.

image 24

Program for implementation of Ternary Operator:

image 25

7. Bitwise Operators= Bitwise operators are used to perform the manipulation of individual bits of a number. They can be used with any integral type (char, short, int, etc.). They are used when performing update and query operations.

image 26

Program for implementation of Bitwise Operator:

image 28

Output:

image 29

8. Shift Operators= The shift operator is a java operator that is used to shift bit patterns right or left. The various types of shift operators available in java programming language are:

image 30

Program for implementation of Shift Operators:

image 31

Output:

image 32

Note: For negative bits, the signed and unsigned right shift operators provide different results.

Unsigned left shift= There is no such operator available in Java Programming. Instead, you can perform logical (<<).

image 33
Unit 3: Control Flow Statements

Control statements are used to control the flow of the Java code.

As we know Java compiler executes code from top to bottom so, the statements in the code are used to execute the program according to the order in which they appear. Such controls statements are called control flow statements.

Java provides three types of control flow statements, they are:

  • Decision- making statements
    – If statement
    – switch statements
  • Loop statements
    – do while loop
    – while loop
    – for loop
    – for each loop
  • Jumping statements
    – break statement
    – continue statement

Decision- making Statements

Decision making statements are the control statements in java that are used when we want a block of code to execute only when some sort of condition is fulfilled. It is similar to decision making in real life. There are two types of decision-making statements in java, they are.

  • If- statement = In java, if statement is used to evaluate a specific condition and divert it depending on that specific condition i.e., else part of the block. if statement returns boolean value i.e., either true or false. There are four types of If statement in java, they are.
    • Simple if statement
    • if-else statement
    • if-else-if ladder
    • Nested if-statement

Simple if statements= simple if statements is one of the easiest control flow statements in java, in which the block of code execute only if the condition is true.

Consider the following example in which the code executes only if the age is greater or equal to 18.

import java .util.Scanner;
public class If_statement 
{
	public static void main(String[] args)
	{
	     Scanner  obj = new Scanner(System.in);
	     System.out.print("Enter your age: ");
	     int age = obj.nextInt();
	     if(age>=18) // evalutes if the condition is true or false
	     {
	        System.out.println("You are eligible to cast the vote.");
	     }

	}
}

Here the “if block of code” will execute only if the condition is satisfied.

If-else statement= if-else statement in java evaluates the given condition and executes if part of code if evaluates the result as true and executes the else part if evaluates the result as false.

Consider the following example in which the code executes if block of code only if the condition(age>=18) is evaluated as true or executes else block of code if evaluated as false;

import java .util.Scanner;
public class If_statement 
{
	public static void main(String[] args)
	{
	     Scanner  obj = new Scanner(System.in);
	     System.out.print("Enter your age: ");
	     int age = obj.nextInt();
	     //runs if evaluated as true
	     if(age>=18) // evalutes if the condition is true or false
	     {
	        System.out.println("You are eligible to cast the vote.");
	     }
	     else // runs if evaluated as false
	     {
	        System.out.println("You are not eligible to cast the vote.");
	     }
	}
}

if-else-if ladder= the if-else-if ladder statements is like the decision tree in which the code enters in multiple else-if statements and is evaluated one by one if all are evaluated as false the final else statement is executed.

Consider the following example :

public class if_else_if_ladder
 {
   public static void main(strings[]args)
   {
      String country = "Nepal";
      
      if(country=="India") //executes if condition 1 is true
      {
         System.out.println("Country is India."); 
      }
      else if(country=="Turkey") //executes if condition 2 is true
      {
         System.out.println("Country is Turkey");
      }
      else if(country=="Russia") //executes if condition 3 is true
      {
         System.out.println("Country is Turkey");
      }
      else // executes if all constions are evaluated as false
      {
         System.out.println("Invalid");
      }
  }
}

Nested-if statement= The control flow statement becomes nested if we use multiple if or if-else statement inside another if or if-else statement.

Consider the following example:

import java.util.Scanner;
public class nested_if_statement
{
   public static void main(Strings[]args)
   {
      Scanner obj = new Scanner(System.in);
      //takes input from the user
      System.out.print("Enter any number: );
      int num = obj.nextInt();
      
      if(num>10) //condition 1
      {
         if(num<20) //sub condition 1
         {
           System.out.println("Number is greater than 10 and smaller than 20);
         }
         else // sub condition 2
         {
          System.out.println("Number is greater than both 10 and 20);
         } 
       }
       else if(num<10) // condition 2
       {
         System.out.println("Number is smaller than  10);
       }
       else // condition 3
       {
         System.out.println("Number is equal to 10);
       } 
   }
 }
  • Switch Statement= switch statement in java is similar to if-else-if ladder statement in which single case is switched on the basis of variable. switch statement makes the program more readable and easier to execute. the program is terminated when any of the case is implemented i.e., by break statement and if no case is implemented then the default is executed which is optional.
    Consider the following example:
import java.util.Scanner;
public class switch_statement
{
	public static void main(String[] args)
	{
		Scanner obj = new Scanner(System.in);
		String string = "It is vowel.";
		System.out.print("Enter any alphabet: ");
		char albt = obj.nextLine().charAt(0);
		
		switch(albt)
		{
		        case 'a': System.out.println(string); break; // case 1
			case 'e': System.out.println(string); break; // case 2
			case 'i': System.out.println(string); break; // case 3
			case 'o': System.out.println(string); break; // case 4
			case 'u': System.out.println(string); break; // case 5
		        default: System.out.println("It is consonent."); //optional	
		}
	}
}

Loop Statements

Loops in general are curved or circular shapes or something long. In Java programming language, loops are the programming element that repeats a portion of code repeatedly until the condition is met.

There are four types of loops available in Java, they are.

  • Do- while loop= Java a do- while loop allows the user to check the bollean condition at the end of each pass of the loop rather than before each pass. A consequence of the do-while loop is that its body part always executes at least once even if the condition fails. the syntax is shown below:

do {

//body part to be executed

} while (boolean expression)

Consider the following example for demonstration of do-while loop

public class do_while_loop
{
public static void main(String[]args
       {
           String string = "BimStudies.com";
           int length = string.length();
      	   int count=0;

//will be executed at least once even if there is zero characters in the string
		do{ 
	            count++;
		    length--;
			
		}while(length>0);
		
           System.out.println("No. of characters in the string= "+count);
        
	}
}
  • while- loop= while loop is the simplest kind of loops in Java, this loop tests if a certain condition is satisfied and performs the body of the loop each time this condition is evaluated to be true. Unlike do while loop it executes only if the condition is evaluated to be true. The syntax for while loop is as follows:

while (booleanExpression){

// loop body to be executed}

Consider the folowing example for demonstration of while loop

public class while_loop
{
public static void main(String[]args
       {
           String string = "BimStudies.com";
           int length = string.length();
      	   int count=0;

//will execute until the condition is satisfied
		while(length!=0)
		{ 
	            count++;
		    length--;	
		}
		
           System.out.println("No. of characters in the string = "+count);
        
	}
}
  • for loop= Another kind of the loops in java is for loop. Java supports two different styles of for loop. The first, which we will refer to as “traditional” style, is patterned after a similar syntax as for loops in the C and C++ languages. The syntax of for-loop is as follows:

    for (initializer; booleanCondition; increment/decrement)
    { //Code to be executed }

Consider the following example for demonstration of for-loop:

public class for_loop
{
public static void main(String[]args
       {
           String string = "BimStudies.com";
           int length = string.length();
      	   int count=0;

//will execute until the condition is satisfied/met
		for(int i=1; i<=length;i++)
		{
		        count++;
		}
		
           System.out.println("No. of characters in the string = "+count);
        
	}
}
  • for each loop= The second style of for loop supported by Java, which is known as the “for-each” loop, was introduced into Java in 2004 as part of the Se 5 release. This style provides a more succinct syntax for iterating through elements of an array or an appropriate container type. One thing to note is that the iterator of for-each loop is always initialized inside the loop together. The syntax for for-each loop is as follows:

    for elementType name : container)
    { // code to be executed }

Consider the following example for the demonstration of for-each loop:

public class for_each_loop
{
	public static void main(String[] args) 
	{
		int array[] = { 2, 4, 1, 5}; // declaration of an array
		
		int count=0; //initialize the count 
		
// here all the elements of the array will move to the iterator initialized one by one until all the elements are visited/iterated
	
             for(int i: array) // for each loop
              {
        	count++;// increment of count
              }
            System.out.println("Size of the array is "+count);
	}
}

Jumping Statements

Jumping statements are the control statements that transfer the control of the program from one point to another in the program. There are two types of jumping statements in java, they are:

  • Break Statements= break statement causes the flow of control to jump to the next line after the loop or switch to the body containing the break.

    Consider the following example for demonstration of break statement:
public class break_statement 
{
	public static void main(String[] args)
	{
                int array[] = { 2, 4, 1,8 ,7,5}; // declaration of an array
		int search = 1; //searching element
		int i=-1;
		for(i=0;i<array.length;i++)
		{
			if(array[i]==search)
			{
			//jumps out of loops immediately after the element is found
				break; 
			}
		}
		if(i==-1)
		{
			System.out.println("Element not found.");
		}
		else
		{
			System.out.println("1 is found at index "+i);
		}
	}
}
  • Continue Statements= The continue statement can be used within the loop. Continue statement causes the execution to skip over the remaining steps of the current iteration of the loop body. Unlike the break statement, the flow of control returns to the top of the loop body, assuming its condition remains satisfied.

    Consider the following example for demonstration of continue statement:
public class continue_statement 
{
	public static void main(String[] args) 
	{
		int array[]= {1,2 ,2, 1, 4, 2 , 5, 4}; //initializing array
		int search = 2;// searching element
		int count=0; //count for repeated element
		
		for(int i=0;i<array.length;i++)
		{
			if(array[i]==search)
			{
			        count++;
				// jumps to the start leaving the current iteration
				continue; 
			}
		}
		System.out.println(search+" is repeated "+count+" times in the array");
	}
}
Unit 4: Objects and Classes

In java programs, the primary actors are objects. Every object is an instance of a class, which serves as the type of the object and as a blueprint defining the data which the object stores and the methods for accessing and modifying that data. The major members of a class in Java are the following:

  • Instance Variables, which are also called fields, represent the data associated with an object of a class. Instance variables must have a type, which can either be a base type (such as int, flat, or double), or any class type (also known as reference type for reasons).
  • Methods in Java are blocks of code that can be called to perform actions (similar to functions and procedures in other high-level languages). Methods can accept parameters as arguments, and their behavior may depend on the object upon which they are invoked and the values of any parameters that are passed. A method that returns information to the caller without changing any instance variables is known as an accessor method, while an update method is one that may change one or more instance variables when called.

Preliminaries to be focused on

image 37
Inheritance and interface

→ Definition:

The process of getting property of one class into another class is called Inheritance.

In other word, we can say the process of deriving a new class from an old class is called inheritance. Here, new class is called derived or child or sub classs and old class is called base or super or parent class.

Note: When a class inherits the property of a another class it means it can access all the data member and function of that class except private element.

→ Parent/Super/Base class:

The class which is inherited by another class is called parent or super or base class.

→ Child/Sub/Derived class:

The class which inherits the property of another class is called child or sub or derived class.

Here, Subtraction is a Derived class and Addition is a Parent class and “extends” is a keyword that is used to inherit property of one class into another.

//super or parent class
class Addition {
    void add(){
        int x=20,y=10;
        int sum=x+y;
        System.out.println("Addition="+sum);
    }
}
//child or derived class
class Subtraction extends Addition {
    void sub(){
        int x=30,y=10;
        int difference=x-y;
        System.out.println("Subraction="+difference);
    }
}
public class practice {
    public static void main(String[] args) {
        //creating object of derived class
        Subtraction obj=new Subtraction();
        //calling method
        obj.add();
        obj.sub();
    }
}

→ Advantage of Inheritance:

• Code Reusability:

It promotes code reusability by allowing you to define a new class based on an existing class. You can inherit the fields and methods of the parent class without rewriting them in the child class.

• Efficient Development:

It allows you to focus on extending existing classes rather than building everything from scratch. This speeds up development and reduces the likelihood of introducing errors.

• Time Saving:

As there is no need to define existing property (same code) of a class in another class, it helps to save time.

• Flexibility and Adaptability:

It makes it easier to adapt to changes and accommodate new requirements. You can introduce new classes that inherit from existing ones and customize them as needed.

→ Types of Inheritance:

Single Inheritance

Single inheritance is a concept in object-oriented programming where a class can inherit properties and behaviors from only one single parent class.

In other words, a derived (child) class can have only one immediate superclass (parent class) from which it inherits.

//super or parent class
class Addition {
    void add(){
        int x=20,y=10;
        int sum=x+y;
        System.out.println("Addition="+sum);
    }
}
//child or derived class
class Subtraction extends Addition {
    void sub(){
        int x=30,y=10;
        int difference=x-y;
        System.out.println("Subraction="+difference);
    }
}
public class practice {
    public static void main(String[] args) {
        //creating object of derived class
        Subtraction obj=new Subtraction();
        //calling method
        obj.add();
        obj.sub();
    }
}

In the above example, you can see there are only two classes (addition and subtraction) used in which addition class is inherited by subtraction class.

Multiple Inheritance

Multiple inheritance is a concept in object-oriented programming where a class can inherit properties and behaviors from more than one parent class.

In other words, a derived (child) class can have multiple immediate superclasses (parent classes) from which it inherits.

Note: Java does not support multiple inheritance therefore interface is used to implement multiple inheritance.

Interface:

It is declared with interface keyword.
We cannot define function inside an interface, only can be declared.

//interface
  interface Addition {
    
    //declaring method
    void add();
    //note: we cannot define function inside interface
    }
    
    //Derived class
    class subtraction {
    void sub() {
    int x, y=30, z=10;
    x=y-z;
    System.out.println("Sub="+x);
    }
  }
  
  // derived class extending parent class and implementing interface
  class multiplication extends subtraction implements Addition {
   
   //implementing method of interface
   void add() {
   int x,y=30,z=10;
   x=y+z;
   System.out.println("Add="+x);
  }
   void multi() {
   int x,y=30,z=10;
   x=y*z;
   System.out.println("Multi="+x);
  }
}
class easy {
public static void main(String [] args) {

//creating object
multliplication obj=new mulitplication();
//calling methods
obj.add();
obj.sub();
obj.multi():
   }
 }
Multilevel Inheritance

When first class is inherited by second class, second class is inherited by third class and so on is called multilevel inheritance.

Each derived class is the base class for the next class.

At least three class is compulsory.

Example:

class Addition
{
  public void add()
  {
  int x,y=30,z=10;
  x=y+z;
  System.out.println("Add="+x);
  }  
}
//extending Addition
class Subtraction extends Addition
{
 void sub()
  {
  int x,y=30,z=10;
  x=y-z;
  System.out.println("Sub="+x);
  }  
}
//extending Subtraction
class Multiplication extends Subtraction
{
 void multi()
  {
  int x,y=30,z=10;
  x=y*z;
  System.out.println("Multiply="+x);
  }  
}
class Easy
{
 public static void main(String[] args)
 {
  //Creating instance(object)
  Multiplication obj=new Multiplication();
  obj.add();
  obj.sub();
  obj.multi();
 }
}
Hierarchal Inheritance

When a single class is inherited by two or more than two classes simultaneously is called hierarchial inheritance.

In this type of inheritance, a single parent class serves as the base for multiple child classes, creating a hierarchy of classes with shared properties and behaviors.

At least three class is compulsory.

Example:

class Addition
{
  public void add()
  {
  int x,y=30,z=10;
  x=y+z;
  System.out.println("Add="+x);
  }  
}
//extending Addition
class Subtraction extends Addition
{
 void sub()
  {
  int x,y=30,z=10;
  x=y-z;
  System.out.println("Sub="+x);
  }  
}
//extending same class Addition
class Multiplication extends Addition
{
 void multi()
  {
  int x,y=30,z=10;
  x=y*z;
  System.out.println("Multiply="+x);
  }  
}
class Easy
{
 public static void main(String[] args)
 {
  //Creating instance(object)
  Multiplication obj=new Multiplication();
  //calling base class function
  obj.add();
  //calling derive class function
  obj.multi();
 }
}
Hybrid Inheritance

Interface

Definition:

An interface in Java is a reference type that defines a contract of methods that implementing classes must adhere to.

It serves as a blueprint for classes.
It is declared with interface keyword.
We cannot create object of an interface.

Note: We cannot define function inside interface only can be declared.

A class is extended by a class but an interface is implemented by a class.

Declaring an Interface:

An interface is declared using the interface keyword. It contains method signatures (without method bodies) that the implementing classes must define.

interface Printable {
    void print();
}

Extending an Interface:

An interface can extend one or more other interfaces using the extends keyword. This allows you to inherit method signatures from multiple interfaces.

interface Drawable {
    void draw();
}

interface PrintableAndDrawable extends Printable, Drawable {
    // This interface inherits methods from Printable and Drawable
}

Implementing an Interface:

A class implements an interface using the “implements” keyword. It must provide concrete implementations for all the methods declared in the interface.

class Printer implements Printable {
    public void print() {
        System.out.println("Printing...");
    }
}

Example:

// Define an interface
interface Printable {
    void print();
}

// Implement the interface in a class
class Printer implements Printable {
    public void print() {
        System.out.println("Printing a document.");
    }
}

public class InterfaceExample1 {
    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Abstract Class

An abstract class in Java is a class that cannot be instantiated on its own and is meant to serve as a blueprint for other classes. It allows you to define methods that must be implemented by its subclasses. An abstract class can have both abstract (unimplemented) methods and concrete (implemented) methods. Subclasses of an abstract class must provide implementations for all the abstract methods defined in the abstract class.

Example:

abstract class Shape {
    abstract double calculateArea(); // Abstract method

    void display() {
        System.out.println("This is a shape.");
    }
}

class Circle extends Shape {
    private double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    double calculateArea() {
        return width * height;
    }
}

public class AbstractClassExample {
    public static void main(String[] args) {
        Circle circle = new Circle(5.0);
        Rectangle rectangle = new Rectangle(4.0, 3.0);

        circle.display();
        System.out.println("Area of circle: " + circle.calculateArea());

        rectangle.display();
        System.out.println("Area of rectangle: " + rectangle.calculateArea());
    }
}

In this example, the Shape class is an abstract class with an abstract method calculateArea() and a concrete method display(). The Circle and Rectangle classes extend Shape and provide implementations for the calculateArea() method. The main method demonstrates creating instances of both subclasses and invoking their methods.

Dynamic method dispatch:

Dynamic method dispatch is a concept in object-oriented programming languages like Java that allows a call to an overridden method to be resolved at runtime based on the actual type of the object, rather than at compile time based on the reference type. This enables polymorphism, where objects of different classes can be treated as objects of a common superclass or interface, and their specific implementations are called based on their actual runtime types.

Example:

class Shape {
    void draw() {
        System.out.println("Drawing a shape.");
    }
}

class Circle extends Shape {
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Rectangle extends Shape {
    void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

public class DynamicMethodDispatch {
    public static void main(String[] args) {
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();

        shape1.draw(); // Calls draw() method of Circle class
        shape2.draw(); // Calls draw() method of Rectangle class
    }
}

In this example, the Shape class has a draw() method. Both Circle and Rectangle subclasses override this method with their specific implementations. The main method demonstrates dynamic method dispatch by creating instances of Circle and Rectangle and assigning them to Shape reference variables. When calling the draw() method on these references, the appropriate overridden version is invoked based on the actual object type. This showcases the dynamic resolution of method calls at runtime.

Exception Handling

→ Definition:

Exception handling is a mechanism in Java that allows you to manage and handle runtime errors or exceptional situations that can occur during the execution of a program.

• These exceptions might include errors like division by zero, accessing an array out of bounds, or attempting to open a non-existent file.

• Java provides a structured approach to exception handling through the use of trycatchfinally, and throw keywords.

• The primary goal of exception handling is to gracefully handle errors and prevent abrupt termination of the program.

→There are 4 main keywords used to slove the problem of exception:

• try Blocks:

The try block encloses the code that might raise an exception. If an exception occurs within the try block, it is caught and handled by the corresponding catch block.

try {
    // Code that might raise an exception
} catch (ExceptionType e) {
    // Exception handling code for ExceptionType
}

• catch Blocks:

catch block follows a try block and specifies the type of exception it can handle. If an exception of that type is thrown in the try block, the corresponding catch block’s code is executed.

try {
    // Code that might raise an exception
} catch (ExceptionType e) {
    // Exception handling code for ExceptionType
}

• throw statement:

It is used to show the user-defined message about the exception.

throw new SomeException("This is an exceptional situation.");

• throws:

The throws keyword in Java is used to declare that a method might throw a specific type of exception during its execution. It appears in the method signature to indicate that the method could potentially generate an exception of the declared type.

public void someMethod() throws SomeException {
    // Method code that might throw SomeException
}

• Finally block:

The finally block is used to define a section of code that will be executed regardless of whether an exception was occured or not.

try {
    // Code that might raise an exception
} catch (Exception e) {
    // Exception handling code
} finally {
    // Code that will be executed in any case
}

In this example, the code within the finally block will be executed no matter whether an exception occurred or not within the try block.

→ Types of Exception:

In Java, exceptions are categorized into three main types:
• Checked exceptions
• Unchecked exceptions (also known as runtime exceptions),
• Errors

• Checked Exceptions:

Checked exceptions are exceptions that the compiler requires you to handle explicitly using try-catch blocks or declare them using the throws keyword in the method signature.

They are generally caused by external factors beyond the programmer’s control, such as file I/O errors or network connectivity issues.

Checked exceptions are subclasses of Exception but not subclasses of RuntimeException.

∴ Examples of checked exceptions:

• IOException: Signals input or output-related errors.
 SQLException: Represents database access errors.
• FileNotFoundException: Indicates that a file cannot be found.

• Unchecked Exceptions (Runtime Exceptions):

Unchecked exceptions, also known as runtime exceptions, do not need to be explicitly caught or declared in the method signature.

• They often result from programming errors like invalid calculations or incorrect array indexing.

• Unchecked exceptions are subclasses of RuntimeException.

∴ Examples of unchecked exceptions:

• ArithmeticException: Occurs during arithmetic calculations, such as division by zero.
• NullPointerException: Occurs when trying to access a method or field of a null object.
• ArrayIndexOutOfBoundsException: Occurs when accessing an array with an invalid index.

public class UncheckedExceptionExample {
    public static void main(String[] args) {
        int[] numbers = { 1, 2, 3 };
        try {
            System.out.println(numbers[5]); // Accessing an invalid index
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("An error occurred: " + e.getMessage());
        }
    }
}

Here, an ArrayIndexOutOfBoundsException is thrown because we’re trying to access an array element that doesn’t exist (index 5) in the numbers array.

• Errors:

Errors are exceptional conditions that usually arise due to serious problems that are beyond the control of the application.

• Unlike exceptions, errors should not be caught or handled by your code, as they indicate critical failures that typically cannot be recovered from.

• Common examples of errors include OutOfMemoryError and StackOverflowError.

public class ErrorExample {
    public static void main(String[] args) {
        int[] array = new int[Integer.MAX_VALUE]; // Attempting to create a huge array
    }
}

In this example, an OutOfMemoryError occurs because we’re trying to allocate a massive array that exceeds the available memory.

→ Uncaught Exception:

Uncaught exceptions are exceptions that are not caught and handled within your code using try-catch blocks.

When an uncaught exception occurs, the program prints an error message known as the “stack trace,” which provides information about the sequence of method calls that led to the exception.

Here’s an example of an uncaught exception:

public class UncaughtExceptionExample {
    public static void main(String[] args) {
        int result = divide(10, 0); // Division by zero
        System.out.println("Result: " + result);
    }

    static int divide(int a, int b) {
        return a / b; // This line will throw an ArithmeticException
    }
}

In this example, the divide() method attempts to divide by zero, which raises an ArithmeticException. Since there’s no try-catch block to catch and handle the exception, it becomes an uncaught exception, causing the program to terminate and printing a stack trace indicating the error.

→ Java`s built-in exceptions:

• ArithmeticException:
Raised during arithmetic operations such as division by zero.

• NullPointerException:
Raised when an object reference points to null and an attempt is made to access its methods or fields.

• ArrayIndexOutOfBoundsException:
Raised when an invalid array index is used to access an array element.

• StringIndexOutOfBoundsException:
Raised when an invalid index is used to access characters in a string.

• NumberFormatException:
Raised when a numeric conversion is performed on a string that is not a valid numeric representation.

• IllegalArgumentException:
Raised when an illegal argument is passed to a method.

• IllegalStateException:
Raised when the state of an object is incompatible with the requested operation.

• ClassCastException:
Raised when an invalid cast is attempted between incompatible types.

• FileNotFoundException:
Raised when an attempt to access a file that does not exist occurs.

• IOException:
The base class for exceptions related to input-output operations, such as reading/writing files.

• InterruptedException:
Raised when a thread is waiting, sleeping, or otherwise occupied and is interrupted.

• NoSuchElementException:
Raised when attempting to access an element that does not exist, such as in collections.

• IndexOutOfBoundsException:
A generic exception for index-related errors, like ArrayIndexOutOfBoundsException and StringIndexOutOfBoundsException.

• RuntimeException:
The base class for unchecked exceptions, covering a wide range of runtime errors like division by zero, null pointer, etc.

• Error:
The base class for serious errors that usually cannot be handled by the application, such as OutOfMemoryError and StackOverflowError.

These are just a few examples from the extensive set of built-in exceptions in Java. Understanding these exceptions and their use cases can help you effectively handle errors and exceptional situations in your Java programs.

Generics and Modules

→ Definition:

Generics in Java are a programming feature that allow you to create classes, interfaces, and methods with placeholders for data types.

• They enable you to define classes or methods that can work with different data types while maintaining type safety at compile time.

• Generics provide a way to write more reusable and type-safe code by parameterizing types without sacrificing performance.

• In other words, generics allow you to write code that is not tied to a specific data type but can adapt to different types when used. This helps avoid code duplication and improves the readability and maintainability of your programs.

→ Importance of Generic programming:

• Code Reusability: Generics allow you to write classes, methods, and interfaces that can work with various data types without duplicating code.

• Type Safety: Generics provide compile-time type checking, reducing the chances of runtime errors caused by incompatible data types.

• Compile-Time Errors: Generics help catch type-related errors at compile time, making it easier to identify and fix issues before runtime.

• Reduced Bugs: By ensuring the correct usage of types, generics help in reducing bugs and improving software quality.

• Flexibility: Generics enable the creation of adaptable and flexible classes that can be reused with different data types.

• Performance: Generics do not introduce runtime overhead since type information is resolved at compile time.

• Enhanced Readability: Code using generics is often easier to understand because it explicitly states the intended data types.

• Reduced Redundancy: Generics reduce the need to duplicate code for different data types, which leads to cleaner and less error-prone codebases.

→ Generic Classes:

A generic class is a class that can work with different types of data while maintaining type safety.

• It is defined by specifying a type parameter in angle brackets (<>). The type parameter is then used throughout the class to represent the actual data type.

Here’s the syntax for defining a generic class:

class ClassName<T> {
    // Members and methods using type parameter T
}

Here’s an example of a generic class Box that can store values of any type:

class Box<T> {
    private T value;

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

    public T getValue() {
        return value;
    }
}

→ Generic Methods:

generic method is a method that can operate on different data types.

• It is defined by specifying a type parameter in angle brackets just before the return type. The type parameter can be different from the class’s type parameter (if any).

Here’s the syntax for defining a generic method:

returnType methodName<T>(parameters) {
    // Method body using type parameter T
}

Here’s an example of a generic method printArray that can print elements of an array of any type:

class GenericMethodExample {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = { 1, 2, 3 };
        Double[] doubleArray = { 1.1, 2.2, 3.3 };
        String[] stringArray = { "Hello", "World" };

        printArray(intArray);
        printArray(doubleArray);
        printArray(stringArray);
    }
}

In this example, the printArray method is defined with a type parameter T, and it can work with arrays of various types.

→ Bounds for Type Variables:

Bounds for type variables in generics allow you to restrict the set of possible types that can be used as type arguments when working with generic classes or methods. There are two types of bounds: upper bounds and lower bounds.

  1. Upper Bounds:

An upper bound specifies that the type argument must be a subtype of a certain class or interface. This ensures that the type parameter will only accept types that are compatible with the specified upper bound.

Here’s the syntax for an upper-bounded type parameter:

class ClassName<T extends UpperBoundType> {
    // ...
}

Example:

class NumberBox<T extends Number> {
    private T value;

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

    public T getValue() {
        return value;
    }
}

In this example, NumberBox can only work with types that are subclasses of Number (e.g., Integer, Double, etc.).

→ Lower Bounds:

lower bound specifies that the type argument must be a supertype of a certain class. This allows for more flexibility, as it allows the type parameter to accept types that are the specified class or any of its superclasses.

Here’s the syntax for a lower-bounded type parameter:

class ClassName<T super LowerBoundType> {
    // ...
}

Example:

class Container<T super String> {
    private T value;

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

    public T getValue() {
        return value;
    }
}

In this example, Container can work with types that are either String or any superclass of String.

→ Virtual Machine (Java Virtual Machine – JVM):

The Java Virtual Machine (JVM) is a key component of the Java platform. It’s responsible for executing Java bytecode, which is an intermediate representation of Java source code.

The JVM provides several essential functions:

• Platform Independence: Java programs are compiled into bytecode, which is then executed by the JVM. This enables “write once, run anywhere” capability, as the same bytecode can be executed on different platforms without modification.

• Memory Management: The JVM manages memory allocation and garbage collection. It automatically allocates memory for objects and deallocates memory from objects that are no longer in use, helping prevent memory leaks.

• Optimization: The JVM performs runtime optimizations, such as inlining and JIT compilation, to improve the performance of Java programs.

• Security: The JVM enforces security measures to protect against malicious code by providing features like classloading constraints and bytecode verification.

• Execution Environment: The JVM provides the runtime environment for executing Java programs. It manages threads, synchronization, and provides core functionalities like I/O operations.

→ Restrictions and Limitations of Generic Code:

• Type Erasure: In Java, due to type erasure, the type information of generics is removed during compilation. This can lead to certain limitations, such as not being able to directly access the type parameter’s class at runtime.

• Primitive Types: Generics in Java don’t work directly with primitive types (int, char, boolean, etc.). You need to use their corresponding wrapper classes (Integer, Character, Boolean, etc.) instead.

• Array Creation: You cannot create arrays of a generic type directly. This is because arrays require the component type to be known at compile time, and generic types are erased at runtime.

• Static Contexts: You cannot use type parameters in static fields or static methods of a generic class, as type parameters are associated with instances.

• Unchecked Cast Warnings: When casting from a generic type to a specific type, you might encounter unchecked cast warnings, which can indicate potential type-related issues.

• Complex Syntax: The syntax of generic code can be complex, especially when dealing with multiple type parameters, wildcards, and bounds.

→ Restrictions and Limitations of the Java Virtual Machine (JVM):

• Memory Overhead: The JVM adds some memory overhead due to its runtime environment and features, compared to directly compiling to native machine code.

• Performance Overhead: While modern JVMs are optimized, Java programs may experience slightly slower startup times and early execution compared to fully compiled languages.

• Platform-Dependent Features: Despite Java’s platform independence, certain JVM features can be platform-dependent, potentially causing slight variations in behavior between different platforms.

• Resource Consumption: The JVM requires system resources to execute Java programs, which could impact applications with strict resource constraints.

• Version Compatibility: Sometimes, new versions of the JVM might introduce changes that lead to compatibility issues with older Java programs.

• Security Concerns: While the JVM includes security features, there can still be security vulnerabilities due to bugs or misconfigurations.

• Lack of Real-Time Control: The JVM’s garbage collection and other background processes might introduce unpredictable latencies in real-time applications.

• Limited Hardware Interaction: The JVM abstracts away direct hardware interaction, which could limit the performance optimization opportunities for certain applications.

→ Wildcard:

In Java, a wildcard is a special symbol that can be used as a type argument when working with generic classes, methods, or interfaces.

• Wildcards are denoted by the symbol and provide flexibility when dealing with unknown or varied types in generic code.
• They allow you to work with a range of different types without specifying the exact type.

There are two main types of wildcards in Java generics:

• Upper Bounded Wildcard (? extends Type): This wildcard restricts the type argument to be a specific type or its subtypes. It ensures that the provided type argument is compatible with the upper bound. It’s used when you want to read values from a generic type.

Example: List means a list of any type that extends Number.

• Lower Bounded Wildcard (? super Type): This wildcard restricts the type argument to be a specific type or its supertypes. It ensures that the provided type argument is compatible with the lower bound. It’s used when you want to add values to a generic type.

Example: List means a list of any type that is a superclass of Integer.

Wildcards are useful when you want to write generic code that can handle a variety of types while maintaining type safety. They allow you to create more flexible and adaptable methods and classes that can work with different types of data without having to specify the exact type.

Here’s a simple example of using wildcards:

import java.util.List;

public class WildcardExample {
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0;
        for (Number number : list) {
            sum += number.doubleValue();
        }
        return sum;
    }

    public static void main(String[] args) {
        List<Integer> integers = List.of(1, 2, 3);
        List<Double> doubles = List.of(1.1, 2.2, 3.3);

        double intSum = sumOfList(integers);
        double doubleSum = sumOfList(doubles);

        System.out.println("Sum of integers: " + intSum);
        System.out.println("Sum of doubles: " + doubleSum);
    }
}

In this example, the sumOfList method accepts a list of any type that extends Number. The ? extends Number wildcard allows the method to work with both Integer and Double lists.

→ Reflection:

Reflection in Java allows you to examine and manipulate classes, methods, fields, and other components of a program during runtime.

• It provides the ability to access metadata about classes and objects, instantiate classes dynamically, and invoke methods even when their names are not known at compile time.

Reflection can be used to:

• Inspect class information, fields, and methods.
• Create instances of classes at runtime.
• Access and modify private fields and methods (though not recommended).
• Dynamically invoke methods using their names as strings.
• Build generic tools that work with different classes at runtime.

→ Relationship between Reflection and Generics:

Reflection and generics can complement each other in certain scenarios:

Reflection can be used to inspect and manipulate the generic types of classes, methods, and fields. This can be helpful when you need to determine the actual type arguments used in generic code.
Reflection can be used to create instances of generic classes with specific type arguments, enabling dynamic instantiation of specialized components.
Reflection can help you work with classes and types that are not known at compile time, which is valuable when dealing with generics.

→ Modules:

In Java, a module is a self-contained unit of code that encapsulates a set of related classes, interfaces, and resources. Modules were introduced in Java 9 as part of the Java Platform Module System (JPMS) to enhance the organization, maintainability, and encapsulation of code in large-scale applications.

Key characteristics of modules include:

• Encapsulation: Modules provide a way to encapsulate classes and packages, allowing you to control which parts of your code are exposed to other modules. This helps prevent unintended access and reduces the risk of naming conflicts.

• Dependencies: Modules explicitly declare their dependencies on other modules. This promotes modular design by making dependencies explicit and providing better control over which parts of a module are accessible to other modules.

• Access Control: Modules can specify which packages are accessible to other modules and which are not. This helps enforce access restrictions and improves the overall security of the application.

• Automatic Modules: Libraries that are not yet modularized can be used as automatic modules. These modules are automatically generated based on JAR files and can be used while migrating to a modular project.

• Module Descriptors: Each module is defined by a module descriptor file called module-info.java. This file contains metadata about the module, such as its name, dependencies, exported packages, and required services.

• Explicit Dependencies: Modules must explicitly declare their dependencies using the requires keyword in the module descriptor. This helps ensure that the required dependencies are available at runtime.

• Strong Encapsulation: By default, classes in a module are not accessible to other modules unless explicitly exported. This enhances encapsulation and reduces the likelihood of unintentional coupling between modules.

• Improved Performance: Modules can enable the compiler and runtime to perform optimizations by knowing the explicit dependencies, leading to potentially improved startup times and smaller memory footprints.

Here’s a simple example of a module descriptor:

module com.example.myapp {
    requires java.base;  // Dependency on the Java base module
    requires com.example.mylibrary;  // Dependency on another module
    exports com.example.myapp.api;  // Exports a package for other modules
}

String:

A string is a sequence of characters that represents textual data.

It is one of the most commonly used data types in programming and is used to store and manipulate text-based information.

Note: In some other progamming language, a string is an array of characters. This is not the case with Java. In Java, strings are objects.

Example of String:

public class StringExample {
    public static void main(String[] args) {
        String greeting = "Hello, ";
        String name = "Alice";
        String message = greeting + name; // String concatenation

        System.out.println(message); // Outputs: Hello, Alice
  }
}

StringBuffer:

In Java, StringBuffer is a mutable class that represents a sequence of characters. Unlike the String class, which is immutable, StringBuffer allows you to modify its content without creating a new object each time. This can be beneficial when you need to perform multiple modifications to a string without incurring the overhead of creating new string objects.

Here’s an explanation of StringBuffer with an example:

public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("Hello");

        // Appending content to the StringBuffer
        stringBuffer.append(", World!");

        // Converting StringBuffer to String
        String result = stringBuffer.toString();

        System.out.println("Modified StringBuffer: " + result);
    }
}

// Output
// Modified StringBuffer: Hello, World

In this example:

We create a StringBuffer instance with the initial value “Hello”.
We use the append method to add “, World!” to the end of the existing content.
Finally, we convert the StringBuffer to a regular String using the toString method and print the result.

StringBuilder:

In Java, the StringBuilder class is another mutable alternative to the String class for creating and manipulating sequences of characters. Like StringBuffer, StringBuilder is more efficient than using repeated concatenations with String when you need to perform multiple modifications to a string. However, unlike StringBuffer, StringBuilder is not synchronized, making it more suitable for single-threaded environments.

Here’s an explanation of StringBuilder with an example:

public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder("Hello");

        // Appending content to the StringBuilder
        stringBuilder.append(", World!");

        // Converting StringBuilder to String
        String result = stringBuilder.toString();

        System.out.println("Modified StringBuilder: " + result);
    }
}

In this example:

We create a StringBuilder instance with the initial value “Hello”.
We use the append method to add “, World!” to the end of the existing content.
Finally, we convert the StringBuilder to a regular String using the toString method and print the result.

Primitive Type Wapper:

In Java, primitive type wrappers are classes that provide an object-oriented representation for the eight primitive data types.

In other word, Wrapper classes in java is a mechanism, that can convert primitive datatype to objects and objects to primitive datatypes.

When a primitive datatype is converted to wrapper class object then it is known as Autoboxing.
When the wrapper class object is converted to a primitive datatype, it is known as Unboxing.

The primitive type wrappers are:

  • Byte for byte
  • Short for short
  • Integer for int
  • Long for long
  • Float for float
  • Double for double
  • Character for char
  • Boolean for boolean

Yug

Leave a Reply

Your email address will not be published. Required fields are marked *