সিরিয়ালাইজেশন (Serialization)
আমরা জানি যে ক্লাস থেকে অবজেক্ট তৈরি করা হয়। ক্লাসে মূলত একটি অবজেক্টে কী কী প্রোপ্রার্টিজ থাকবে এবং এটি কী কী কাজ করতে পারবে তার বর্ণনা থাকে। কিন্তু যখন নিউ(new) অপারেটর ব্যবহার করে একে অবজেক্ট তৈরি করা হয়, তখন এটি একটি জীবন্ত প্রাণির মতো অবজেক্টে পরিণত হয়। একটি জীবন্ত প্রাণির যেমন অনেকগুলো নিজস্ব ও অনন্য (unique) বৈশিষ্ট্য থাকে (যেমন- নাম, বয়স, ডিএনএ সিকুয়েন্স ইত্যাদি), তেমনি একটি অবজেক্টের একইরকম অনন্য পরিচয় (unique identity) থাকে। প্রত্যেকটি অবজেক্ট কিছু না কিছু স্টেট(state) বা ডেটা থাকে। প্রত্যেকটি অবজেক্টের একটি জীবন চক্র থাকে (life cycle)। এটি নিউ অপারেটর দিয়ে শুরু হয় এবং গারবেজ কালেক্টর(garbage collector) দিয়ে শেষ হয়। এই শুরু এবং শেষ হওয়ার মধ্যবর্তী অবজেক্টের কোনো অবস্থাকে বাইনারী ফরমেটে স্টোর করা যায় এবং সেই একই অবস্থা থেকে পুনর্গঠিত করা যায়।
অবজেক্টের এই কোনো অবস্থাকে বাইনারী ফরমেটে রূপান্তর করার প্রক্রিয়াকে সিরিয়ালাইজেশন(serialization) বলা হয়। আবার এই বাইনারী ফরমেট থেকে অবজেক্টে পুনর্গঠিত করার প্রক্রিয়াকে ডিসিরিয়ালাইজেশন(deserialization) বলা হয়।
সাধারণত দুটি কাজে এই সিরিয়ালাইজেশনের দরকার হয় –
১. অ্যাপ্লিকেশনের কোনো প্রয়োজনে অবজেক্টের অবস্থানকে স্থায়িভাবে সংরক্ষণ করার প্রয়োজন হতে পারে। যেমন- ডেটাবেইজে সংরক্ষণ।
২. একটি অজবেক্টেকে একটি কম্পিউটার থেকে অন্য কম্পিউটারে পাঠোনোর প্রয়োজন হতে পারে।
এবার তাহলে দেখা যাক কীভাবে অবজেক্টকে সিরিয়ালাইজ করা যায়।
সব অবজেক্টকেই সিরিয়ালাইজ করা যায় না। কোনো অবজেক্টকে সিরিয়ালাইজ করতে হলে, সেই অবজেক্টের ক্লাসকে অবশ্যই java.io.Serializable ইন্টারফেসকে ইমপ্লিমেন্ট করতে হবে। এই ইন্টারফেসটিতে কোনো মেথড নেই। এটি মূলত একটি মার্কার ইন্টারফেইস।
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// more properties & methods
}
উপরের Person ক্লাসটি Serializable ইন্টরফেসটিকে ইমপ্লিমেন্ট করে। এর অর্থ হলো, এই ক্লাসের যেকোনো অবজেক্টকে সিরিয়ালাইজ করা যাবে।
তবে যদি কোনো ক্লাস যদি এই ইন্টারফেসকে ইমপ্লিমেন্ট না করে, এবং সেই ক্লাসের অবজেক্টকে সিরিয়ালাইজ করার চেষ্টা করা হয়, তাহলে জাভা রানটাইম NotSerializableException থ্রু করবে।
সিরিয়ালাইজেশন প্রক্রিয়া:
প্রথমে আমাদের একটি অবজেক্ট তৈরি করতে হবে। আমরা অবজেক্টের বাইনারি ফরমেটটি একটি ফাইলে সংরক্ষণ করতে চাই। এজন্য আমাদের একটি আউটপুট স্ট্রিম লাগবে- সেক্ষেত্রে যা FileOutputStream । একে একটি ObjectOutputStream দিয়ে wrap করে এর writeObject() মেথডটি কল করলেই অবজেক্টটি সিরিয়ালাইজ হয়ে আউটপুট স্ট্রিমে রাইট হবে।
public static void serializeToDisk(String fileName, List<OrderLine> orders) {
File file = new File(fileName);
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file))) {
outputStream.writeObject(orders);
} catch (IOException e) {
e.printStackTrace();
}
}
উপরের মেথডটি একটি অর্ডার লিস্টকে একটি নির্দিষ্ট ফাইলে সংরক্ষণ করে। এখানে আর্গুমেন্ট থেকে প্রাপ্ত ফাইলের নাম দিয়ে একটি ফাইল তৈরি করা হয়েছে। তারপর ট্রাই-রিসোর্স ব্যবহার করে আউটপুট স্ট্রিম তৈরি করা হয়েছে। এবং ট্রাই ব্লকের ভেতরে অর্ডার লিস্টকে writeObject() মেথড দিয়ে সিরিয়ালাইজ করা হয়েছে।
ডিসিরিয়ালাইজেশন প্রক্রিয়া
সিরিয়ালেশনের মূল উদ্দেশ্য হচ্ছে সিরিয়ালাইজ কৃত অবজেক্টিকে আবার পুনর্গঠিন করে ব্যবহার করা। এই প্রক্রিয়াটি উপরের প্রক্রিয়াটির মতোই সহজ। এক্ষেত্রে একটি ইনপুট স্ট্রিমের প্রয়োজন হয়।
public static List<OrderLine> deserializeFromDisk(String fileName) {
File file = new File(fileName);
try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file))) {
return (List<OrderLine>) inputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
উপরের মেথডে আর্গুমেন্ট থেকে প্রাপ্ত থেকে ফাইলের নাম থেকে একটি ফাইল তৈরি করা হয়েছে। এরপর ট্রাই-রিসোর্স স্টেটমেন্ট ব্যবহার করে ইনপুট স্ট্রিম তৈরি করা হয়েছে। ট্রাই ব্লকের মধ্যে ইনপুট স্ট্রিমে মেথড readObject() ব্যবহার করে সিরিয়ালাইজকৃত অবজেক্টটি পুনর্গঠন করা হয়েছে।
সম্পূর্ণ সোর্সকোড:
package article.serialization;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import static article.serialization.Price.*;
import static article.serialization.Weight.*;
public class Store {
public static void main(String[] args) {
Product toothPaste = new Product("Tooth Paste", price(1.5), weight(0.5));
Product toothBrush = new Product("Tooth Brush", price(3.5), weight(0.3));
List<OrderLine> orders =
Arrays.asList(new OrderLine(toothPaste, 2),
new OrderLine(toothBrush, 3));
System.out.println("Before serialization: ");
printOrder(orders);
String fileName = "orders.ser";
serializeToDisk(fileName, orders);
System.out.println();
System.out.println("After serialization: ");
List<OrderLine> deserializeOrders = deserializeFromDisk(fileName);
printOrder(deserializeOrders);
/*
Output:
Before serialization:
Total Price: 13.5
Total Weight: 1.9
After serialization:
Total Price: 13.5
Total Weight: 1.9
*/
}
private static void printOrder(List<OrderLine> orders) {
orders.stream()
.map(OrderLine::getAmount)
.reduce(Price::add)
.ifPresent(price ->
System.out.println(String.format("Total Price: %s", price)));
orders.stream()
.map(OrderLine::getWeight)
.reduce(Weight::add)
.ifPresent(weight ->
System.out.println(String.format("Total Weight: %s", weight)));
}
public static void serializeToDisk(String fileName, List<OrderLine> orders) {
File file = new File(fileName);
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file))) {
outputStream.writeObject(orders);
} catch (IOException e) {
e.printStackTrace();
}
}
public static List<OrderLine> deserializeFromDisk(String fileName) {
File file = new File(fileName);
try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file))) {
return (List<OrderLine>) inputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
package article.serialization;
import java.io.Serializable;
public class OrderLine implements Serializable {
private Product product;
private int count;
public OrderLine(Product product, int count) {
this.product = product;
this.count = count;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Weight getWeight() {
return this.product.getWeight().mult(this.count);
}
public Price getAmount() {
return this.product.getPrice().mult(this.count);
}
}
package article.serialization;
import java.io.Serializable;
public class Price implements Serializable {
public final double value;
private Price(double value) {
this.value = value;
}
public static Price price(double value) {
if (value <= 0) {
throw new IllegalArgumentException("Price must be greater than 0");
} else {
return new Price(value);
}
}
public Price add(Price price) {
return new Price(this.value + price.value);
}
public Price mult(int count) {
return new Price(this.value count);
}
@Override
public String toString() {
return Double.toString(this.value);
}
}
package article.serialization;
import java.io.Serializable;
public class Product implements Serializable{
private final String name;
private final Price price;
private final Weight weight;
public Product(String name, Price price, Weight weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public Price getPrice() {
return price;
}
public Weight getWeight() {
return weight;
}
}
package article.serialization;
import java.io.Serializable;
public class Weight implements Serializable {
public final double value;
private Weight(double value) {
this.value = value;
}
public static Weight weight(double value) {
if (value <= 0) {
throw new IllegalArgumentException("Weight must be greater than 0");
}
return new Weight(value);
}
public Weight add(Weight weight) {
return new Weight(this.value + weight.value);
}
public Weight mult(int count) {
return new Weight(this.value count);
}
@Override
public String toString() {
return Double.toString(this.value);
}
}