Java泛型重写静态方法的变通方案及自定义序列化问询
Got it, let's work through building a flexible, multi-implementation serialization system for your generic objects—since Java's built-in serialization is notoriously rigid, insecure, and prone to versioning issues, rolling your own modular setup is a smart move. Here's how to structure it to support multiple custom serializers while keeping your generic Foo class clean and reusable:
Serializer Base Class (Generic-Friendly) First, tweak your abstract Serializer to properly handle generic types—since Java's type erasure hides generic info at runtime, we'll use a TypeReference (or a custom type token) to capture that context during deserialization:
public abstract class Serializer<T> { // Serialize an instance to raw bytes public abstract byte[] toBytes(T instance); // Deserialize bytes back to T, using TypeReference to preserve generic type info public abstract T fromBytes(byte[] data, TypeReference<T> typeRef); }
Note: You can implement your own TypeReference or use the one from libraries like Jackson—either way, it lets you handle generic types like List<MyObject> that would otherwise be erased.
Create concrete Serializer subclasses for each format you want to support. Here are common examples:
JSON Serializer (Using Jackson)
public class JsonSerializer<T> extends Serializer<T> { private final ObjectMapper mapper = new ObjectMapper(); @Override public byte[] toBytes(T instance) { try { return mapper.writeValueAsBytes(instance); } catch (JsonProcessingException e) { throw new SerializationException("JSON serialization failed", e); } } @Override public T fromBytes(byte[] data, TypeReference<T> typeRef) { try { return mapper.readValue(data, typeRef); } catch (IOException e) { throw new SerializationException("JSON deserialization failed", e); } } }
Protobuf Serializer
public class ProtobufSerializer<T extends Message> extends Serializer<T> { private final Parser<T> parser; public ProtobufSerializer(Parser<T> parser) { this.parser = parser; } @Override public byte[] toBytes(T instance) { return instance.toByteArray(); } @Override public T fromBytes(byte[] data, TypeReference<T> typeRef) { try { return parser.parseFrom(data); } catch (InvalidProtocolBufferException e) { throw new SerializationException("Protobuf deserialization failed", e); } } }
Custom Binary Serializer (Your Own Format)
public class CustomBinarySerializer<T extends User> extends Serializer<T> { @Override public byte[] toBytes(T instance) { // Implement your own compact binary logic try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos)) { dos.writeUTF(instance.getUsername()); dos.writeInt(instance.getAge()); return baos.toByteArray(); } catch (IOException e) { throw new SerializationException("Custom binary serialization failed", e); } } @Override public T fromBytes(byte[] data, TypeReference<T> typeRef) { try (ByteArrayInputStream bais = new ByteArrayInputStream(data); DataInputStream dis = new DataInputStream(bais)) { String username = dis.readUTF(); int age = dis.readInt(); return (T) new User(username, age); } catch (IOException e) { throw new SerializationException("Custom binary deserialization failed", e); } } }
Foo Work With Any Serializer Update Foo to accept a Serializer instance via constructor or setter—this lets you swap implementations without modifying Foo itself:
public class Foo<T> { private final Serializer<T> serializer; // Inject serializer via constructor for dependency injection compatibility public Foo(Serializer<T> serializer) { this.serializer = serializer; } public byte[] serialize(T obj) { return serializer.toBytes(obj); } public T deserialize(byte[] data, TypeReference<T> typeRef) { return serializer.fromBytes(data, typeRef); } }
To avoid creating Serializer instances everywhere, use a factory to fetch the right implementation based on format:
public class SerializerFactory { public enum Format { JSON, PROTOBUF, CUSTOM_BINARY } public static <T> Serializer<T> getSerializer(Format format) { return switch (format) { case JSON -> new JsonSerializer<>(); case CUSTOM_BINARY -> new CustomBinarySerializer<>(); case PROTOBUF -> throw new IllegalArgumentException("Protobuf requires a parser—use overloaded method"); }; } // Overloaded method for Protobuf (needs a specific parser) public static <T extends Message> Serializer<T> getProtobufSerializer(Parser<T> parser) { return new ProtobufSerializer<>(parser); } }
Usage Example
// Use JSON serializer Foo<User> jsonFoo = new Foo<>(SerializerFactory.getSerializer(SerializerFactory.Format.JSON)); byte[] userBytes = jsonFoo.serialize(new User("Alice", 30)); User deserialized = jsonFoo.deserialize(userBytes, new TypeReference<User>() {}); // Use Protobuf serializer Parser<UserProto> protoParser = UserProto.parser(); Foo<UserProto> protoFoo = new Foo<>(SerializerFactory.getProtobufSerializer(protoParser));
- Flexibility: Swap serializers (JSON → Protobuf → custom binary) without touching domain code.
- Type Safety: Generics ensure you're always working with the correct object type.
- Maintainability: Each serializer handles one format, keeping code modular and testable.
- Avoids Java Serialization Flaws: No more
serialVersionUIDheadaches, tight class coupling, or security vulnerabilities.
内容的提问来源于stack exchange,提问作者MeetTitan

