0% found this document useful (0 votes)
2 views

Java

The document outlines the key features of Java, including its object-oriented nature, platform independence, robustness, and automatic memory management. It also provides a step-by-step guide to writing and running a simple 'Hello World' program, along with explanations of Java's variables, data types, operators, and control statements. Overall, it serves as a comprehensive introduction to Java programming concepts and syntax.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Java

The document outlines the key features of Java, including its object-oriented nature, platform independence, robustness, and automatic memory management. It also provides a step-by-step guide to writing and running a simple 'Hello World' program, along with explanations of Java's variables, data types, operators, and control statements. Overall, it serves as a comprehensive introduction to Java programming concepts and syntax.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 96

JAVA

Features of Java
1. Object-Oriented

Java allows data to be represented in the form of objects.

It promotes concepts like inheritance, encapsulation, polymorphism, and


abstraction.

2. Platform-Independent

Java programs are compiled into bytecode, which can run on any device
equipped with a Java Virtual Machine (JVM).

This "Write Once, Run Anywhere" (WORA) capability is one of Java's most
significant advantages.

3. Robust

Java emphasizes reliability and robustness through:

Strong memory management.

Exception handling mechanisms.

Type-checking features.

4. Interpreted and Compiled

Java code is first compiled into bytecode and then interpreted by the JVM
at runtime.

5. Automatic Memory Management

Java includes a Garbage Collector that automatically manages memory by


reclaiming unused objects.

This ensures efficient memory utilization.

6. Multi-Threaded

A thread is the smallest part of a process.

JAVA 1
Java can execute multiple threads simultaneously, enabling efficient
multitasking.

7. Java Development Kit (JDK)

To run Java programs on a machine, the JDK (Java Development Kit) is


required.

The JDK contains:

JRE (Java Runtime Environment)

JVM (Java Virtual Machine)

Compilers like javac .

8. JShell

Introduced in Java 9, JShell is a Java command-line tool.

It allows developers to write and execute Java code interactively.

JAVA 2
Hello World in java

1. Set up your development environment:


Ensure you have the following installed:

Java Development Kit (JDK): Download and install the JDK from the official
Oracle website.

IDE or Text Editor: You can use an IDE like IntelliJ IDEA, Eclipse, or a simple
text editor like VS Code.

2. Writing the "Hello World" program:


1. Create a new file: Open your text editor or IDE and create a new file named
HelloWorld.java .

2. Write the code: Add the following code to the file:

public class HelloWorld { // Class definition (class name must match file nam
e)
public static void main(String[] args) { // Main method - entry point
System.out.println("Hello, World!");
}
}

Explanation of the code:


: This defines a class named
public class HelloWorld HelloWorld . In Java, every
program must have at least one class.

public static void main(String[] args) : This is the main method, which serves as the
entry point of the program. The program starts executing from here.

public : Means the method can be accessed from anywhere.

: Means the method can be called without creating an object of the


static

class.

Hello World in java 1


void : Means the method does not return any value.

: An array of strings passed to the method (it can be used to


String[] args

pass command-line arguments).

System.out.println("Hello, World!"); : This line prints "Hello, World!" to the console.

3. Compiling and running the program:


Using Command Line:

1. Open your command prompt or terminal.

2. Navigate to the directory where the HelloWorld.java file is saved.

3. Compile the code:

javac HelloWorld.java

4. Run the compiled code:

java HelloWorld

Using an IDE:

Most IDEs have a built-in button to compile and run the program. Simply
click "Run" after writing the code.

Expected Output:
When you run the program, you should see:

Hello, World!

This is your first Java program! It demonstrates how Java code is structured, how
to define a class and the main method, and how to print output to the console.

Hello World in java 2


How Java Code Works?
When you install the JDK (Java Development Kit) on your computer, the JVM
(Java Virtual Machine) is also installed.

What is JVM?
JVM is the part of Java that runs your programs.

It takes the bytecode (a special code Java creates after compilation) and
converts it into instructions your computer can understand.

Why is Java Platform-Independent?


Java programs are compiled into bytecode, which can run on any computer.

This is why Java is "Write Once, Run Anywhere."

Why is JVM Platform-Dependent?


Even though Java is the same everywhere, the JVM works differently on
different operating systems (Windows, Mac, Linux, etc.).

Every OS has its own version of JVM.

How Java Code Works? 1


What Happens When You Run a Java Program?
1. Write Code
You write your Java program, for example:

public class HelloWorld {


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

2. Compile Code

The Java compiler ( javac ) converts your code into bytecode (a .class file).

This bytecode is the same no matter what computer you use.

3. Run Code

The JVM reads the bytecode and converts it into instructions your
operating system understands.

Since every OS has its own version of JVM, it knows how to work on that
system.

How Java Code Works? 2


Variables and Datatypes

Variables in Java
A variable is a container that stores data values. Every variable in Java must be
declared with a specific data type.

Types of Variables:
1. Local Variables

Declared inside a method or block.

Accessible only within that method or block.

Example:

public void display() {


int number = 10; // Local variable
System.out.println(number);
}

2. Instance Variables

Declared inside a class but outside methods.

Belongs to an object, and each object has its copy.

Example:

class Person {
String name; // Instance variable
}

3. Global Variables

Declared as static inside a class but outside any method, constructor, or


block.

Variables and Datatypes 1


Shared across all instances of the class, as it belongs to the class rather
than an object.

Can be accessed directly using the class name.

Example:

public class Main {


static int MAX_USERS = 100; // Global variable

public static void main(String[] args) {


System.out.println(MAX_USERS); // Accessing global variable
}
}

Key Notes:

Global variables are part of the class and have a single copy in
memory.

They can be accessed anywhere within the class or even outside the
class (if public) using the class name.

Careful use is necessary to avoid unintended side effects.

Data Types in Java


Java has two types of data types:

1. Primitive Data Types (for storing simple values)

2. Non-Primitive Data Types (for storing complex objects)

1. Primitive Data Types


These are the basic building blocks of Java. There are 8 types:

Data Type Size Default Value Example

byte 1 byte 0 byte b = 10;

short 2 bytes 0 short s = 100;

Variables and Datatypes 2


int 4 bytes 0 int i = 1000;

long 8 bytes 0L long l = 10000L;

float 4 bytes 0.0f float f = 3.14f;

double 8 bytes 0.0 double d = 3.14159;

char 2 bytes '\u0000' char c = 'A';

boolean 1 bit false boolean b = true;

2. Non-Primitive Data Types


These are user-defined or provided by Java to store complex data. Examples
include:

String: Stores sequences of characters.

String name = "Java";

Arrays: Stores multiple values of the same type.

int[] numbers = {1, 2, 3};

Classes and Objects: To model real-world entities.

class Person {
String name;
}

Variables and Datatypes 3


Type Conversions
Type conversion (also known as type casting) is the process of converting a value
from one data type to another.

Types of Type Conversions in Java


1. Implicit (Widening) Type Casting

Definition: This happens automatically when converting a lower data type


to a higher data type.

Example: When converting an int to a long , Java does it automatically


since long can store larger values.

Rule: The data types must be compatible, and the conversion will not lose
data.

Examples:

int i = 7;
long l = i; // int to long (Widening)
float f = l; // long to float (Widening)
double d = f; // float to double (Widening)

Widening Conversion happens when converting from smaller to larger


data types:

byte → short → int → long → float → double

2. Explicit (Narrowing) Type Casting

Definition: This requires the programmer to explicitly tell Java to convert a


higher data type to a lower data type. This may cause data loss.

Example: Converting a double to an int will lose the decimal part.

Syntax:

Type Conversions 1
int i = (int) 7.5; // double to int (Explicit)

Narrowing Conversion occurs when converting from larger to smaller


data types:

double → float → long → int → short → byte

Type Conversions 2
Operators in Java
Operators in Java are special symbols or keywords used to perform operations on
variables and values. Java has several types of operators:

1. Arithmetic Operators
Arithmetic operators are used to perform basic mathematical operations like
addition, subtraction, multiplication, division, and modulus.

Operator Operation Example


+ Addition a+b

- Subtraction a-b

* Multiplication a*b

/ Division a/b

% Modulus (Remainder) a%b

2. Relational (Comparison) Operators


Relational operators are used to compare two values. They return true or false .

Operator Description Example


== Equal to a == b

!= Not equal to a != b

> Greater than a>b

< Less than a<b

>= Greater than or equal to a >= b

<= Less than or equal to a <= b

3. Logical Operators
Logical operators are used to combine multiple conditions (boolean expressions).
These return true or false .

Operators in Java 1
Operator Description Example
&& Logical AND a > b && b > 0

|| Logical OR a > b || b > 0

! Logical NOT !(a > b)

4. Assignment Operators
Assignment operators are used to assign values to variables. There are several
variations of the assignment operator.

Operator Description Example


= Simple assignment a = 10;

+= Add and assign a += 5;

-= Subtract and assign a -= 5;

*= Multiply and assign a *= 5;

/= Divide and assign a /= 5;

%= Modulus and assign a %= 5;

5. Bitwise Operators
Bitwise operators are used to perform operations on the binary representations of
integers. These operators work on bits and perform bit-by-bit operations.

Operator Description Example


& AND a&b

` ` OR
^ XOR (exclusive OR) a^b

~ NOT (bitwise negation) ~a

<< Left shift a << 2

>> Right shift a >> 2

>>> Unsigned right shift a >>> 2

Example:

Operators in Java 2
int a = 5, b = 3;
int andResult = a & b; // 1 (0101 & 0011 = 0001)
int orResult = a | b; // 7 (0101 | 0011 = 0111)
int xorResult = a ^ b; // 6 (0101 ^ 0011 = 0110)
int notResult = ~a; // -6 (~0101 = 1010 in two's complement)
int leftShift = a << 1; // 10 (0101 << 1 = 1010)
int rightShift = a >> 1; // 2 (0101 >> 1 = 0010)

Operators in Java 3
Control Statements
Control statements are used to control the flow of execution in a Java program.
They allow you to make decisions, repeat actions, or jump between different parts
of your program. Java provides several types of control statements:

1. Conditional (Selection) Statements


Conditional statements allow you to make decisions based on conditions.

if Statement
The if statement is used to test a condition and execute a block of code if the
condition is true .
Syntax:

if (condition) {
// Code to be executed if condition is true
}

if-else Statement
The if-else statement is used when you have two possible outcomes based on a
condition.
Syntax:

if (condition) {
// Code to be executed if condition is true
} else {
// Code to be executed if condition is false
}

if-else if-else Statement

Control Statements 1
This statement allows you to test multiple conditions.
Syntax:

if (condition1) {
// Code if condition1 is true
} else if (condition2) {
// Code if condition2 is true
} else {
// Code if neither condition1 nor condition2 is true
}

switch Statement
The switch statement is used when you have multiple possible conditions for a
variable, and you want to choose between them.
Syntax:

switch (expression) {
case value1:
// Code to be executed if expression equals value1
break;
case value2:
// Code to be executed if expression equals value2
break;
default:
// Code to be executed if expression doesn't match any value
}

2. Looping (Iteration) Statements

Looping statements allow you to execute a block of code multiple times.

for Loop
The for loop is used when the number of iterations is known in advance.

Control Statements 2
Syntax:

for (initialization; condition; increment/decrement) {


// Code to be executed
}

Example:

for (int i = 1; i <= 5; i++) {


System.out.println(i);
}

while Loop
The while loop is used when the number of iterations is not known in advance, and
the loop continues until the condition is false .
Syntax:

while (condition) {
// Code to be executed
}

Example:

int i = 1;
while (i <= 5) {
System.out.println(i);
i++;
}

do-while Loop
The do-while loop is similar to the while loop, but it guarantees that the block of
code is executed at least once, even if the condition is false .

Syntax:

Control Statements 3
do {
// Code to be executed
} while (condition);

Example:

int i = 1;
do {
System.out.println(i);
i++;
} while (i <= 5);

3. Jump (Transfer of Control) Statements


Jump statements are used to transfer control to another part of the program.

break Statement
The break statement is used to exit from a loop or a switch statement prematurely.

Example:

for (int i = 1; i <= 5; i++) {


if (i == 3) {
break; // Exit the loop when i is 3
}
System.out.println(i);
}

continue Statement
The continue statement is used to skip the current iteration of a loop and proceed
with the next iteration.

Example:

Control Statements 4
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue; // Skip this iteration when i is 3
}
System.out.println(i);
}

return Statement
The return statement is used to exit from a method and optionally return a value.
Example:

public class Test {


public static void main(String[] args) {
int result = sum(5, 3);
System.out.println("Sum: " + result);
}

public static int sum(int a, int b) {


return a + b; // Exit the method and return the result
}
}

Ternary Operator

The ternary operator is a shorthand for an if-else statement. It allows you to assign
a value to a variable based on a condition in a more compact form.

Syntax:

condition ? expression1 : expression2;

condition: A boolean expression that evaluates to either true or false .

expression1: The value that will be returned if the condition is true .

Control Statements 5
expression2: The value that will be returned if the condition is false .

How It Works:
If the condition is true , the operator returns expression1.

If the condition is false , it returns expression2.

Example:

public class TernaryExample {


public static void main(String[] args) {
int a = 10, b = 20;

// Using ternary operator to find the larger number


int larger = (a > b) ? a : b;
System.out.println("The larger number is: " + larger);

// Using ternary operator to check if a number is even or odd


String result = (a % 2 == 0) ? "Even" : "Odd";
System.out.println("The number is: " + result);
}
}

Output:

The larger number is: 20


The number is: Even

Control Statements 6
Methods in java
In Java, the terms function and method are often used interchangeably. A
method is a block of code that performs a specific task and is usually part of a
class. A function is essentially the same as a method in Java, but in other
programming languages, it may not be part of a class.

1. Declaring a Method (Function)


To define a method, you need to specify the following:

Access Modifier: Defines the visibility of the method (e.g., public , private ).

Return Type: The type of value the method will return (e.g., int , void ).

Method Name: A unique name for the method.

Parameters (optional): The input values the method will use to perform its
task.

Syntax:

accessModifier returnType methodName(parameters) {


// method body
}

Example: A method that adds two numbers:

public int add(int a, int b) {


return a + b;
}

Here:

public : The method is accessible from anywhere.

int : The method returns an integer.

add : The method name.

Methods in java 1
int a, int b : Parameters (inputs) of the method.

2. Calling a Method
To call (or invoke) a method, you need to:

Create an object of the class (unless the method is static).

Use the object to call the method (if the method is not static).

If the method is static, you can call it directly using the class name.

Example: Creating an object and calling the add method:

MyClass obj = new MyClass(); // Create an object of the class


int result = obj.add(5, 10); // Call the add method with arguments 5 and 10
System.out.println(result); // Output will be 15

If the method is static, you can call it without creating an object:

int result = MyClass.add(5, 10); // Call static method directly


System.out.println(result); // Output will be 15

Pass by Value:

A copy of the actual value is passed to the method.

Changes made to the parameter inside the method do not affect the original
value.

Example: In Java, primitives (e.g., int , float ) are passed by value.

Pass by Reference:

A reference (address) to the actual object is passed to the method.

Changes made to the parameter inside the method affect the original object.

Example: In Java, objects (e.g., arrays, custom classes) behave like pass by
reference.

Methods in java 2
(Note: Java technically uses pass by value for both primitives and objects, but for
objects, the "value" is the reference itself.)

Pass-by-Value in Java

Java always uses pass-by-value. This means that when you pass a variable to a
method, a copy of the variable's value is passed. This applies to both primitive
types and object references.

Primitive Types
For primitive types (e.g., int , char , double ), the value itself is copied. Any changes
made to the parameter inside the method do not affect the original variable.

Example:

public void modifyValue(int num) {


num = 10; // Modifies the copy of the original value
}

int original = 5;
modifyValue(original); // original remains 5
System.out.println(original); // Output: 5

Here, original remains 5 because modifyValue only modifies the copy of original .

Object References
For objects, Java passes a copy of the reference (memory address) to the object.
This means:

The reference itself is passed by value (a copy of the reference is made).

However, since the copied reference still points to the same object, changes to
the object's state (e.g., modifying fields or array elements) will affect the
original object.

Example:

Methods in java 3
public void modifyArray(int[] nums) {
nums[0] = 10; // Changes the first element of the original array
}

int[] originalArray = {1, 2, 3};


modifyArray(originalArray); // originalArray[0] will be changed to 10
System.out.println(originalArray[0]); // Output: 10

Here, originalArray is modified because the method receives a copy of the reference
to the same array object.

Java is strictly pass-by-value.

For primitives, the value itself is copied.

For objects, a copy of the reference (memory address) is passed.

Changes to the state of an object (e.g., modifying fields or array elements) are
visible outside the method.

Reassigning the reference inside the method does not affect the original
reference outside the method.

Important Question
1. In Java, when I pass a primitive type (e.g., int ) to a method and modify it
inside the method, the changes do not reflect on the original variable. For
example:

public void modifyValue(int num) {


num = 10; // Modifies the copy of the original value
}

int original = 5;
modifyValue(original); // original remains 5
System.out.println(original); // Output: 5

Methods in java 4
Why does this happen, and how can I modify the value of the original variable?

Answer:

In Java, primitive types (like int , float , boolean , etc.) are passed by value, meaning
that a copy of the variable is passed to the method. Changes made to the variable
inside the method only affect the copy, not the original value.

To modify the original value, you can use one of the following approaches:
1. Return the New Value

Modify the method to return the updated value and reassign it to the original
variable:

public int modifyValue(int num) {


num = 10; // Modify the value
return num; // Return the updated value
}

int original = 5;
original = modifyValue(original); // Update the original variable
System.out.println(original); // Output: 10

2. Use a Wrapper Class

Wrap the primitive value in an object so that changes to the object persist outside
the method:

Using AtomicInteger :

import java.util.concurrent.atomic.AtomicInteger;

public void modifyValue(AtomicInteger num) {


num.set(10); // Modify the value inside the wrapper
}

AtomicInteger original = new AtomicInteger(5);

Methods in java 5
modifyValue(original); // Modify the wrapper's value
System.out.println(original.get()); // Output: 10

Using a Custom Wrapper Class:

class Wrapper {
int value;

Wrapper(int value) {
this.value = value;
}
}

public void modifyValue(Wrapper wrapper) {


wrapper.value = 10; // Modify the value in the wrapper
}

Wrapper original = new Wrapper(5);


modifyValue(original); // Modify the wrapper's value
System.out.println(original.value); // Output: 10

3. Use an Array
Since arrays in Java are passed by reference, modifying an array element inside
the method will affect the original array:

public void modifyValue(int[] num) {


num[0] = 10; // Modify the first element of the array
}

int[] original = {5};


modifyValue(original); // Modify the array
System.out.println(original[0]); // Output: 10

4. Use Mutable Data Structures

Methods in java 6
You can use a mutable data structure, such as a List , to hold the value and modify
it:

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

public void modifyValue(List<Integer> num) {


num.set(0, 10); // Modify the first element of the list
}

List<Integer> original = new ArrayList<>();


original.add(5);
modifyValue(original); // Modify the list
System.out.println(original.get(0)); // Output: 10

Methods in java 7
Memory Allocation in Java
In Java, memory is allocated to variables and objects in two main areas:

1. Stack Memory

2. Heap Memory

1. Stack Memory

Definition: Stack memory is used for the execution of threads and stores
primitive types (like int , char , boolean , etc.) and references to objects (not the
objects themselves).

Characteristics:

Temporary storage: It is used to store method calls and local variables.

Faster access: Stack memory is much faster compared to heap memory.

LIFO (Last In First Out): Stack works on the principle of Last In First Out.
When a method is called, a block (frame) is pushed onto the stack, and
when the method finishes execution, that block is popped off.

Limited size: The stack memory is relatively small compared to heap


memory.

How it works:

When a method is invoked, the local variables and references to objects


are pushed onto the stack.

When the method finishes execution, the stack frame is removed, and the
space is freed.

Example:

public class StackExample {


public static void main(String[] args) {
int a = 5; // 'a' is stored in stack memory

Memory Allocation in Java 1


int b = 10; // 'b' is stored in stack memory
int sum = add(a, b); // Stack holds the call to 'add' method and its param
eters
System.out.println(sum);
}

public static int add(int x, int y) {


return x + y; // 'x' and 'y' are local variables, stored in stack
}
}

2. Heap Memory

Definition: Heap memory is used for dynamic memory allocation. Objects


and arrays are created in the heap memory. This memory is shared across all
threads.

Characteristics:

Dynamic memory: Objects are stored in the heap, and memory is


allocated at runtime.

Slower access: It is slower to access compared to stack memory due to


the need for garbage collection.

Garbage Collection: The heap is managed by the Garbage Collector in


Java, which automatically frees up memory when objects are no longer in
use.

Larger size: Heap memory is much larger compared to stack memory.

How it works:

Objects are created in the heap using the new keyword.

References to these objects are stored in the stack, and the actual object
data is stored in the heap.

When objects are no longer referenced, the garbage collector removes


them to free up memory.

Memory Allocation in Java 2


Example:

public class HeapExample {


public static void main(String[] args) {
Person person = new Person("Alice", 25);
// 'person' is a reference stored in stack
// The actual object is created in heap memory
System.out.println(person.name);
}

static class Person {


String name;
int age;

Person(String name, int age) {


this.name = name;
this.age = age;
}
}
}

In this example:

The person reference is stored in stack memory.

The Person object is created in heap memory.

Memory Management in Java

Automatic Garbage Collection: Java automatically manages memory by


reclaiming the space used by objects that are no longer referenced.

Heap Memory and Garbage Collection:

The garbage collector checks for objects that are no longer referenced
and frees up memory from the heap.

Memory Allocation in Java 3


The heap is divided into regions: Young Generation, Old Generation, and
Permanent Generation (or Metaspace in newer versions).

Example Code in Java:

public class MemoryManagementExample {


public static void main(String[] args) {
// Stack: Primitive data types are stored here
int num = 10; // Local variable, stored in stack

// Stack: Reference to the object is stored here


Person person = new Person("John", 25); // Reference to a Person object

// Heap: The object 'person' is created here


// person refers to the object in the heap, which contains the "name" and
"age" attributes.
person.displayInfo();
}
}

class Person {
String name;
int age;

Person(String name, int age) {


this.name = name;
this.age = age;
}

void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}

Memory Breakdown:

Memory Allocation in Java 4


Stack:

The local variable num with the value 10 .

The reference person , which holds the memory address of the Person

object.

Heap:

The Person object containing name ("John") and age (25) is allocated in the
heap.

This diagram shows the relationship between the stack and heap:

The stack contains the local variables and references.

The heap contains the actual object ( Person ) that the reference points to.

Important Questions
1. What is stored in the stack memory?

Local variables (primitive types and object references).

Memory Allocation in Java 5


Method calls and their execution context (e.g., return addresses,
parameters).

2. What is stored in the heap memory?

Objects and their instance variables.

Arrays.

Class-level variables (static variables).

3. Can you explain the lifecycle of an object in Java?

An object is created using the new keyword and stored in the heap
memory.

The reference to the object is stored in the stack memory.

The object remains in the heap memory until it is no longer referenced.

The garbage collector (GC) automatically reclaims the memory of


unreferenced objects.

4. What is garbage collection in Java?

Garbage collection is the process of automatically reclaiming memory by


identifying and deleting objects that are no longer reachable or in use.

It runs on the heap memory to free up space occupied by unreferenced


objects.

5. What happens if the stack memory is full?

If the stack memory is full, a StackOverflowError is thrown. This typically


happens due to excessive recursion or deeply nested method calls.

6. What happens if the heap memory is full?

If the heap memory is full and no more objects can be allocated, an


OutOfMemoryError is thrown.

7. Can you control the size of stack and heap memory in Java?

Memory Allocation in Java 6


Yes, you can control the size of stack and heap memory using JVM
options:

Stack Memory: Use Xss to set the stack size (e.g., Xss1m for 1 MB).

Heap Memory: Use Xms to set the initial heap size and Xmx to set the
maximum heap size (e.g., Xms256m -Xmx1024m ).

8. What is the difference between a memory leak and a stack overflow?

Memory Leak: A memory leak occurs when a program allocates memory


(typically in the heap) but fails to release it after it is no longer needed.
Over time, these unreleased memory blocks accumulate, reducing the
amount of available memory and potentially causing the application or
system to slow down, crash, or throw an OutOfMemoryError .

Stack Overflow: Occurs when the stack memory is exhausted, typically


due to excessive recursion or deeply nested method calls.

9. How does the new keyword work in terms of memory allocation?

The new keyword allocates memory in the heap for the object and returns
a reference to that memory location. The reference is stored in the stack
memory.

10. Can you explain the concept of memory fragmentation in the heap?

Memory fragmentation occurs when free memory is divided into small,


non-contiguous blocks, making it difficult to allocate large objects. It can
lead to inefficient memory usage and OutOfMemoryError .

Memory Allocation in Java 7


Shallow and Deep Copy
When you "copy" an object, you create a new object based on the original.
However, the degree of independence between the original and the copy depends
on the type of copying used.

1. Shallow Copy

Definition: A shallow copy creates a new object but does not duplicate the
internal objects (like fields, arrays, or collections). Instead, the new object and
the original share references to the internal objects.

Memory: Only the top-level object is new; the internal objects remain shared
between the original and the copy.

Impact: Changes made to shared internal objects in the copy will reflect in the
original and vice versa.

Shallow Copy Example

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

class Connection {
private String url;

Connection(String url) {
this.url = url;
}

@Override
public String toString() {
return "Connection{url='" + url + "'}";
}
}

Shallow and Deep Copy 1


class ConnectionPool {
private List<Connection> connections;

ConnectionPool(List<Connection> connections) {
this.connections = connections; // Shallow copy
}

@Override
public String toString() {
return "ConnectionPool{connections=" + connections + "}";
}
}

public class ShallowCopyExample {


public static void main(String[] args) {
// Original list of connections
List<Connection> connections = new ArrayList<>();
connections.add(new Connection("jdbc:mysql://localhost:3306/mydb"));

// Create a connection pool with a shallow copy


ConnectionPool pool = new ConnectionPool(connections);

// Modify the original list


connections.add(new Connection("jdbc:mysql://localhost:3306/otherd
b"));

// Output
System.out.println("Original Connections: " + connections);
System.out.println("Connection Pool: " + pool);
}
}

What Happens in Memory (Shallow Copy)

Shallow and Deep Copy 2


1. The connections list is created and contains one Connection object.

2. A ConnectionPool object is created, which takes the same connections list


reference.

3. When a new object is added to the connections list, it also affects the
Connection

ConnectionPool object because they share the same reference.

2. Deep Copy

Definition: A deep copy creates a new object and also recursively duplicates
all internal objects. Both the top-level object and the internal objects are
independent of the original.

Memory: The new object and all its internal objects are stored in separate
memory locations.

Impact: Changes made to the copy do not affect the original object and vice
versa.

Deep Copy Example

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

class Player {
String name;

Player(String name) {

Shallow and Deep Copy 3


this.name = name;
}

// Deep copy constructor for Player


Player(Player other) {
this.name = other.name;
}

@Override
public String toString() {
return "Player{name='" + name + "'}";
}
}

class GameState {
int level;
List<Player> players;

GameState(int level, List<Player> players) {


this.level = level;
this.players = new ArrayList<>();
for (Player player : players) {
this.players.add(new Player(player)); // Deep copy of players
}
}

@Override
public String toString() {
return "GameState{level=" + level + ", players=" + players + "}";
}
}

public class DeepCopyExample {


public static void main(String[] args) {
// Original game state
List<Player> players = new ArrayList<>();

Shallow and Deep Copy 4


players.add(new Player("Player1"));
players.add(new Player("Player2"));

GameState original = new GameState(1, players);

// Deep copy of the game state


GameState deepCopy = new GameState(original.level, original.players);

// Modify the deep copy


deepCopy.players.get(0).name = "PlayerX";

// Output
System.out.println("Original: " + original); // Players remain unchanged
System.out.println("Deep Copy: " + deepCopy); // Modified copy
}
}

What Happens in Memory (Deep Copy)

1. The players list is created with two Player objects.

2. A GameState object ( original ) is created, containing a new list where new Player

objects are created for each player in the original list.

3. The deepCopy is created independently of the original . Changes to deepCopy 's


Player objects do not affect original .

Shallow and Deep Copy 5


Aspect Shallow Copy Deep Copy

Memory Usage Less memory (shared references). More memory (separate objects).

Faster (only references are Slower (entire object graph is


Performance
copied). copied).

Not independent (shared internal


Independence Fully independent.
objects)

Changes Affects the original object. Does not affect the original object.

When to Use

Shallow Copy:

When objects are immutable or when shared references are acceptable.

If memory optimization is critical.

Deep Copy:

When objects are mutable and need full independence.

When modifications to the copy should not affect the original.

Shallow and Deep Copy 6


Data Structures
What are Data Structures?
Data structure is a storage that is used to store and organize data. It is a way of
arranging data on a computer so that it can be accessed and updated efficiently.

Classification of Data Structures:

Data structures generally fall under these two categories:

Linear data structures:Elements are arranged sequentially (e.g. arrays, linked


lists, stacks, queues)

Non-linear data structures:Elements are not arranged sequentially, but are


stored within different levels (e.g. trees, graphs)

Data Structures Types:

1. Arrays:

An array is a sequential list of values.

We can use arrays to store and access multiple values of the same data
type under one identifier.

Data Structures 1
Arrays make it easier to perform operations on an entire dataset.

There are one-dimensional arrays and multi-dimensional arrays.

The values stored in an array are called elements.

Elements are stored in consecutive blocks of memory called indexes.

2. Stack:

Stack follows the principle of LIFO (Last in First out) i.e. element which is
inserted at last will be removed first.

The operation for insertion of elements in stack is known as Push


operation and the operation for deletion of element in stack is known as
Pop operation.

3. Queue:

Queue follows the principle of FIFO (First in First out) i.e. element which is
inserted first will be removed first.

The operation for insertion of elements is known as enqueue operation


and the operation for deletion of elements is known as dequeue operation.

Data Structures 2
4. Linked List:

A linked list is a linear data structure, in which the elements are not stored
at contiguous memory locations.

The elements in a linked list are linked using pointers as shown in the
below image:

5. Binary Tree: A binary tree is a tree data structure in which each parent node
can have at most two children. Each node of a binary tree consists of three
items:

data item

address of left child

address of right child

Data Structures 3
6. Graph:

A Graph is a non-linear data structure consisting of nodes and edges.

The nodes are sometimes also referred to as vertices and the edges are
lines or arcs that connect any two nodes in the graph.

More formally a Graph can be defined as, A Graph consists of a finite set
of vertices(or nodes) and a set of Edges that connect a pair of nodes.

Data Structures 4
Arrays
An array is a collection of similar types of data stored in contiguous memory
locations. It allows us to store multiple values under one name and access them
using their index.

Why Do We Need Arrays?

1. Efficient Storage: Arrays allow storing multiple values in a single variable


instead of creating separate variables for each value.

2. Index-based Access: You can easily access any element using its index.(0
based indexing)

3. Static Size: Arrays have a fixed size, which helps in memory allocation and
management.

4. Group Operations: Perform operations (e.g., sort, search) on multiple


elements together.

How to Create an Array in Java

To create an array:

1. Declare the array.

2. Allocate memory for the array.

3. Initialize the array elements.

Syntax:

dataType[] arrayName = new dataType[size];

Example:

public class ArrayExample {


public static void main(String[] args) {

Arrays 1
int[] numbers = new int[5]; // Creates an array of size 5
numbers[0] = 10; // Initialize the first element
numbers[1] = 20;
numbers[2] = 30;
System.out.println("First Element: " + numbers[0]);
System.out.println("Array Length: " + numbers.length);
}
}

What Happens When We Create an Array?

1. Memory Allocation: A contiguous block of memory is reserved to store the


array elements.

2. Default Initialization:

Numeric types: 0

Char types: '\u0000'

Boolean: false

Objects: null

Here, we are using the int data type,


which occupies 4 bytes of memory.
Therefore, the first element will be
stored at memory location 100, the
second at 104, the third at 108, and so
on, with each subsequent element
placed 4 bytes apart.

Multi-Dimensional Arrays

A multi-dimensional array is an array of arrays. The most common type is the 2D


array, used to represent tables or matrices.
Syntax:

Arrays 2
dataType[][] arrayName = new dataType[rows][columns];

Example:

public class MultiDimensionalArray {


public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println("Element at (1,2): " + matrix[1][2]); // Access element a
t row 1, column 2
}
}

Jagged Arrays

A jagged array is an array of arrays where each sub-array can have a different
length.
Syntax:

dataType[][] arrayName = new dataType[rows][];

Example:

public class JaggedArray {


public static void main(String[] args) {
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2]; // First row has 2 elements
jaggedArray[1] = new int[3]; // Second row has 3 elements
jaggedArray[2] = new int[1]; // Third row has 1 element

// Initialize elements

Arrays 3
jaggedArray[0][0] = 10;
jaggedArray[0][1] = 20;
jaggedArray[1][0] = 30;

System.out.println("Element: " + jaggedArray[0][1]); // Access element


}
}

Operations on Arrays

1. Length: Use arrayName.length to know the number of elements present in an


array.

System.out.println(numbers.length);

2. Traversal: Access each element one by one.

for (int num : numbers) {


System.out.println(num);
}

3. Sorting: Use Arrays.sort(arrayName) to sort elements.

import java.util.Arrays;

public class ArraySort {


public static void main(String[] args) {
int[] arr = {5, 3, 8, 1};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}

4. Search: Use linear or binary search.

Arrays 4
import java.util.Arrays;

public class ArraySearch {


public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(arr, 3);
System.out.println("Index of 3: " + index);
}
}

Limitations of Arrays

1. Fixed size: You cannot resize an array once it's created.

2. Homogeneous: Only stores elements of the same type.

3. Static memory allocation: Unused elements waste memory.

Important Questions
1. What Happens if You Try to Access an Array Index Out of Bounds?

Java throws an ArrayIndexOutOfBoundsException at runtime if you try to access


an index that is less than 0 or greater than or equal to the array length.

2. Can You Change the Size of an Array After Creation?

No, the size of an array is fixed after creation. To resize an array, you need
to create a new array and copy the elements from the old array to the new
one.

3. What is the Difference Between a Primitive Array and an Object Array?

Primitive Array: Stores primitive data types (e.g., int , double ).

int[] arr = new int[5];

Arrays 5
Object Array: Stores objects (e.g., String , custom objects).

String[] arr = new String[5];

4. What is the Difference Between System.arraycopy() and Arrays.copyOf() ?

System.arraycopy() :

Copies elements from a source array to a destination array.

Requires manual creation of the destination array.

Example:

int[] src = {1, 2, 3};


int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, src.length);

Arrays.copyOf() :

Creates a new array and copies elements from the source array.

Automatically creates the destination array.

Example:

int[] src = {1, 2, 3};


int[] dest = Arrays.copyOf(src, src.length);

5. What is the Time Complexity of Accessing an Element in an Array?

Accessing an element in an array is O(1) because arrays provide direct


access to elements using their index.

6. What is the Difference Between Arrays.sort() and Collections.sort() ?

Arrays.sort() :

Sorts arrays of primitives or objects.

Uses a dual-pivot quicksort for primitives and TimSort for objects.

Arrays 6
Collections.sort() :

Sorts collections like ArrayList , LinkedList , etc.

Internally uses Arrays.sort() for sorting.

7. What is the Role of the Arrays Class in Java?

The Arrays class provides utility methods for working with arrays, such as:

Sorting ( Arrays.sort() )

Searching ( Arrays.binarySearch() )

Copying ( Arrays.copyOf() )

Comparing ( Arrays.equals() )

Filling ( Arrays.fill() )

8. When you execute System.out.println(arr); in Java, where arr is an array, the output
will be something like this:

[I@1b6d3586

Explain why does this happen ?

Explanation of the Output

1. [I :

The [ indicates that the object is an array.

The I indicates that the array is of type int (the letter corresponds to
the internal JVM representation of the data type).

2. @ :

The @ symbol separates the type information from the hash code.

3. 1b6d3586 :

This is the hash code of the array object, represented in hexadecimal.


It is the memory address or a unique identifier for the array object.

Why Does This Happen?

Arrays 7
In Java, arrays are objects. When you pass an object to System.out.println() ,
the toString() method of that object is called.

The default implementation of toString() in the Object class (which is the


superclass of all objects, including arrays) returns a string in the format:

ClassName@HashCode

Since arrays do not override the toString() method, the default


implementation is used, resulting in the output like [I@1b6d3586 .

Arrays 8
Strings
A String in Java is a sequence of characters. Strings are widely used in Java
programming and are immutable, meaning they cannot be changed after creation.

Why Do We Need Strings?

1. Text Handling: Strings allow easy representation and manipulation of text


data.

2. Immutability: Ensures security and thread safety, making it ideal for tasks like
password handling.

3. Rich Library Support: Java provides the java.lang.String class with numerous
methods for string manipulation.

4. Memory Efficiency: String literals are stored in the String pool, avoiding
unnecessary memory allocation

Ways to Create a String in Java


1. Using String Literals
When a string is directly assigned to a variable using double quotes, it is stored in
the String pool.

String s = "Hello";

2. Using the new Keyword


A string object can be created using the new keyword. This creates a new object
in the heap memory.
Example:

String s1 = new String("Hello");

Strings 1
3. Using Character Arrays
You can create a string by passing a character array to the String constructor.
Example:

char[] chars = {'H', 'e', 'l', 'l', 'o'};


String s = new String(chars);

5. Using String.valueOf()

This method converts different data types into a string.


Example:

int num = 42;


String s = String.valueOf(num);

String Pool and Immutability

1. String Pool: The String Pool is a special area of memory in the Java Heap.
Specifically, it resides in the Heap Memory section known as the Method
Area (or Metaspace in Java 8 and later).

2. Immutability:

Once a string is created, its value cannot be modified.

Any modification creates a new string, ensuring security and reducing


memory leaks.

Why Are Strings Immutable in Java?

In Java, String is not just a type but a class. Its immutability plays a significant role
in making it efficient and secure.

String name = "Abhiram";


name = "Sai" + name;

Strings 2
System.out.println(name);

What Happens Here?

1. Initially, the string name points to "Abhiram" in the String Pool (e.g., memory
location 101 ).

2. When concatenation ( "Sai" + name ) occurs, Java does not modify the original
string. Instead:

A new string "Sai Abhiram" is created in the String Pool (e.g., memory
location 105 ).

The reference variable name is updated to point to the new string ( 105 ).

3. The old string "Abhiram" at memory location 101 remains in the pool and may
eventually be cleaned up by the garbage collector if not referenced.

Thus, the original string is never modified, and a new string is always created for
changes. This behavior demonstrates immutability.

String Pool Optimization

String s1 = "Abhiram";
String s2 = "Abhiram";

Does Java Create Two Strings?


No. Both s1 and s2 point to the same memory location in the String Pool, as
Java optimizes memory usage by storing only one copy of a string literal in the
pool.

Strings 3
Key Takeaways

Strings Are Immutable: Any modification to a string results in the creation of a


new string, leaving the original unchanged.

Memory Efficiency: String literals with the same value share the same location
in the String Pool.

Security: Immutability ensures that strings cannot be altered, making them


safe for sensitive operations like passwords and network connections.

Common String Operations

1. Concatenation: Combines two or more strings.

String s1 = "Hello";
String s2 = "World";
String result = s1 + " " + s2; // Outputs: Hello World

2. Length: Returns the length of the string.

String s = "Java";
System.out.println(s.length()); // Outputs: 4

3. Substring: Extracts a part of the string.

Strings 4
String s = "Programming";
System.out.println(s.substring(0, 6)); // Outputs: Progra

4. Comparison

a. Equals: Compares two strings for equality.

String s1 = "Java";
String s2 = "Java";
System.out.println(s1.equals(s2)); // Outputs: true

b. Equals Ignore Case: Compares two strings ignoring case differences.

String s1 = "Java";
String s2 = "java";
System.out.println(s1.equalsIgnoreCase(s2)); // Outputs: true

c. Compare To: Compares strings lexicographically.

String s1 = "Apple";
String s2 = "Banana";
System.out.println(s1.compareTo(s2)); // Outputs: -1 (negative if s1 < s2)

5. Search
a. Index Of: Finds the first occurrence of a character or substring.

String s = "Hello World";


System.out.println(s.indexOf("World")); // Outputs: 6

b. Last Index Of: Finds the last occurrence of a character or substring.

String s = "Programming";
System.out.println(s.lastIndexOf('g')); // Outputs: 10

Strings 5
6. Replace: Replaces characters or substrings.

String s = "apple";
System.out.println(s.replace('a', 'A')); // Outputs: Apple

7. Starts With: Checks if the string starts with a specified prefix.

String s = "Hello World";


System.out.println(s.startsWith("Hello")); // Outputs: true

8. Ends With: Checks if the string ends with a specified suffix.

String s = "Hello World";


System.out.println(s.endsWith("World")); // Outputs: true

9. To Upper Case: Converts the string to uppercase.

String s = "hello";
System.out.println(s.toUpperCase()); // Outputs: HELLO

10. To Lower Case: Converts the string to lowercase.

String s = "HELLO";
System.out.println(s.toLowerCase()); // Outputs: hello

11. Trim: Removes leading and trailing whitespaces.

String s = " Hello World ";


System.out.println(s.trim()); // Outputs: Hello World

12. Split: Splits the string into an array of substrings based on a delimiter.

Strings 6
String s = "Java,Python,C++";
String[] languages = s.split(",");
for (String lang : languages) {
System.out.println(lang);
}
// Outputs:
// Java
// Python
// C++

13. Char At: Returns the character at a specified index.

String s = "Hello";
System.out.println(s.charAt(1)); // Outputs: e

14. Contains: Checks if the string contains a specified substring.

String s = "Hello World";


System.out.println(s.contains("World")); // Outputs: true

15. Is Empty: Checks if the string is empty.

String s = "";
System.out.println(s.isEmpty()); // Outputs: true

16. Join: Joins multiple strings with a delimiter.

String result = String.join("-", "Java", "Python", "C++");


System.out.println(result); // Outputs: Java-Python-C++

17. Value Of: Converts various data types into a string.

Strings 7
int num = 42;
String s = String.valueOf(num);
System.out.println(s); // Outputs: 42

18. Matches: Checks if the string matches a regular expression.

String s = "12345";
System.out.println(s.matches("\\d+")); // Outputs: true (only digits)

19. The compareTo() method compares two strings lexicographically (based on


Unicode values).

Returns:

0 if the strings are equal.

A negative number if the first string is lexicographically smaller.

A positive number if the first string is lexicographically larger.

Example:

String str1 = "apple";


String str2 = "banana";
System.out.println(str1.compareTo(str2)); // Output: -1 (apple < banana)

Important Questions
1. What is the Difference Between == and .equals() for Strings?

== :

Compares references (memory addresses) of two objects.

Returns true if both references point to the same object.

.equals() :

Compares the content of two strings.

Strings 8
Returns true if the content of the strings is the same.

Example:

String s1 = new String("Hello");


String s2 = new String("Hello");

System.out.println(s1 == s2); // false (different references)


System.out.println(s1.equals(s2)); // true (same content)

2. Difference Between String str = "Hello" and String str = new String("Hello") ?

String str = "Hello" :

Creates a string in the String Pool.

If the string already exists in the pool, the existing reference is reused.

String str = new String("Hello") :

Creates a new string object in the heap memory, even if the same
string exists in the pool.

This creates a new reference, which is different from the one in the
pool.

3. Why Should You Use StringBuilder or StringBuffer Instead of String for


Concatenation?

is immutable, so every concatenation operation creates a new


String

object, which is inefficient for frequent modifications.

and
StringBuilder StringBuffer are mutable and designed for efficient string
manipulation.

Use StringBuilder for single-threaded environments and StringBuffer for multi-


threaded environments.

4. What is the format() Method in Java?

The format() method formats a string using specified format specifiers.

Example:

Strings 9
String str = String.format("Name: %s, Age: %d", "Alice", 25);
System.out.println(str); // Output: "Name: Alice, Age: 25"

Strings 10
String vs String Builder vs String
Buffer
In Java, both StringBuilder and StringBuffer are classes used to create mutable
(modifiable) strings. Unlike the immutable String class, these allow for efficient
string manipulation, especially when performing operations like appending,
inserting, or deleting characters repeatedly.

Key Differences Between String, StringBuilder, and StringBuffer

Feature String StringBuilder StringBuffer

Mutability Immutable Mutable Mutable

Thread-Safety Not thread-safe Not thread-safe Thread-safe

Faster (no Slower (due to


Slower for frequent
Performance synchronization synchronization
modifications
overhead) overhead)

When immutability is Single-threaded Multi-threaded


Use Case required (e.g., keys in programs requiring programs requiring
a map) efficient modifications efficient modifications

StringBuilder

Features:

1. Mutable class, meaning you can modify the object without creating a new one.

2. Suitable for single-threaded applications.

3. Provides better performance than String and StringBuffer because it is not


synchronized.

StringBuffer

Features:

String vs String Builder vs String Buffer 1


1. Mutable class with similar functionality to StringBuilder .

2. Synchronized methods make it thread-safe (only one thread can operate at a


time).

3. Slightly slower than StringBuilder due to synchronization overhead.

Common Methods:

append(String s) : Adds text to the end.

insert(int offset, String s) : Inserts text at a specified position.

replace(int start, int end, String s) : Replaces part of the string.

delete(int start, int end) : Removes part of the string.

reverse() : Reverses the string.

capacity() : Returns the current capacity of the StringBuilder .

public class StringBuilderExample {


public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
StringBuffer sb = new StringBuffer("Hello");

// Append
sb.append(" World");
System.out.println(sb); // Outputs: Hello World

// Insert
sb.insert(6, "Java ");
System.out.println(sb); // Outputs: Hello Java World

// Replace
sb.replace(6, 10, "Beautiful");
System.out.println(sb); // Outputs: Hello Beautiful World

// Delete

String vs String Builder vs String Buffer 2


sb.delete(6, 16);
System.out.println(sb); // Outputs: Hello World

// Reverse
sb.reverse();
System.out.println(sb); // Outputs: dlroW olleH
}
}

Key Points to Remember

1. Use String when immutability is desired or when strings are infrequently


modified.

2. Use StringBuilder for high-performance string operations in single-threaded


environments.

3. Use StringBuffer for thread-safe operations in multi-threaded environments.

Important Questions
1. Why is StringBuilder Faster than StringBuffer ?

StringBuilderis faster than StringBuffer because StringBuffer is synchronized, which


means it has built-in thread safety mechanisms. This synchronization makes
StringBuffer thread-safe but adds overhead, slowing it down.

Thread-safety: When multiple threads access the same object, StringBuffer

ensures that only one thread can access it at a time.

Performance: In cases where thread safety is not required (single-threaded


applications), StringBuilder is faster because it doesn't have the overhead of
synchronization.

2. Why is String thread-safe due to immutability?

Immutability of String means that once a String is created, its value cannot
be changed. You cannot modify the characters in a string. If you want to

String vs String Builder vs String Buffer 3


change a String , a new string is created, and the old one remains the
same.

Thread safety happens because of this immutability. If multiple threads (or


parts of a program) are using the same String , they can safely access it
without worrying about one thread changing the string while another
thread is using it.

How Does This Work?

When we say a string is immutable, it means:

You can't change the string once it is created. For example, when you
create the string "Hello" , it will stay "Hello" forever. If you try to change it,
you actually create a new string, not modify the original one.

So, in a multi-threaded environment, where multiple threads are running at the


same time:

One thread can safely use the string "Hello" .

Another thread can also safely use the same string, because no thread
can change "Hello" . If you try to modify it, you will get a new string, and the
old one is untouched.

String str = "Hello"; // This string is immutable, it can't be changed.

// Thread 1: Prints the string


Thread t1 = new Thread(() -> {
System.out.println(str); // It will print "Hello"
});

// Thread 2: Prints the string


Thread t2 = new Thread(() -> {
System.out.println(str); // It will also print "Hello"
});

t1.start();
t2.start();

String vs String Builder vs String Buffer 4


Here, both threads are printing the string "Hello" . Even though both threads are
running at the same time, there is no risk of one thread changing the string
while the other is using it because strings can't be changed. Each thread is
using the exact same string "Hello" , and since it can't change, there is no
conflict.

3. Explain Scenarios Where StringBuffer Would Be Preferred Over StringBuilder

StringBuffer would be preferred over StringBuilder in multi-threaded


environments, where multiple threads need to access and modify the
same string. Since StringBuffer is synchronized, it ensures that only one
thread can modify the string at any given time, preventing data
inconsistencies.

Imagine you are building an application where multiple threads need to


append to the same string:

StringBuffer buffer = new StringBuffer(); // Synchronized, thread-safe

// Thread 1
Thread t1 = new Thread(() -> {
buffer.append("Thread 1 ");
});

// Thread 2
Thread t2 = new Thread(() -> {
buffer.append("Thread 2 ");
});

t1.start();
t2.start();

In this case, StringBuffer ensures that the threads safely append their data
without interference, which is crucial when multiple threads are involved.

However, if you don’t need thread safety (single-threaded scenario),


StringBuilder is a better option because it's faster.

String vs String Builder vs String Buffer 5


String vs String Builder vs String Buffer 6
Collection Frameworks
Java provides a framework known as the Collections Framework to store and
manipulate data. The framework provides a set of interfaces, classes, and
algorithms that allow you to work with different data structures in an efficient way.

Types of Collections
There are two main categories of collections in Java:

Collection Interface: The root interface for all collection classes, such as List,
Set, Queue.

Map Interface: Used to store key-value pairs (like a dictionary or hash map).

Common Operations on Collections

Add: Add elements to a collection.

Remove: Remove elements from a collection.

Contains: Check if a collection contains a certain element.

Size: Get the number of elements in a collection.

Iteration: Iterate over the elements of a collection using an Iterator.

Summary

List: Ordered collection (can have duplicates).

Set: Unordered collection (no duplicates).

Queue: FIFO collection (first-in, first-out).

Map: Key-value pair collection.

Each collection type has its specific use cases based on whether you need order,
uniqueness, or key-value pairing. The Collections Framework allows you to easily
work with these data structures in a consistent and efficient manner.

Collection Frameworks 1
Comparison of ArrayList, LinkedList, Vector, and
Stack

Feature ArrayList LinkedList Vector Stack

Synchronized
Doubly-linked Synchronized
Implementation Resizable array resizable array
list resizable array
(LIFO)

Access Time O(1) O(n) O(1) O(1)

Insertion Time O(1) O(1) O(1) O(1)

Deletion Time O(1) O(1) O(1) O(1)

Less overhead More than More than


More overhead
Memory Usage (contiguous ArrayList ArrayList
(node objects)
memory) (synchronized) (synchronized)

Not Not
Synchronization Synchronized Synchronized
synchronized synchronized

Collection Frameworks 2
Thread Safety No No Yes Yes

Iterating Over
Fast Moderate Fast Fast
List

Frequent Frequent
access, modifications, Multi-threaded
Usage Scenario LIFO operations
infrequent infrequent environment
modifications access

All List All List and All List


All List and
Methods interface Deque interface
Stack methods
methods methods methods

Maintains Maintains Maintains Maintains


Insertion Order
insertion order insertion order insertion order insertion order
new ArrayList<> new LinkedList<>
Example new Vector<>() new Stack<>()
() ()

List Methods

1. add(): Adds an element to the list.


list.add(element);

2. add(index, element): Inserts an element at a specific index.


list.add(index, element);

3. remove(): Removes an element by value.


list.remove(Integer.valueOf(element));

4. remove(): Removes an element by index.


list.remove(index);

5. contains(): Checks if the list contains a specific element.


list.contains(element);

6. size(): Returns the size (number of elements) of the list.


list.size();

7. isEmpty(): Checks if the list is empty.


list.isEmpty();

8. addAll(): Adds all elements from an array to the list.

Collection Frameworks 3
for (int ele : array) { list.add(ele); }

9. for-each loop: Iterates over the list using an enhanced for loop.

for (int ele : list) { System.out.println(ele); }

10. for loop: Iterates over the list using a traditional for loop.

for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }

11. toArray(): Converts the list to an array (but doesn't store the result).
list.toArray();

Comparison of HashSet, LinkedHashSet, and TreeSet

Feature HashSet LinkedHashSet TreeSet

Hash table with Red-black tree (balanced


Implementation Hash table
linked list tree)

No guaranteed Sorted order (natural or


Order Insertion order
order comparator)

Access Time O(1) O(1) O(log n)

Insertion Time O(1) O(1) O(log n)

Deletion Time O(1) O(1) O(log n)

More overhead More overhead (tree


Memory Usage Less overhead
(linked list nodes) nodes)

Synchronization Not synchronized Not synchronized Not synchronized

Thread Safety No No No

Slow compared to
Iterating Over
Fast Moderate HashSet and
Set
LinkedHashSet

General-purpose, Maintain insertion


Usage Scenario Maintain sorted order
no order order

Collection Frameworks 4
Allows one null Allows one null Does not allow null
Null Elements
element element elements
new LinkedHashSet<>
Example new HashSet<>() new TreeSet<>()
()

Set Methods

1. add(): Adds an element to the set.


set.add(element);

2. remove(): Removes an element from the set.


set.remove(element);

3. contains(): Checks if the set contains a specific element.


set.contains(element);

4. size(): Returns the size (number of elements) of the set.


set.size();

5. isEmpty(): Checks if the set is empty.


set.isEmpty();

6. addAll(): Adds all elements from an array to the set.

for (int ele : array) { set.add(ele); }

7. for-each loop: Iterates over the set using an enhanced for loop.

for (int ele : set) { System.out.println(ele); }

Comparison of HashMap, LinkedHashMap, and TreeMap

Feature HashMap LinkedHashMap TreeMap

Hash table with linked Red-black tree


Implementation Hash table
list (balanced tree)

Sorted order (natural


Order No guaranteed order Insertion order
or comparator)

Collection Frameworks 5
Access Time O(1) O(1) O(log n)

Insertion Time O(1) O(1) O(log n)

Deletion Time O(1) O(1) O(log n)

More overhead (linked More overhead (tree


Memory Usage Less overhead
list nodes) nodes)

Synchronization Not synchronized Not synchronized Not synchronized

Thread Safety No No No

Iterating Over
Fast Moderate Moderate to slow
Map

General-purpose, no Maintain insertion


Usage Scenario Maintain sorted order
order order

Allows one null key Allows one null key Does not allow null
Null
and multiple null and multiple null keys, allows null
Keys/Values
values values values

Example new HashMap<>() new LinkedHashMap<>() new TreeMap<>()

Map Methods

1. put(): Adds a key-value pair to the map.


map.put(key, value);

2. putIfAbsent(): Adds a key-value pair only if the key does not exist.
map.putIfAbsent(key, value);

3. remove(): Removes a key-value pair by key.


map.remove(key);

4. containsKey(): Checks if the map contains a specific key.


map.containsKey(key);

5. containsValue(): Checks if the map contains a specific value.


map.containsValue(value);

6. size(): Returns the size (number of key-value pairs) of the map.


map.size();

7. isEmpty(): Checks if the map is empty.

Collection Frameworks 6
map.isEmpty();

8. putAll(): Adds all key-value pairs from another map.


map.putAll(anotherMap);

9. for-each loop: Iterates over the map using an enhanced for loop.

for (Map.Entry<K, V> entry : map.entrySet()) {


K key = entry.getKey();
V value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}

10. keySet(): Returns a set of all keys in the map.


map.keySet();

11. values(): Returns a collection of all values in the map.


map.values();

12. get(): Retrieves the value associated with a specific key.


map.get(key);

Collection Frameworks 7
Comparator and Comparable
1. Comparator:

A class implements the Comparator interface to define custom sorting logic


externally, without modifying the class itself.

It is useful when you want to sort objects in different ways (not necessarily
the natural order defined by Comparable ).

The compare() method is overridden in the comparator class to define the


sorting criteria.

Usage: Sort objects with different sorting criteria using an external


comparator.

Example:

@Override
public int compare(Student s1, Student s2) {
if (s1.age == s2.age) {
return s1.name.compareTo(s2.name);
}
return Integer.compare(s1.age, s2.age);
}

You can also use anonymous Comparator objects or lambda expressions for
different sorting logic, like sorting by weight in the examples.

2. Comparable:

A class implements the Comparable interface to define its natural ordering.

It is used when you want to define the default sorting order for the objects
of a class.

The compareTo() method is overridden in the class to provide the sorting


logic.

Comparator and Comparable 1


Usage: Sort objects using Collections.sort() without needing an external
comparator.

Example:

@Override
public int compareTo(Student other) {
if (this.age == other.age) {
return this.name.compareTo(other.name);
}
return this.age - other.age;
}

The above code sorts students first by age and then by name if ages are
equal.

Feature Comparable Comparator

Implemented The class whose objects need to An external class that compares
by be compared. objects.

Method compareTo() compare()

For natural ordering (default For custom ordering (multiple


Use Case
sorting). sorting ways).

Doesn't modify the class, external


Modification Modifies the class itself.
sorting logic.

Comparator Implementation:

import java.util.*;

class Student {
String name;
int age;
int weight;

public Student(String name, int age, int weight) {

Comparator and Comparable 2


this.name = name;
this.age = age;
this.weight = weight;
}

@Override
public String toString() {
return "Student {" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
}

class MyComparator implements Comparator<Student> {


@Override
public int compare(Student s1, Student s2) {
if (s1.age == s2.age) {
return s1.name.compareTo(s2.name);
}
return Integer.compare(s1.age, s2.age);
// return s1.age - s2.age;
}
}

public class Comparator {


public static void main(String[] args) {
Student s1 = new Student("Leo", 18, 60);
Student s2 = new Student("Bruno", 18, 54);
Student s3 = new Student("Maxo", 17, 46);
Student s4 = new Student("Don", 20, 53);

ArrayList<Student> students = new ArrayList<>();


students.add(s1);
students.add(s2);

Comparator and Comparable 3


students.add(s3);
students.add(s4);

// Sorting students by age and name using MyComparator


Collections.sort(students, new MyComparator());

System.out.println("Sorted Students by age and name:");


for (Student student : students) {
System.out.println(student);
}

// Sorting students by weight using anonymous Comparator


Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.weight - s2.weight;
}
});

System.out.println("Sorted Students by weight:");


for (Student student : students) {
System.out.println(student);
}

// Sorting students by weight using lambda expression


Collections.sort(students, (student1, student2) -> (student1.weight - student

System.out.println("Sorted Students by weight:");


for (Student student : students) {
System.out.println(student);
}
}
}

Comparable Implementation:

Comparator and Comparable 4


import java.util.*;

class Student implements Comparable<Student> {


String name;
int age;
int weight;

public Student(String name, int age, int weight) {


this.name = name;
this.age = age;
this.weight = weight;
}

@Override
public String toString() {
return "Student {" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}

@Override
public int compareTo(Student other) {
if (this.age == other.age) {
return this.name.compareTo(other.name);
}
return this.age - other.age;
}
}

public class Comparable {


public static void main(String[] args) {
Student s1 = new Student("Leo", 18, 60);
Student s2 = new Student("Bruno", 18, 54);

Comparator and Comparable 5


Student s3 = new Student("Maxo", 17, 46);
Student s4 = new Student("Don", 20, 53);

ArrayList<Student> students = new ArrayList<>();


students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);

// Sorting students based on Comparable implementation (natural ordering)


Collections.sort(students);

// Printing sorted students


System.out.println("Sorted Students:");
for (Student student : students) {
System.out.println(student);
}
}
}

Comparator and Comparable 6


Streams
What are Streams?

Streams in Java are a new abstraction introduced in Java 8 to represent a


sequence of elements that can be processed in a functional style.

They allow operations like filtering, sorting, mapping, and reducing data in a
more readable and efficient manner.

Why Use Streams?

Functional Approach: They allow for functional-style programming (such as


map , filter , and reduce ) which can make the code more concise and expressive.

Less Boilerplate Code: With streams, you can perform operations on


collections (like ArrayList , HashSet ) without writing loops explicitly.

Lazy Evaluation: Operations on streams are often performed lazily, meaning


that computation is deferred until it's necessary, potentially improving
performance.

Parallel Processing: Streams can be processed in parallel, making it easy to


take advantage of multi-core processors.

Advantages of Streams:

1. Conciseness: They allow you to chain operations together in a clear and


readable way.

2. Readability: They avoid the need for boilerplate code (like for-loops or
iterators) and are easier to understand.

3. Parallelism: Streams can be processed in parallel with minimal effort, making


them suitable for handling large datasets.

4. Lazy Evaluation: Operations on streams are executed only when required,


potentially saving resources.

5. Functional Operations: Streams support high-level operations like map , filter ,


reduce , collect , etc., which can simplify complex tasks.

Streams 1
Key Operations on Streams:

1. Filtering ( filter() ): Keeps elements that match a given condition.

2. Mapping ( map() ): Transforms elements into other forms.

3. Sorting ( sorted() ): Sorts elements.

4. Distinct ( distinct() ): Removes duplicate elements.

5. Collecting ( collect() ): Collects results into a collection like a list or map.

6. Reducing ( reduce() ): Combines elements to produce a single result.

7. Aggregating ( average() , sum() , etc.): Calculates values like the average or sum
of elements.

Example Code Explanation


1. Filtering and Mapping:

This stream filters students who are 18 or younger and have a weight of 50
or less. Then, it increases their weight by 5 and prints it.

students.stream()
.filter(s -> s.age <= 18 && s.weight <= 50)
.sorted()
.distinct()
.map(s -> s.weight + 5)
.forEach(s -> System.out.println(s));

2. Collecting to a List:

This stream filters and sorts students and collects them into a new list.

students.stream()
.filter(s -> s.age <= 18 && s.weight <= 50)
.sorted()
.map(s -> s.weight + 5)
.collect(Collectors.toList());

Streams 2
3. Finding Max/Min:

Streams can easily find the maximum or minimum value based on a


comparator.

students.stream()
.max(Comparator.comparing(s -> s.age))
.ifPresent(s -> System.out.println(s));

4. Aggregating Data:

This example calculates the average age of students.

students.stream()
.mapToInt(s -> s.age)
.average()
.orElse(0);

5. Group By:

You can group elements based on a condition. Here, students are grouped
by age greater than 17.

students.stream()
.collect(Collectors.groupingBy(student -> student.age > 17))
.forEach((age, studentList) -> {
System.out.println("Age: " + age);
studentList.forEach(System.out::println);
});

Streams 3
Exceptions
An exception is an unexpected event that occurs during program execution, which
can affect the program's flow and may cause it to terminate abnormally.

Common Causes of Exceptions:


Invalid user input

Device failure

Loss of network connection

Insufficient resources (e.g., out of disk space)

Code errors

Trying to access an unavailable file

Java Exception Hierarchy


The exception hierarchy in Java is a tree structure where Throwable is the root
class. This class is divided into two main branches: Errors and Exceptions.

Errors: Represent serious problems (e.g., JVM running out of memory or stack
overflow). Errors are usually not recoverable by the program.

Exceptions: These can be handled by the program. When an exception


occurs, it creates an exception object containing information like the name,
description, and state of the program.

Exceptions 1
Types of Exceptions

Java has two main types of exceptions:

1. RuntimeException (Unchecked Exceptions):

Occurs due to programming errors and happens at runtime.

Examples:

IllegalArgumentException : Improper use of an API.

NullPointerException : Attempting to access an uninitialized variable.

ArrayIndexOutOfBoundsException : Accessing an array out of its bounds.

ArithmeticException : Dividing a number by zero.

Note: These exceptions are usually caused by the programmer's mistake.

2. IOException (Checked Exceptions):

These exceptions are checked at compile-time, and the programmer is


required to handle them.

Examples:

FileNotFoundException : Trying to open a file that doesn't exist.

Reading past the end of a file.

Code Without Exception Handling:

Exceptions 2
public class ExceptionWithoutHandling {

public static void main(String[] args) {


// Start of program execution
System.out.println("Start");

// Division operation that will throw an exception


int i = 0; // Variable 'i' is set to 0
int j = 18 / i; // Attempting to divide 18 by 0

// This line won't be executed due to the exception above


System.out.println("Result of 18 / i: " + j);

// This line won't be executed either


System.out.println("End");
}
}

Explanation of the Code Execution and Output:

1. The program starts and prints "Start".

2. It tries to perform a division: 18 / i , but since i is 0, it throws an ArithmeticException

(division by zero).

3. The exception causes the program to stop, and the remaining lines (such as
printing the result) are never executed.

4. The output is:

Start
Exception in thread "main" java.lang.ArithmeticException: / by zero

Difference Between Errors and Exceptions:

Errors: Prevent the program from even compiling. The bytecode file is not
generated.

Exceptions 3
Exceptions: Allow the program to compile, but can cause the program to stop
during execution unless handled.

Exceptions 4
Exception Handling
Java exceptions, which occur when something goes wrong during the execution
of a program, causing it to terminate abnormally. To ensure that the program
doesn’t crash abruptly, we use exception handling mechanisms.

Why is Exception Handling Important?

Exceptions can disrupt the normal flow of a program.

It helps maintain smooth execution and prevents the program from terminating
unexpectedly.

By handling exceptions, we can give clear error messages, log issues, or even
recover from certain types of errors.

1. try...catch Block
The try...catch block is the most commonly used method for handling exceptions in
Java. It allows you to try running a block of code that may throw an exception,
and if an exception occurs, you can catch it and handle it properly.

try {
// code that might generate an exception
}
catch(ExceptionType e) {
// handle the exception
}

The try block contains the code that might throw an exception.

The catch block catches and handles the exception.

Example:

Exception Handling 1
class Main {
public static void main(String[] args) {
try {
// code that generates exception
int divideByZero = 5 / 0;
}
catch (ArithmeticException e) {
// handling the exception
System.out.println("Exception: " + e.getMessage());
}
}
}

Output:

Caught Exception: / by zero

In this example:

When the code 5 / 0 is executed, it throws an ArithmeticException because division


by zero is not allowed.

The catch block catches this exception and prints the message: "Error: / by
zero".

2. finally Block

The finally block is used to ensure that specific code gets executed regardless of
whether an exception occurs or not. It is commonly used for cleanup tasks, like
closing files, releasing resources, etc.

try {
// code
}
catch (ExceptionType e) {
// handle exception

Exception Handling 2
}
finally {
// always execute code (cleanup tasks)
}

The finally block is optional but useful.

It gets executed after the try...catch block, regardless of whether an exception


occurred or not.

Example:

class Main {
public static void main(String[] args) {
try {
int divideByZero = 5 / 0;
}
catch (ArithmeticException e) {
System.out.println("Caught Exception: " + e.getMessage());
}
finally {
System.out.println("This is the finally block.");
}
}
}

Output:

Caught Exception: / by zero


This is the finally block.

Here, the finally block ensures that the message "This is the finally block." is always
printed, even if an exception is thrown.

3. throw Keyword

Exception Handling 3
The throw keyword is used when we want to manually throw an exception in our
code. This is useful when something goes wrong, and we want to stop the normal
flow of the program and signal an error.
When to Use throw :

If something in the code doesn't follow the rules, we can use throw to generate
an exception (error).

It allows us to handle errors in a controlled way.

Let's say we have a method that checks if someone's age is below 18. If the age is
under 18, we want to throw an exception saying "Underage."

class Main {
public static void checkAge(int age) {
if (age < 18) {
// Throw an exception if age is less than 18
throw new IllegalArgumentException("Underage! You must be 18 or olde
r.");
} else {
System.out.println("You are allowed.");
}
}

public static void main(String[] args) {


try {
checkAge(16); // This will throw an exception because the age is 16
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // Catch and print the exception mess
age
}
}
}

Output:

Exception Handling 4
Underage! You must be 18 or older.

In this example:

We manually throw an IllegalArgumentException if the person is underage.

The catch block catches this exception and prints the message to the user.

4. throws Keyword

The throws keyword is used in a method's declaration to tell the calling method
that this method might throw an exception. So, the calling method needs to be
prepared to handle that exception.

When to Use throws :

If a method is likely to cause an exception (like file handling or network calls),


we use throws to warn the calling method.

The calling method can then decide how to handle the exception.

Let's say we have a method that reads a file, and reading a file might cause an
exception (like the file not existing). We use throws to declare this possibility.

import java.io.*;

class Main {
// This method declares that it might throw an IOException (if the file doesn't
exist)
public static void readFile(String filename) throws IOException {
FileInputStream file = new FileInputStream(filename);
System.out.println("File opened successfully!");
}

public static void main(String[] args) {


try {
readFile("nonexistentfile.txt"); // Trying to read a file that doesn't exist
} catch (IOException e) {
System.out.println("Error: " + e.getMessage()); // Handle the error (file not

Exception Handling 5
found)
}
}
}

Output:

Error: nonexistentfile.txt (The system cannot find the file specified)

In this example:

The method readFile() declares with throws IOException that it might throw an
IOException (this can happen if the file is not found).

In the main() method, we call readFile() . Since readFile() might throw an exception,
we have to handle it with a try...catch block.

If the file doesn't exist, it will throw a FileNotFoundException , and the catch block will
handle it.

Custom Exceptions in Java

In Java, we can create our own exceptions (custom exceptions) by extending the
built-in exception classes. This allows us to create meaningful error messages
that are specific to our application's needs.

Custom exceptions are useful when:

We want to signal a specific problem in our application that the built-in


exceptions don't cover.

We need more control over how exceptions are handled.

How to Create Custom Exceptions:

1. Extend an Exception Class:

To create a custom exception, we can extend either Exception or one of its


subclasses (like RuntimeException if we want it to be an unchecked exception).

2. Add Constructors:

Exception Handling 6
We can create custom constructors to pass specific messages or cause
information when the exception is thrown.

3. Throw the Custom Exception:

Once the custom exception is created, we can use throw to raise it in the
code.

Example of a Custom Exception

Let's create a custom exception that handles a situation where a user's age is
invalid (e.g., less than 0 or greater than 150).

Step 1: Define the Custom Exception

// Custom exception class


class InvalidAgeException extends Exception {
// Constructor that accepts a custom message
public InvalidAgeException(String message) {
super(message); // Passing the message to the parent class (Exception)
}
}

Here, InvalidAgeException is our custom exception class, which extends the Exception

class.

Step 2: Throw the Custom Exception


Now, let's use this custom exception in a method where we validate a person's
age.

class Main {
public static void validateAge(int age) throws InvalidAgeException {
// If the age is invalid (less than 0 or greater than 150), throw custom exce
ption
if (age < 0 || age > 150) {
throw new InvalidAgeException("Invalid age: " + age + ". Age must be
between 0 and 150.");
} else {

Exception Handling 7
System.out.println("Age is valid: " + age);
}
}

public static void main(String[] args) {


try {
validateAge(200); // Trying with an invalid age
} catch (InvalidAgeException e) {
// Catching and printing the custom exception message
System.out.println("Caught an exception: " + e.getMessage());
}

try {
validateAge(25); // Trying with a valid age
} catch (InvalidAgeException e) {
System.out.println("Caught an exception: " + e.getMessage());
}
}
}

Output:

Caught an exception: Invalid age: 200. Age must be between 0 and 150.
Age is valid: 25

Explanation:

The validateAge() method checks if the age is valid. If the age is less than 0 or
greater than 150, it throws the custom InvalidAgeException with a relevant
message.

The catch block catches the custom exception and prints the exception
message.

Exception Handling 8

You might also like