OOP Java - Unit-III
OOP Java - Unit-III
UNIT - III
Files
Introduction to I/O Streams in Java
I/O (Input/Output) in Java refers to the process of reading from and writing to different sources like
files, keyboard, network, or memory. Java provides a rich set of classes in the java.io package to
handle I/O operations.
Understanding Streams
A stream is a continuous flow of data. Java handles input and output operations through streams.
Types of Streams:
1. Input Stream – Used to read data from a source.
2. Output Stream – Used to write data to a destination.
Note:
The string is converted into a byte array using getBytes().
Each byte is written into the file.
Note:
Uses FileReader to read characters from a file.
Reads character by character instead of byte by byte.
Note: Uses BufferedReader for efficient reading of text files line by line.
Example: Writing to a File using BufferedWriter
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
Multithreading is useful for concurrent tasks within the same application (e.g., handling multiple
user requests in a web server).
Multitasking is broader and enables running multiple independent applications at the same time.
Multithreading
Multithreading is a Java feature that allows concurrent execution of two or more parts of a
program for maximum utilization of CPU.
Each part of such program is called a thread. So, threads are light-weight processes within a
process.
Threads are independent, if there occurs exception in one thread, it doesn't affect other
threads. It shares a common memory area.
There can be multiple processes inside the OS and one process can have multiple threads.
What is a Thread?
• A thread is a lightweight process that allows parallel execution.
• In Java, threads can be created using Thread class or Runnable interface.
• The JVM manages the execution of threads.
2. Runnable State
When the start() method is called, the thread moves to the Runnable state.
The thread is ready to run but may not execute immediately (depends on the CPU
scheduler).
Example:
t.start(); // Thread is now runnable
It may still be waiting for CPU time if other threads have higher priority.
State Trigger
Example:
class MyThread extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
try {
Thread.sleep(500); // Sleep for 500ms
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
Note: The output order may differ each time because thread scheduling is handled by the JVM
and OS.
Why is "Main thread running..." Printed First?
In the given multithreading example, the output "Main thread running..." is printed first
because of how Java schedules and executes threads.
Step-by-Step Execution
1. The main thread starts execution.
2. The start() method is called on t1 and t2.
o Important: Calling start() does not immediately execute run(). It only schedules the
thread for execution.
3. The statement System.out.println("Main thread running..."); is executed immediately by the
main thread.
4. The JVM then assigns CPU time to t1 and t2 whenever available, meaning their execution
starts after the main thread prints its message.
Threads in java
Thread can be created by using two mechanisms:
1. Extending the Thread class
2. Implementing the Runnable Interface
Example:
import java.lang.Thread;
class Thread2 implements Runnable {
public void run() {
System.out.println("Thread is Running...");
System.out.println("Current running thread name is " +
Thread.currentThread().getName());
System.out.println("Current running thread id is " + +
Thread.currentThread().getId());
}
}
public class Multithread2 {
public static void main(String args[]) {
Thread t1=new Thread( new Thread2());
t1.start();
}
}
t1.start();
t2.start();
t3.start();
}
}
Mutual Exclusive - It keep threads from interfering with one another while sharing data.
a) Synchronized Method
If you declare any method as synchronized, it is known as synchronized method.
Synchronized method is used to lock an object for any shared resource.
When a thread invokes a synchronized method, it automatically acquires the lock for that
object and releases it when the thread completes its task.
A synchronized method ensures that only one thread can execute it at a time for the
same object
Example:
class Table{
synchronized void printTable(int n) {
for(int i=1;i<=5;i++){
System.out.println(n+"*"+i+"="+n*i);
try{
Thread.sleep(200);
}
catch(Exception e){
System.out.println(e);
}
}
class TestSynchronization{
public static void main(String args[]) {
Table obj = new Table();//only one object
MyThread1 t1=new MyThread1(obj);
MyThread2 t2=new MyThread2(obj);
t1.start();
t2.start();
}
}
b) Synchronized Block
Instead of locking an entire method, a synchronized block locks only a specific part of the code.
• Synchronized block is used to lock an object for any shared resource.
• Scope of synchronized block is smaller than the method.
• A Java synchronized block doesn't allow more than one JVM, to provide access control to a
shared resource.
• The system performance may degrade because of the slower working of synchronized
keyword.
• Java synchronized block is more efficient than Java synchronized method.
Syntax:
synchronized (object reference expression) {
//code block
}
Example:
class SharedResource {
void printNumbers(int n) {
synchronized (this) { // Synchronization block
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
c) Static Synchronization
When multiple threads access static methods, we use static synchronization.
• If you make any static method as synchronized, the lock will be on the class not on object.
Example:
class SharedResource {
synchronized static void printNumbers(int n) {
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
t1.start();
t2.start();
}
}
Suppose there are two objects of a shared class (e.g. Table) named object1 and object2. In
case of synchronized method and synchronized block there cannot be interference between
Producer(SharedResource resource) {
this.resource = resource;
}
Consumer(SharedResource resource) {
this.resource = resource;
}
producer.start();
consumer.start();
Note:
The Producer thread produces an item and notifies the Consumer.
The Consumer thread waits until an item is produced and then consumes it.
This prevents race conditions and busy-waiting.
Example2:
java.util Package
The java.util package in Java contains utility classes for data structures, date/time manipulation,
random number generation, and string processing.
Collection Framework -Key components include:
Collection interfaces – List, Set, Queue, Map
Collection classes: LinkedList, HashMap, TreeSet
Utility Classes – StringTokenizer, Date, Random, Scanner.
Features of ArrayList:
Dynamic Size – Unlike arrays, ArrayList automatically resizes when elements are added or
removed.
Indexed Access – Elements can be accessed using an index, like arrays.
Allows Duplicates – ArrayList can store duplicate values.
Maintains Insertion Order – Elements remain in the order they were added.
Non-Synchronized – Not thread-safe; requires manual synchronization for multi-threaded
applications.
Syntax :
ArrayList<DataType> VariableName = new ArrayList<DataTYpe>()
Example:
import java.util.ArrayList;
import java.util.Collections;
// 2. Adding elements
list.add("Apple");
// 6. Removing elements
list.remove("Banana"); // Remove by value
System.out.println("After removing 'Banana': " + list);
list.remove(2); // Remove by index
System.out.println("After removing element at index 2: " + list);
b) LinkedList
LinkedList is a doubly linked list implementation of the List interface in Java. Unlike ArrayList,
which is backed by a dynamic array, LinkedList uses nodes where each node contains:
Data (value stored)
Reference to the next node (next pointer)
Reference to the previous node (previous pointer)
It is part of the java.util package.
Example:
import java.util.LinkedList;
import java.util.Collections;
// 2. Adding elements
list.add("Apple");
list.add("Banana");
list.add("Cherry");
list.add(1, "Orange"); // Add at a specific index
list.addFirst("Mango"); // Add at the beginning
list.addLast("Grapes"); // Add at the end
// 3. Accessing elements
System.out.println("First Element: " + list.getFirst());
System.out.println("Last Element: " + list.getLast());
// 4. Updating elements
list.set(2, "Pineapple");
System.out.println("After updating index 2: " + list);
// 5. Removing elements
list.remove("Banana"); // Remove by value
list.remove(2); // Remove by index
list.removeFirst(); // Remove first element
list.removeLast(); // Remove last element
c) Vector
Vector is a resizable-array implementation of the List interface, similar to ArrayList. However, it
is synchronized, making it thread-safe for concurrent access.
Features of Vector:
Implements List Interface – Works like an array but grows dynamically.
Thread-Safe (Synchronized) – Multiple threads can access it safely.
Allows Duplicates & Null Values – Just like ArrayList.
Slower than ArrayList – Because of synchronization overhead.
Example:
public class VectorMethods import java.util.Vector;
import java.util.Collections;
// 2. Adding elements
vector.add("Apple");
vector.add("Banana");
vector.add("Cherry");
vector.add(1, "Orange"); // Insert at index 1
vector.addElement("Mango"); // Alternative way to add
vector.add("Grapes");
// 3. Accessing elements
System.out.println("First Element: " + vector.firstElement());
System.out.println("Last Element: " + vector.lastElement());
System.out.println("Element at index 2: " + vector.get(2));
// 4. Updating elements
vector.set(2, "Pineapple");
System.out.println("After updating index 2: " + vector);
// 5. Removing elements
vector.remove("Banana"); // Remove by value
vector.remove(2); // Remove by index
vector.removeElement("Mango"); // Remove by value (alternative)
vector.removeElementAt(1); // Remove at index
vector.removeAllElements(); // Clear all elements
// 6. Checking if empty
System.out.println("Is Vector empty? " + vector.isEmpty());
// 11. Converting to
{
}
2. Queue – interface
The Queue interface is a part of the Java Collections Framework and is used to hold multiple
elements prior to processing. It follows the FIFO (First-In-First-Out) principle.
Note: import java.util.Queue;
Key Features
Elements are added at the rear (tail) and removed from the front (head).
Offers methods for insertion, deletion, and inspection.
Sub-interfaces:
o Deque (double-ended queue)
Commonly Used Implementing Classes:
element() Retrieves but does not remove the head. Throws exception if empty.
peek() Retrieves but does not remove the head. Returns null if empty.
Example:
import java.util.*;
// Adding elements
q.add("Apple");
q.offer("Banana");
q.offer("Cherry");
// Retrieving head
System.out.println("Head using peek(): " + q.peek());
// Removing elements
System.out.println("Removed using remove(): " + q.remove());
System.out.println("Removed using poll(): " + q.poll());
Example:
import java.util.*;
pq.offer(30);
pq.offer(10);
pq.offer(20);
while (!pq.isEmpty()) {
System.out.println("Polling: " + pq.poll());
}
}
}
3. Set interface
a) HashSet
Backed by: Hash table
Order: No guaranteed order
Duplicates: Not allowed
Null: Allowed (only one null element)
b) LinkedHashSet
Backed by: Hash table + linked list
Order: Maintains insertion order
Duplicates: Not allowed
Null: Allowed
c) Sub Interfaces of Set (SortedSet)
SortedSet interface- Maintains elements in ascending order. Implemented by TreeSet class.
TreeSet
Backed by: Red-Black tree
Order: Sorted (natural or custom)
Duplicates: Not allowed
Null: ❌ Not allowed (throws NullPointerException)
Implements: SortedSet
Notes:
All these methods are available in all implementations of Set (like HashSet, TreeSet,
LinkedHashSet).
Some methods like retainAll() and removeAll() are useful when performing set operations
like intersection or difference.
The iterator() method is commonly used for looping through elements.
Duplicate
❌ No ❌ No ❌ No
Allowed?
Example:
import java.util.*;
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
hashSet.add("Apple"); // Duplicate
hashSet.add(null); // One null allowed
// Iteration
System.out.print("Iterating HashSet: ");
for (String s : hashSet) {
System.out.print(s + " ");
}
System.out.println();
// Convert to array
Object[] hashArray = hashSet.toArray();
System.out.println("Array from HashSet: " + Arrays.toString(hashArray));
hashSet.clear();
System.out.println("After clear(): " + hashSet);
System.out.println("\n--------------------------\n");
linkedSet.add("Dog");
linkedSet.add("Cat");
linkedSet.add("Elephant");
linkedSet.add("Dog"); // Duplicate
linkedSet.add(null); // One null allowed
linkedSet.remove("Cat");
System.out.println("After removing 'Cat': " + linkedSet);
linkedSet.removeAll(Arrays.asList("Elephant")); // Difference
System.out.println("After removeAll(): " + linkedSet);
linkedSet.clear();
System.out.println("After clear(): " + linkedSet);
System.out.println("\n--------------------------\n");
treeSet.add("Zebra");
treeSet.add("Lion");
treeSet.add("Tiger");
treeSet.add("Lion"); // Duplicate
// treeSet.add(null); // Uncommenting this will throw
NullPointerException
treeSet.remove("Zebra");
System.out.println("After removing 'Zebra': " + treeSet);
treeSet.clear();
System.out.println("After clear(): " + treeSet);
}
}
Hashtable ❌ No ❌ No ❌ No ✅ Yes
System.out.println("\n-------------------------\n");
System.out.println("\n-------------------------\n");
System.out.println("\n-------------------------\n");
StringTokenizer in Java
StringTokenizer class in Java is used to break a string into tokens. A StringTokenizer object
internally maintains a current position within the string to be tokenized.
• The String Tokenizer class allows an application to break strings into tokens.
• To use String Tokenizer class we have to specify an input string and a string that contains
delimiters. Delimiters are the characters that separate tokens.
Constructors
• StringTokenizer(String str): default delimiters like newline, space, tab, carriage return,
and form feed.
• StringTokenizer(String str, String delim): delim is a set of delimiters that are used to
tokenize the given string.
• StringTokenizer(String str, String delim, boolean flag): The first two parameters have
the same meaning wherein . If the flag is false, delimiter characters serve to separate tokens
Example:
import java.util.StringTokenizer;
public class StringTokenizerDemo {
public static void main(String[] args) {
Methods:
• boolean after(Date date) - Returns true if the invoking Date object contains a date that is
later than the one specified by date, otherwise, it returns false.
• boolean before(Date date) - Returns true if the invoking Date object contains a date that is
earlier than the one specified by date, otherwise, it returns false.
• boolean equals(Object date) - Returns true if the invoking Date object contains the same
time and date as the one specified by date, otherwise, it returns false.
Example:
import java.text.SimpleDateFormat;
import java.util.*;
Example:
import java.util.Scanner;
// Reading integer
System.out.print("Enter an integer: ");
int num = sc.nextInt();
// Reading float
System.out.print("Enter a float: ");
float f = sc.nextFloat();