You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
안녕하세요, @silberbullet 님! 언제나 좋은 질문 감사드립니다! 👍
많은 분들이 의례적인 작업으로 생각하시고 넘어가는 파트인 것 같습니다.
말씀해 주신 내용과 관련해서 다형성과 마커 인터페이스를 짧게(?) 다루어 보겠습니다.
각 섹션은 짧습니다.
⚡ 사전 지식
이 답변의 사전 지식으로 직렬화에 대해 알아야 합니다.
직렬화가 무엇이고, 왜 하는 지는 @shin-jingyu 님과 @github-insu 님이 함께 작성한 코멘트를 참고해 주세요.
관련 코멘트:
⚡️ 바쁜 직장인을 위한 요약
Serializable은 그 자체로 특별한 기능을 하지 않고, 마치 애노테이션처럼 "이 클래스는 직렬화해도 됨."을 표시하는 역할입니다.
(마커 인터페이스)
현대적으로는 다형성의 특징을 잘 살릴 수 있습니다.
고전적으로는 다형성의 특징을 파라미터 수준에서 완성하지 않았습니다.
일부러 느슨한 제약을 가진 공통 상위타입을 설계하여, 설계의 원형을 유지합니다.
(공통 상위 타입으로 사용되는 ObjectOutput 인터페이스의 writeObject(Object object) 메서드는, 파라미터로 Serializable을 받지 않고 Object를 받는 느슨한 제약을 가진 추상메서드를 제공하죠.)
대신 런타임에 Serializable 인스턴스인지 체크합니다. (instanceof)
❓ 그렇다면 Serializable이 왜 필요할까요?
작업자의 의도에 맞는 객체만 취사선택하여 직렬화 대상으로 체크할 수 있습니다. 중요한 정보가 무방비하게 직렬화되지 않도록 보호됩니다.
내부적으로 직렬화가 불가능하거나 부적절한 케이스를 미리 체크해 둘 수 있습니다. (빠른 예외 반환)
✔️ 마커 인터페이스: 함수를 제공하지 않음
Serializable 인터페이스는 아무런 함수도 제공하지 않습니다.
따라서 현대적인 개발 문화로는 다형성을 제공하는 정도 역할로 생각하고 있습니다.
또한 일종의 '마커 인터페이스'이므로, instanceof 연산자로 체크하여 Serializable 인스턴스인지 확인할 수 있습니다. (애노테이션과 유사한 역할)
🟢🔺🟪 다형성으로서
자주 이야기되는 다형성은 런타임 다형성으로, 타입에 대해 표현할 때는 상위 타입 객체가 하위 타입 인스턴스를 포함할 수 있다는 개념입니다.
흔히 '부모는 자식을 품을 수 있다' 등으로 연상시켜 기억을 돕습니다.
다형성 예시
만약 직렬화 함수들이 파라미터로 Serializable을 받기로 한다면, 이곳에 전달하려는 객체는 Serializable을 구현해야 합니다.
이곳에 넣을 객체들은 Serializable 인터페이스를 구현한 클래스의 인스턴스여야 합니다.
또한 이는 컴파일타임에 체크할 수 있는 예시입니다.
✅ instanceof 🟩: instanceof 연산자
instanceof 연산자로 런타임에 체크하는 예시
파라미터 타입에서 Serializable로 제한하지 않고 설계하는 예시로,
이번 예시에서는 Object로 모든 객체를 받는다고 하겠습니다.
이 함수는 구현하는 사람의 의도에 따라, 런타임에 instanceof로 Serializable로 마크된 객체인지 확인할 수 있습니다.
publicvoidserialize(Objectsource) {
if (!(sourceinstanceofSerializable)) {
throw ...;
}
// do serialization here// ...
}
이 함수를 호출할 때는 반드시 Serializable 인스턴스가 아니어도 호출 자체는 됩니다.
하지만 Serializable 인스턴스가 아니라면 런타임에 오류를 띄웁니다.
// JDK 16+ (14 Preview)if (iteminstanceofSerializables) {
serialize(s); // ✅ Ensurance
} else {
serialize(item); // ❎ 호출 가능 -> 실행 시 Runtime Error !!
}
⚠️ 미리 진단되지 않은 코드
이 방식은 컴파일타임에 체크할 수 있었던 문제를 런타임으로 유보시켜
프로세스 운영에 잠재적 문제를 야기하는 코드로서 현대적으로 선호되는 방식은 아닙니다.
❓ 그럼에도 자바를 설계한 사람들이 이러한 방식을 초기에 선택했던 이유가 무엇일까요?
💡 느슨한 제약의 공통 설계부
---
config:
look: handDrawn
theme: forest
---
classDiagram
%% 마커 인터페이스
class Serializable {
<<marker>>
}
%% 직렬화를 위한 인터페이스
class ObjectOutput {
<<interface>>
+writeObject(obj: Object)
}
%% 역직렬화를 위한 인터페이스
class ObjectInput {
<<interface>>
+readObject() Object
}
%% 직렬화 기능 구현 클래스
class ObjectOutputStream {
+writeObject(obj: Object)
+[기타 메서드...]
}
%% 역직렬화 기능 구현 클래스
class ObjectInputStream {
+readObject() Object
+[기타 메서드...]
}
%% 관계 표현
ObjectOutputStream ..|> ObjectOutput
ObjectInputStream ..|> ObjectInput
%% ObjectOutputStream --> Serializable : <<<span>uses</span>>>
%% ObjectInputStream --> Serializable : <<<span>uses</span>>>
Loading
'추상적인' 설계 파트에서는 느슨한 제약을 선호할 수 있습니다. 위 방식은 다음과 같은 항목을 포함합니다.
메서드는 Object 타입 파라미터를 받습니다.
함수 내부에서 런타임에 instanceof로 Serializable 인스턴스인지 확인합니다.
(아마도) 오버라이딩이 가능한 상태거나, 수퍼 메서드가 있을 것입니다. 즉, 가상메서드(≠ 추상메서드)가 존재할 것입니다.
위 다이어그램에서 ObjectOutput 인터페이스의 writeObject(Object object) 메서드가 해당합니다.
이 방식은 직렬화 함수나 그 원형이 오버라이딩이 가능한 함수라는 전제에서 다음과 같은 특징을 갖습니다.
오버라이딩한 함수에서는 Serializable이 아닌 Object 인스턴스까지 실제 직렬화 대상으로 통과시키는 의도를 추가할 수 있습니다. (instanceof 제거)
그 선택을 공통 상위타입을 설계하는 사람이 Serializable로 제한하지 않고, 그 설계를 이어 받은(오버라이딩 하는) 사람이 선택할 수 있도록 합니다.
공통 상위타입은 느슨한 제약으로 설계의 원형을 오랫동안 유지할 수 있게 됩니다. (문제가 발생할 때 상위 타입 대신 구현부를 수정할 수 있습니다.)
자바를 만든 사람들은 본인들이 자바에서 처음 설계한 것들이 나중에 과도하게 바뀌는 것을 최대한 피하려고 한 것으로 유명합니다. (1.8까지 하위호환 대체로 보장) 따라서, 처음부터 '느슨한 제약'으로 설계하여 설계의 원형을 오랫동안 유지하려고 한 것 같습니다. 만약 처음부터 Serializable 객체만 입력받도록 만들었다면, 추후 Serializable이 아닌 객체를 직렬화하는 직렬화 함수로 확장할 수 없습니다. 또한 Serializable로 마크되지 않은 객체는 직렬화할 수 없으므로, 다른 라이브러리에서 가져 온 어느 클래스의 객체를 직접 직렬화할 수 없을 수 있습니다. (그 클래스가 Serializable을 누락했다면.)
❓ 그렇다면 Serializable이 왜 필요할까요?
앞서 설명한 대로라면, Object 타입을 받아서 모두 직렬화에 통과시키면 되는 문제로 생각이 듭니다.
그런데 왜 굳이 Serializable을 추가하고, 이 타입을 체크해 두지 않으면 필터하는 걸까요?
이는 일종의 '안전 장치'로 마크하는 것과 같은 의도입니다. Serializable을 붙이지 않은 클래스의 객체는 직렬화 대상에서 제외하는 거죠.
직렬화할 객체의 클래스에는 Serializable을 붙여서 직렬화 대상이 된다는 걸 표시하는 거구요.
이로써 다음과 같은 효과를 얻을 수 있습니다.
🧑🏻💻 작업자의 의도를 담을 수 있습니다. 작업자는 이 클래스의 인스턴스가 직렬화 대상인지 아닌지 명시할 수 있습니다. 이로써 보안상 함부로 직렬화해선 안 되는 정보를 무방비한 직렬화로부터 보호할 수 있고, 내부적으로 직렬화가 부적절한 유동 환경 정보 등을 실수로 직렬화하는 상황을 피할 수 있습니다.
🎁 민감한 정보 보호: 의도하지 않은 데이터가 무방비하게 직렬화되는 것을 방지할 수 있습니다.
👷 안전한 직렬화 명시: 내부적으로 직렬화가 불가능하거나 부적절한 유동적 환경 정보가 포함되는 객체가 있을 수 있습니다. 이를 무작정 직렬화하면, 추후 역직렬화 시 적절한 재구조화가 안 될 수 있습니다. 예를 들어 운영체제에 종속되는 정보를 직렬화 대상으로 포함하면 안 되겠죠!
이러한 이유로 느슨한 제약을 담은 설계와 동시에, 런타임에 결정권을 갖게 하는 Serializable 인터페이스를 제공한 것으로 생각합니다.
이런 걸 모두 예상하고 느슨한 제약과 추가 타입 필터용 마커 인터페이스를 설계했다니, 당시 자바를 개발한 팀의 예견이 참 놀랍습니다.
@silberbullet 님께서 모두에게,
세세한 부분에서 좋은 호기심으로 접근할 수 있도록
유익한 질문을 제시해 주셨습니다! 🚀
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
여러 소스들을 접하면서 VO 객체에 Serializable를 구현받는 것을 많이 접했습니다.
저희 헥사고날에서도 하기와 같이 적용을 하였는데, 어떤 의도로 사용하게 되는 걸까요?
리서치 했을 때는 객체를 자바 환경에서 직렬화, 역직렬화하여 활용한다는데 와닿지가 않네요..
Beta Was this translation helpful? Give feedback.
All reactions