Serialization allows an object's state to be converted into a byte stream so it can be stored or transferred over a network. Although lambda expressions can also be serialized, they are not serializable by default and require special handling to participate in Java serialization.
- Lambda expressions can be serialized only when their target functional interface extends Serializable.
- Captured variables must also be serializable.
- Serialization of lambda expressions is generally discouraged unless required by a specific use case.
Lambda Serialization
Lambda serialization is the process of converting a lambda expression into a byte stream so that it can later be reconstructed. Unlike ordinary objects, lambda expressions do not automatically implement Serializable.
Runnable task = () -> System.out.println("Hello");
The above lambda cannot be serialized because Runnable does not extend Serializable.
Serialize a Lambda Expression
By default, lambda expressions do not implement the Serializable interface. Java treats lambdas as implementations of functional interfaces, so serialization is only possible when the target functional interface is serializable.
A lambda expression can be serialized only if:
- Its target functional interface implements or extends java.io.Serializable.
- Every object captured by the lambda is also serializable.
Note: If any captured object is not serializable, serialization fails with a NotSerializableException.
Creating a Serializable Functional Interface
- Instead of repeatedly casting lambda expressions, you can create your own functional interface.
- Every lambda assigned to this interface automatically becomes serializable.
Syntax:
interface SerializableFunction extends Function<String, String>, Serializable {
}
Serializing and Deserializing a Lambda Expression
In this example, the lambda expression is explicitly cast to both Function and Serializable. It is then written to a file using ObjectOutputStream and restored using ObjectInputStream.
// Importing input output classes
import java.io.*;
// Importing all function utility classes
import java.util.function.*;
// Interface
interface MyInterface {
// Method inside this interface
void hello(String name);
}
// Class 1
// Helper class implementing above interface
class MyImpl implements MyInterface {
// Method of this class
public void hello(String name)
{
System.out.println("Hello " + name);
}
}
// Class 2
// Main class
class GFG {
// Method 1
// To Serialize
private static void serialize(Serializable obj,
String outputPath)
throws IOException
{
File outputFile = new File(outputPath);
if (!outputFile.exists()) {
outputFile.createNewFile();
}
try (ObjectOutputStream outputStream
= new ObjectOutputStream(
new FileOutputStream(outputFile))) {
outputStream.writeObject(obj);
}
}
// Method 2
// To Deserialize
private static Object deserialize(String inputPath)
throws IOException, ClassNotFoundException
{
File inputFile = new File(inputPath);
try (ObjectInputStream inputStream
= new ObjectInputStream(
new FileInputStream(inputFile))) {
return inputStream.readObject();
}
}
// Method 3
// To Serialize and deserialize lambda functions
private static void serializeAndDeserializeFunction()
throws Exception
{
Function<Integer, String> fn
= (Function<Integer, String> & Serializable)(n)
-> "Hello " + n;
System.out.println("Run original function: "
+ fn.apply(10));
String path = "./serialized-fn";
serialize((Serializable)fn, path);
System.out.println("Serialized function to "
+ path);
Function<Integer, String> deserializedFn
= (Function<Integer, String>)deserialize(path);
System.out.println("Deserialized function from "
+ path);
System.out.println("Run deserialized function: "
+ deserializedFn.apply(11));
}
// Method 4
// To Serialize and deserialize lambda classes
private static void serializeAndDeserializeClass()
throws Exception
{
String path = "./serialized-class";
serialize(MyImpl.class, path);
System.out.println("Serialized class to " + path);
// Pretending we don't know the exact class of the
// serialized bits, create an instance from the
// class and use it through the interface.
Class<?> myImplClass = (Class<?>)deserialize(path);
System.out.println("Deserialized class from "
+ path);
MyInterface instance
= (MyInterface)myImplClass.newInstance();
instance.hello("Java");
}
// Method 5
// Main driver method
public static void main(String[] args) throws Exception
{
// Calling above method 3 and method 4
// in the main() body
serializeAndDeserializeFunction();
serializeAndDeserializeClass();
}
}
Output:
Run original function: Hello 10
Serialized function to ./serialized-fn
Deserialized function from ./serialized-fn
Run deserialized function: Hello 11
Serialized class to ./serialized-class
Deserialized class from ./serialized-class
Hello JavaAdvantages
- Allows lambda expressions to be stored and restored when required.
- Useful for distributed applications and remote execution.
- Supports persistence of executable tasks.
- Integrates with Java's built-in serialization mechanism.