Project Based Learning in Java (24CSH-301 / 24ITH-301) - Lab 5
Experiment 2.2: Autoboxing, Serialization, and File Handling
Develop Java programs using autoboxing, serialization, file handling, and
efficient data processing and management.
AimConvert between primitives and wrapper objects, and make program data outlive the program by writing objects to files.
ObjectivesTo learn about the concepts of wrapper classes, and to learn about file handling.
Mapped COCO3, CO4
Lab Brief
What You Need To Build
Easy Level
Sum with Autoboxing
Write a Java program to calculate the sum of a list of integers using autoboxing and unboxing. Include methods to parse strings into their respective wrapper classes, for example Integer.parseInt().
Read the numbers as text and split them.
Parse each token with Integer.parseInt().
Store them in a List<Integer> by autoboxing.
Add them back into an int by unboxing.
Medium Level
Student Serialization
Serialize a Student object containing id, name, and GPA to a file, then deserialize it from that file and display the student details.
Make Student implement Serializable.
Write the object with ObjectOutputStream.
Read it back with ObjectInputStream.
Handle FileNotFoundException, IOException, and ClassNotFoundException.
Hard Level
Employee File Menu
Create a menu-based application with the options 1. Add an Employee, 2. Display All, and 3. Exit. Adding an employee gathers name, id, designation, and salary and stores the record in a file.
Store each record in an Employee object.
Keep all records in a List<Employee>.
Persist the list to employees.dat.
Reload the file when the program restarts.
Input/Apparatus used: Hardware - minimum 384 MB RAM, 100 GB hard disk. Software - JDK with any Java IDE such as Eclipse, NetBeans, or IntelliJ IDEA, or a plain terminal with javac and java.
Before Coding
Reading Material
Wrapper Classes in Java
A wrapper class provides the mechanism to convert a primitive into an object and an object back into a primitive. Wrappers are needed wherever Java requires objects rather than primitives: collections, serialization, synchronization, and the java.util package all work with objects only.
int primitive = 20;
Integer wrapped = Integer.valueOf(primitive);
int backAgain = wrapped.intValue();
Autoboxing
The automatic conversion of a primitive into its corresponding wrapper class is known as autoboxing, for example int to Integer or double to Double. Since Java 5 the valueOf() call is written by the compiler, so the assignment can be made directly.
int a = 20;
Integer i = Integer.valueOf(a); // explicit
Integer j = a; // autoboxing
System.out.println(a + " " + i + " " + j); // 20 20 20
Unboxing
The automatic conversion of a wrapper type back into its primitive type is known as unboxing. It is the reverse of autoboxing, and since Java 5 the intValue() call is inserted by the compiler.
Integer a = 3;
int i = a.intValue(); // explicit
int j = a; // unboxing
System.out.println(a + " " + i + " " + j); // 3 3 3
Streams in java.io
A stream is a sequence of data. An InputStream reads data from a source and an OutputStream writes data to a destination. Byte streams such as FileInputStream and FileOutputStream handle 8-bit bytes, while character streams such as FileReader and FileWriter handle 16-bit Unicode characters.
try (FileInputStream in = new FileInputStream("input.txt");
FileOutputStream out = new FileOutputStream("output.txt")) {
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
}
The Eight Wrapper Classes
Primitive
Wrapper class
Parsing method
byte
Byte
Byte.parseByte(s)
short
Short
Short.parseShort(s)
int
Integer
Integer.parseInt(s)
long
Long
Long.parseLong(s)
float
Float
Float.parseFloat(s)
double
Double
Double.parseDouble(s)
char
Character
no parse method; use s.charAt(0)
boolean
Boolean
Boolean.parseBoolean(s)
Parsing is not boxing:Integer.parseInt("42") turns text into the primitive int 42. Storing that result in a List<Integer> is a second, separate step, and that step is the autoboxing.
Core Ideas
Prerequisite Concepts
The Serializable Interface
Serialization converts an object into a stream of bytes so it can be stored in a file. A class can only be serialized if it implements Serializable. The interface declares no methods; it is a marker that permits the JVM to write the object out.
class Student implements Serializable {
private int id;
private String name;
private double gpa;
}
serialVersionUID
This constant is the version number of the class. The same value must be present when the object is written and when it is read back, otherwise deserialization fails with an InvalidClassException. Declaring it explicitly keeps old files readable after small edits to the class.
private static final long serialVersionUID = 1L;
Object Streams
ObjectOutputStream.writeObject() writes a whole object graph, and ObjectInputStream.readObject() reads it back. Because readObject() returns Object, the result must be cast to the original type.
Streams must be closed or the file may stay locked and buffered bytes may never reach the disk. A resource declared in the try (...) header is closed automatically, in reverse order, even when an exception is thrown.
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(FILE_NAME))) {
oos.writeObject(student);
}
The transient Keyword
A field marked transient is skipped during serialization. It is used for values that must not be saved, such as passwords, or values that can be recalculated. After reading the object back, a transient field holds its default value.
private transient String password; // not written to the file
Which Exception, and Why
FileNotFoundException means the file is missing, IOException covers read and write failures, and ClassNotFoundException means the class named inside the file is not on the classpath. Because FileNotFoundException extends IOException, it must be caught first.
catch (FileNotFoundException e) { ... } // must come first
catch (IOException e) { ... }
catch (ClassNotFoundException e) { ... }
Easy Level
Sum of Integers with Autoboxing and Unboxing
Problem statement: Write a Java program to calculate the sum of a list of integers using autoboxing and unboxing. Include methods to parse strings into their respective wrapper classes, for example Integer.parseInt().
Method Design
Method
Purpose
parseNumbers(...)
Converts each text token into an int with Integer.parseInt() and stores it in a List<Integer> by autoboxing.
calculateSum(...)
Adds every Integer into an int accumulator, which unboxes each element.
calculateAverage(...)
Divides the sum by the count, casting to double to keep the decimal part.
NumberFormatException
Thrown by parseInt() for text that is not a number; caught so one bad token does not stop the program.
Figure 1. Text becomes a primitive, the primitive is boxed into the list, and the list is unboxed to total it.
Algorithm
Read the numbers as a single line of text.
Split the line into tokens using a whitespace separator.
Parse each token with Integer.parseInt() and catch NumberFormatException for invalid text.
Add every parsed value to a List<Integer>, which autoboxes the int into an Integer.
Traverse the list and add each element to an int accumulator, which unboxes it.
Display the valid numbers, their count, the sum, and the average.
Java Program
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class WrapperSumDemo {
// Parses text into primitives, then stores them as objects
public static List<Integer> parseNumbers(String[] tokens) {
List<Integer> numbers = new ArrayList<>();
for (String token : tokens) {
try {
int value = Integer.parseInt(token);
numbers.add(value); // autoboxing: int -> Integer
} catch (NumberFormatException e) {
System.out.println("Skipping invalid number: " + token);
}
}
return numbers;
}
// Adds every element into an int accumulator
public static int calculateSum(List<Integer> numbers) {
int sum = 0;
for (Integer number : numbers) {
sum += number; // unboxing: Integer -> int
}
return sum;
}
public static double calculateAverage(List<Integer> numbers) {
if (numbers.isEmpty()) {
return 0.0;
}
return (double) calculateSum(numbers) / numbers.size();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("Enter numbers separated by space: ");
String line = scanner.nextLine().trim();
String[] tokens = line.split("\\s+");
List<Integer> numbers = parseNumbers(tokens);
if (numbers.isEmpty()) {
System.out.println("No valid numbers were entered.");
return;
}
System.out.println("Valid numbers: " + numbers);
System.out.println("Count: " + numbers.size());
System.out.println("Sum of numbers: " + calculateSum(numbers));
System.out.printf("Average: %.2f%n", calculateAverage(numbers));
} finally {
scanner.close();
}
}
}
Sample Output
Enter numbers separated by space: 12 8 45 x9 30
Skipping invalid number: x9
Valid numbers: [12, 8, 45, 30]
Count: 4
Sum of numbers: 95
Average: 23.75
Where the boxing happens:numbers.add(value) stores an int in a collection that can only hold objects, so the compiler inserts Integer.valueOf(value). In sum += number the reverse happens, because += needs a primitive, so the compiler inserts number.intValue(). Both lines look like ordinary assignments, which is exactly the point of the feature.
Medium Level
Serializing and Deserializing a Student Object
Problem statement: Create a Java program to serialize and deserialize a Student object. Serialize a Student object containing id, name, and GPA and save it to a file, deserialize the object from the file, and display the student details. Handle FileNotFoundException, IOException, and ClassNotFoundException using exception handling.
Class and Method Design
Member
Purpose
Student implements Serializable
Marks the class as safe to convert into bytes.
serialVersionUID
Fixes the class version so a saved file stays readable.
serializeStudent(...)
Writes the object to student.ser with ObjectOutputStream.
deserializeStudent()
Reads the object back and casts it to Student, or returns null on failure.
displayStudent()
Prints the id, name, and GPA of the restored object.
Catch order matters:FileNotFoundException is a subclass of IOException. If IOException is caught first, the more specific catch block becomes unreachable and the program will not compile.
Figure 2. The object is flattened into bytes, stored, and rebuilt as an equal object later.
Algorithm
Create a Student class with id, name, and GPA that implements Serializable.
Declare a serialVersionUID constant inside the class.
Read the student details and construct one Student object.
Open an ObjectOutputStream over a FileOutputStream and call writeObject().
Open an ObjectInputStream over a FileInputStream and call readObject(), casting the result to Student.
Catch FileNotFoundException, then IOException, then ClassNotFoundException.
Display the details of the restored object if it is not null.
Java Program
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Scanner;
// Only a Serializable class can be written to an object stream
class Student implements Serializable {
private static final long serialVersionUID = 1L;
private final int id;
private final String name;
private final double gpa;
public Student(int id, String name, double gpa) {
this.id = id;
this.name = name;
this.gpa = gpa;
}
public void displayStudent() {
System.out.println("Student ID: " + id);
System.out.println("Name: " + name);
System.out.println("GPA: " + gpa);
}
}
public class StudentSerializationDemo {
private static final String FILE_NAME = "student.ser";
public static void serializeStudent(Student student) {
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(FILE_NAME))) {
oos.writeObject(student);
System.out.println("Student object serialized to " + FILE_NAME);
} catch (IOException e) {
System.out.println("Error during serialization: " + e.getMessage());
}
}
public static Student deserializeStudent() {
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(FILE_NAME))) {
return (Student) ois.readObject();
} catch (FileNotFoundException e) {
System.out.println("File not found. Serialize the object first.");
} catch (IOException e) {
System.out.println("Error during deserialization: " + e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("Class not found: " + e.getMessage());
}
return null;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("Enter student id: ");
int id = Integer.parseInt(scanner.nextLine().trim());
System.out.print("Enter student name: ");
String name = scanner.nextLine().trim();
System.out.print("Enter GPA: ");
double gpa = Double.parseDouble(scanner.nextLine().trim());
serializeStudent(new Student(id, name, gpa));
Student restored = deserializeStudent();
if (restored != null) {
System.out.println("\nDeserialized Student Details:");
restored.displayStudent();
}
} finally {
scanner.close();
}
}
}
Sample Output
Enter student id: 101
Enter student name: Alice
Enter GPA: 3.8
Student object serialized to student.ser
Deserialized Student Details:
Student ID: 101
Name: Alice
GPA: 3.8
Check the file: after running the program, student.ser appears in the project folder. Opening it in a text editor shows mostly unreadable bytes with the class name Student visible near the start. That is the object in its serialized form, and it is why the class must not be renamed before reading the file back.
Hard Level
Menu-Based Employee Records Stored in a File
Problem statement: Create a menu-based Java application with the following options: 1. Add an Employee, 2. Display All, 3. Exit. If option 1 is selected, the application should gather details of the employee such as employee name, employee id, designation, and salary, and store it in a file. If option 2 is selected, the application should display all the employee details. If option 3 is selected the application should exit.
Class and Method Design
Method
Purpose
Employee implements Serializable
Holds one record: id, name, designation, and salary.
loadEmployees()
Reads the saved list at startup, or returns an empty list on the first run.
saveEmployees(...)
Writes the whole list to employees.dat with one writeObject() call.
addEmployee(...)
Reads the four details, adds the record, and saves the file immediately.
displayEmployees(...)
Prints every record, or a message when no records exist.
Why save the list, not one record: an ObjectOutputStream opened on a file replaces its contents. Writing the whole List in one call keeps the file consistent, and reading it back needs only one readObject().
Figure 3. The list is loaded once at startup and written back whenever it changes.
Algorithm
Create an Employee class with id, name, designation, and salary that implements Serializable.
At startup, load the saved list from employees.dat, or create an empty list if the file does not exist.
Repeat: display the menu with Add Employee, Display All, and Exit, and read the choice.
For choice 1, read the four details, add the record to the list, and write the list to the file.
For choice 2, print every record, or a message when the list is empty.
For choice 3, save the list once more and end the loop.
For any other value, print an error message and show the menu again.
Java Program
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private final int id;
private final String name;
private final String designation;
private final double salary;
public Employee(int id, String name, String designation, double salary) {
this.id = id;
this.name = name;
this.designation = designation;
this.salary = salary;
}
public void display() {
System.out.printf("%-6d %-15s %-15s %.2f%n", id, name, designation, salary);
}
}
public class EmployeeFileManager {
private static final String FILE_NAME = "employees.dat";
// readObject() returns Object, so the cast to List cannot be checked at compile time
@SuppressWarnings("unchecked")
private static List<Employee> loadEmployees() {
File file = new File(FILE_NAME);
if (!file.exists()) {
return new ArrayList<>(); // first run: nothing saved yet
}
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(FILE_NAME))) {
return (List<Employee>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error loading employees: " + e.getMessage());
return new ArrayList<>();
}
}
private static void saveEmployees(List<Employee> employees) {
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(FILE_NAME))) {
oos.writeObject(employees);
} catch (IOException e) {
System.out.println("Error saving employees: " + e.getMessage());
}
}
private static void addEmployee(List<Employee> employees, Scanner scanner) {
System.out.print("Enter Employee ID: ");
int id = Integer.parseInt(scanner.nextLine().trim());
System.out.print("Enter Employee Name: ");
String name = scanner.nextLine().trim();
System.out.print("Enter Designation: ");
String designation = scanner.nextLine().trim();
System.out.print("Enter Salary: ");
double salary = Double.parseDouble(scanner.nextLine().trim());
employees.add(new Employee(id, name, designation, salary));
saveEmployees(employees); // store the record in the file now
System.out.println("Employee added successfully!");
}
private static void displayEmployees(List<Employee> employees) {
if (employees.isEmpty()) {
System.out.println("No employees found.");
return;
}
System.out.println("\nEmployee Details:");
System.out.println("ID Name Designation Salary");
for (Employee employee : employees) {
employee.display();
}
}
public static void main(String[] args) {
List<Employee> employees = loadEmployees();
Scanner scanner = new Scanner(System.in);
try {
int choice;
do {
System.out.println("\n1. Add Employee");
System.out.println("2. Display All");
System.out.println("3. Exit");
System.out.print("Enter your choice: ");
choice = Integer.parseInt(scanner.nextLine().trim());
switch (choice) {
case 1 -> addEmployee(employees, scanner);
case 2 -> displayEmployees(employees);
case 3 -> {
saveEmployees(employees);
System.out.println("Records saved. Exiting...");
}
default -> System.out.println("Invalid choice! Please enter a valid option.");
}
} while (choice != 3);
} finally {
scanner.close();
}
}
}
Sample Output
1. Add Employee
2. Display All
3. Exit
Enter your choice: 1
Enter Employee ID: 201
Enter Employee Name: Ravi Verma
Enter Designation: Analyst
Enter Salary: 52000
Employee added successfully!
1. Add Employee
2. Display All
3. Exit
Enter your choice: 2
Employee Details:
ID Name Designation Salary
201 Ravi Verma Analyst 52000.00
1. Add Employee
2. Display All
3. Exit
Enter your choice: 3
Records saved. Exiting...
Proving persistence: run the program, add one employee, and exit. Run it a second time and choose Display All without adding anything. The record from the first run is still listed, because it was read back from employees.dat at startup. That is the difference between data in memory and data in a file.
Review
Quiz and Viva Questions
1. What are wrapper classes in Java? Why do we need them?
A wrapper class wraps a primitive value inside an object, for example Integer for int. They are needed because collections, serialization, synchronization, and the java.util classes all work with objects and cannot hold primitives directly.
2. What is the difference between autoboxing and unboxing?
Autoboxing converts a primitive into its wrapper object, as in Integer i = 20;. Unboxing is the reverse conversion from wrapper to primitive, as in int j = i;. Both are inserted automatically by the compiler since Java 5.
3. Can you store primitive data types in an ArrayList? How does Java handle it?
Not directly, because generics accept only reference types. Writing list.add(5) compiles because the compiler autoboxes the int into an Integer before storing it.
4. How do you convert a String to an Integer using a wrapper class?
Integer.parseInt("42") returns the primitive int, and Integer.valueOf("42") returns an Integer object. Both throw NumberFormatException when the text is not a valid number.
5. What are the different classes available in Java for file handling?
File describes a path, byte streams such as FileInputStream and FileOutputStream read and write raw bytes, character streams such as FileReader and FileWriter handle text, and ObjectInputStream and ObjectOutputStream read and write whole objects.
6. What is the difference between FileReader and BufferedReader?
FileReader reads characters directly from a file, one small read at a time. BufferedReader wraps another reader, fills a buffer in large blocks, and adds readLine(), which makes it both faster and more convenient for text.
7. What is serialization?
Serialization is the process of converting an object into a stream of bytes so it can be written to a file or sent over a network. Deserialization is the reverse process that rebuilds the object from those bytes.
8. Why must a class implement Serializable?
Serializable is a marker interface with no methods. It records the programmer's permission to convert objects of that class into bytes. Writing an object of a class that does not implement it throws NotSerializableException.
9. What is the purpose of serialVersionUID?
It identifies the version of the class. During deserialization the value in the file is compared with the value in the loaded class, and a mismatch causes InvalidClassException. Declaring it explicitly keeps existing files readable after small class changes.
10. What does the transient keyword do?
It excludes a field from serialization. The field is not written to the file, and after deserialization it holds its default value, such as null or 0. It is used for sensitive or easily recomputed data.
11. Which exceptions must the serialization program handle, and why is the order important?
FileNotFoundException for a missing file, IOException for read and write failures, and ClassNotFoundException because readObject() must locate the class named in the file. FileNotFoundException extends IOException, so it must be caught first or the code will not compile.
12. Why does the program use try-with-resources for streams?
It closes each stream automatically, even if an exception is thrown. Without closing, buffered bytes may never reach the disk and the file may remain locked by the program.