Java의 특징
- Java는 객체지향 프로그래밍 언어
- 기본 자료형을 제외한 모든 요소들이 객체로 표현됨
- 객체 지향의 특징인 캡슐화, 상속, 다형성, 추상화 등이 적용된 언어
Java의 장점
1. JVM(자바가상머신) 위에서 동작하기 때문에 운영체제에 독립적
2. GarbageCollector를 통한 자동 메모리 관리 가능
Java의 단점
1. JVM 위에서 동작하기 때문에 실행 속도가 상대적으로 느림
2. 다중 상속이나 타입에 엄격하며, 제약 사항들이 많음
JVM 역할
- JVM은 스택 기반으로 동작
- Java Byte Code를 OS에 맞게 해석해주는 역할
- Garbage Collection을 통해 자동적으로 메모리 관리
Java의 컴파일 과정
1. .java 파일 생성
2. build
3. Java Compiler의 javac의 명령어를 통해 바이트코드(.class) 생성
4. Class Loader를 통해 JVM 메모리 내로 로드
5. 실행엔진을 통해 컴퓨터가 읽을 수 있는 기계어로 해석(각 운영체제에 맞는 기계어)

Java에서 제공하는 원시 타입들은 무엇이고, 각 몇 바이트?
- 정수형 byte, short, int, long -> 1, 2, 4, 8
- 실수형 float, double -> 4, 8
- 문자형 char -> 2
- 논리형 boolean -> 1
오버라이딩(Overriding)과 오버로딩(Overloading)
- 오버라이딩 : 상위 클래스에 있는 메소드를 하위 클래스에서 재정의하는 것
- 오버로딩 : 매개변수의 개수나 타입을 다르게 하여 같은 이름의 메소드를 여러 개 정의하는 것
객체 지향 프로그래밍(OOP) 이란
- 우리가 실생활에서 쓰는 모든 것을 객체로 표현
- 객체 지향 프로그래밍은 프로그램 구현에 필요한 객체를 파악
- 상태와 행위를 가진 객체를 만들고
- 각각의 객체들의 역할이 무엇인지를 정의하여
- 객체들 간의 상호작용을 통해 프로그램을 만듦
- 즉, 기능이 아닌 객체가 중심, "누가 어떤 일을 할 것인가?"가 핵심
- 특징 : 캡슐화, 상속, 다형성, 추상화 등이 있음
- 모듈 재사용으로 확장 및 유지보수가 용이
불변 객체가 무엇이고, 대표적 Java 예시
- 불변 객체는 객체 생성 이후 내부의 상태가 변하지 않는 객체
- Java에서는 필드가 원시 타입인 경우, final 키워드를 사용해 불변 객체를 만들 수 있음
- 참조 타입일 경우엔 추가 작업 필요(객체, 배열, List 등)
1. 참조 변수가 일반 객체인 경우, 객체를 사용하는 필드의 참조 변수도 불변 객체로 변경해야 함
2. 배열일 경우, 배열을 받아 copy해서 저장하고, getter를 clone으로 반환하도록 해야 함
(배열을 그대로 참조하거나, 반환할 경우 외부에서 내부 값을 변경할 수 있음, 때문에 clone을 반환해 외부에서 값 변경 못하게 함)
3. 리스트인 경우에도 배열과 마찬가지로 생성시 새로운 List를 만들어 값을 복사하도록 해야 함
(배열과 리스트는 내부를 복사하여 전달하는데, 이를 방어적 복사(defensive-copy)라고 함
불변 객체나 final을 사용하는 이유
1. Thread-Safe 하여 병렬 프로그래밍에 유용, 동기화를 고려하지 않아도 됨
(공유 자원이 불변이기 때문에 항상 동일한 값을 반환하기 때문)
2. 실패 원자적인 메소드를 만들 수 있음
(어떤 예외가 발생되더라도, 메소드 호출 전의 상태를 유지할 수 있어 예외 발생 전과 똑같은 상태로 다음 로직 처리 가능)
3. 부수효과를 피해 오류를 최소화 가능
(부수효과: 변수의 값이 바뀌거나 객체의 필드 값을 설정하거나 예외나 오류가 발생하여 실행이 중단되는 현상)
4. 메소드 호출 시 파라미터 값이 변하지 않다는 것을 보장
5. 가비지 컬렉션 성능 높일 수 있음
(가비지 컬렉터가 스캔하는 객체의 수가 줄기 때문에 gc 수행 시 지연시간도 줄어듦)
추상 클래스와 인터페이스 설명 및 차이
추상 클래스
- 클래스 내 '추상 메소드'가 하나 이상 포함되거나, abstract로 정의된 경우
- 상속을 통해서 자손 클래스에서 완성을 유도하는 클래스
- 추상 클래스를 상속받아서 기능을 이용, 확장시는 것에 의미
- 미완성 설계도
- 상속을 위한 클래스이기 때문에 따로 객체 생성 불가
- 다중 상속 불가능
- "~이다"
- 사용시기 : 상속 관계를 쭉 타고 올라갔을 때, 같은 조상 클래스를 상속하는데 기능까지 완전히 똑같은 기능이 필요한 경우
인터페이스
- 모든 메소드가 추상 메소드인 경우 (자바 8에서는 deafult 키워드를 이용해서 일반 메소드의 구현도 가능)
- 기본 설계도
- 다른 클래스를 작성하는데 도움을 주는 목적으로 작성
- 클래스와 다르게 다중상속 가능
- "~할 수 있는"
- 사용시기 : 상속 관계를 쭉 타고 올라갔을 때, 다른 조상 클래스를 상속하는데 같은 기능이 필요할 경우
공통점
- 상속받는 클래스 혹은 구현하는 인터페이스 안에 있는 추상 메소드를 구현하도록 강제
차이점
- 존재 목적이 다름
- 추상 클래스는 그 추상 클래스를 상속받아서 기능을 이용, 확장시키는 것에 의미
- 인터페이스는 함수의 껍데기만 있는데, 그 함수의 구현을 강제하기 위함
- 구현을 강제함으로써 구현 객체의 같은 동작을 보장 가능
자바는 다중 상속을 지원하지 않음
- 다중 상속은 여러 개의 슈퍼 클래스를 두는 것
ex)
class MyVehicle extends car, plane {
@Override
public void goTo() {
super.drive();
}
}
- car, plane 클래스 모두 drive() 메소드를 갖고 있다면, 어떤 메소드가 실행될지 애매함 -> 다중 상속의 모호함
인터페이스는 여러 개의 인터페이스를 구현 가능
ex)
class Car implements Vehicle, Engine {
@Override
public void drive() {
@doSomething
}
}
상속은 슈퍼클래스의 기능을 이용하거나 확장하기 위해 사용, 다중 상속의 모호성 때문에 하나만 상속 받을 수 있음
인터페이스는 해당 인터페이스를 구현한 객체들에 대해서 동일한 동작을 약속하기 위해 존재
싱글톤 패턴
- 싱글톤 패턴은 단 하나의 인스턴스를 생성해 사용하는 디자인패턴
- 인스턴스가 1개만 존재해야 한다는 것을 보장하고 싶은 경우
- 동일한 인스턴스를 자주 생성해야 하는 경우 주로 사용됨(메모리 낭비 방지)
싱글톤 패턴 예시
- Spring Bean
- 스프링 빈 등록 방식은 기본적으로 싱글톤 스코프
- 스크링 컨테이너는 모든 빈들을 싱글톤으로 관리
- 스프링은 요청할 때마다 새로운 객체를 생성해서 반환하는 기능도 제공하긴 함
가비지 컬렉션(Garbage Collection)
- 가비지 컬렉션은 JVM의 메모리 관리 기법 중 하나로 시스템에서 동적으로 할당됐던 메모리 영역 중에서 필요 없어진 메모리 영역을 회수하여 메모리를 관리하는 기법
가비지 컬렉션 과정
- GC의 작업을 수행하기 위해 JVM이 어플리케이션의 실행을 잠시 멈추고, GC를 실행하는 thread를 제외한 모든 thread 작업을 중단 후, 사용하지 않는 메모리를 제거하고 작업이 재개됨
- GC 작업은 Young 영역에 대한 Minor GC와 Old 영역에 대한 Major GC로 구분됨
객체지향의 설계 원칙 (SOLID)
1. SRP(Single Responsibility Principle)
- 단일 책임 원칙
- "한 클래스는 하나의 책임만 가져야 함"
- 즉, 클래스는 그 책임을 완전히 캡슐화해야 함
ex) 결제 클래스는 오직 결제 기능만 책임, 이 클래스를 수정해야 한다면, 결제 관련 문제만 수정
2. OCP(Open-Closed Principle)
- 개방-폐쇄의 원칙
- "확장에는 열려있고, 수정에는 닫혀 있어야 함"
- 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려 있어야 하지만, 변경에는 폐쇄적이어야 함
- 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되는 원칙
ex) 캐릭터 하나 생성, 각 캐릭터마다 움직임이 다를 경우
움직임 패턴 구현을 하위 클래스에 맡긴다면,
캐릭터 클래스의 수정은 필요없고(Closed),
움직임 패턴만 재정의하면 됨(Open)
- OCP를 적용하기 위한 중요 매커니즘은 추상화와 다형성
3. LSP(Liskov Substitution Principle)
- 리스코프 치환 원칙
- "상위 타입은 항상 하위 타입으로 대체할 수 있어야 함"
- 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 역할을 하는데 문제가 없어야 함
- 다형성과 확장성을 극대화하여 , 개방-폐쇄 원칙을 구성

4. ISP(Interface Segregation Principle)
- 인터페이스 분리 원칙
- "인터페이스 내에 메소드는 최소한 일수록 좋음"
- "하나의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스가 좋음"
- 최소한의 기능만 제공하면서 하나의 역할에 집중하라는 뜻
- 단일 책임 원칙(SRP)와 인터페이스 분리 원칙은(ISP)는 같은 문제에 대한 2가지 다른 해결책
- 가능한 최소한의 인터페이스를 사용하도록 하여 단일 책임을 강조
- 일반적으로 ISP보다 SRP 할 것을 권장
5. DIP(Dependency Inversion Principle)
- 의존관계 역전 원칙
- "구체적인 것이 추상화된 것에 의존해야 함, 자주 변경되는 구체 클래스에 의존하지 말기"
- 구체적인 클래스보다 상위 클래스, 인터페이스, 추상클래스와 같이 변하지 않을 가능성이 높은 클래스와 관계를 맺음
- DIP 원칙을 따라는 가장 인기 있는 방법은 의존성 주입(DI)

자바 메모리 영역
- 자바 메모리 공간은 크게 Method 영역, Stack 영역, Heap 영역으로 구분, 데이터 타입에 따라 할당됨
1. 메소드(Method) 영역
- 전역변수와 static변수를 저장, Method 영역은 프로그램의 시작부터 종료까지 메모리에 남아있음
2. 스택(Stack) 영역
- 지역변수와 매개변수 데이터 값이 공간
- 메소드가 호출될 때 메모리가 할당되고, 종료되면 메모리가 해제됨
- LIFO(Last In First Out) 구조를 갖고 변수에 새로운 데이터가 할당되면 이전 데이터는 지워짐
3. 힙(Heap) 영역
- new 키워드로 생성되는 객체(인스턴스), 배열 등이 Heap 영역에 저장되며, 가비지 컬렉션에 의해 메모리 관리됨
각 메모리 영역이 할당되는 시점
1. Method 영역: JVM이 동작해서 클래스가 로딩될 때 생성
2. Stack 영역: 컴파일 타임 시 할당
3. Heap 영역: 런타임시 할당
- 컴파일 타임: 소스코드가 기계어로 변환되어 실행가능한 프로그램이 되는 과정
- 런타임: 컴파일 타임 이후 프로그램이 실행되는 때
클래스와 객체
- 클래스는 객체를 만들기 위한 설계도 혹은 틀, 객체를 생성하는데 사용
- 객체는 설계도(클래스)를 기반으로 생성되며, 자신의 고유 이름과 상태, 행동을 갖음
- 상태는 필드, 행동은 메소드라고 표현
- 객체에 메모리가 할당되어 실제로 활용되는 실체는 "인스턴스"
생성자
- 생성자는 클래스와 같은 이름의 메소드로, 객체가 생성될 때 호출되는 메소드
- 명시적으로 생성자를 만들지 않아도 default로 만들어지며, 생성자는 파라미터를 다르게하여 오버로딩할 수 있음
Wrapper Class
- 기본 자료형(Primitive data type)에 대한 객체 표현
- 기본 자료형 -> Wrapper class (Boxing)
- Wrapper class -> 기본 자료형 (UnBoxing)

Synchronized
- 여러 개의 쓰레드가 한 개의 자원을 사용하고자 할 때, 현재 데이터를 사용하는 쓰레드를 제외하고 나머지 쓰레드들은 데이터에 접근할 수 없게 막는 개념
- 데이터의 thread-safe를 하기 위해 자바에서 Synchronized 키워드를 제공해 멀티 쓰레드 환경에서 쓰레드간 동기화를 시켜 데이터의 thread-safe를 보장
- Synchronized는 변수와 메소드에 사용해서 동기화 할 수 있으며, Synchronized 키워드를 남발하게 되면, 오히려 프로그램의 성능 저하를 일으킬 수 있음
String, StringBuffer, StringBuilder
공통점
- String(문자열)을 저장하고 관리하는 클래스
차이점
- String은 불변의 속성을 갖고, StringBuffer와 StringBuilder는 가변의 속성을 가짐
- StringBuffer는 동기화를 지원하여, 멀티 쓰레드 환경에서 주로 사용
- StringBuilder는 동기화를 지원하지 않아 싱글 쓰레드 환경에서 주로 사용
String
String str = "hello"; // String str = new String("hello");
str = str + " world"; // hello world
- "hello" 값을 갖는 String 클래스의 참조변수 str이 가리키는 곳에 저장된 "hello"에 "world" 문자열을 더해 "hello world"로 변경한 것처럼 보이지만
- 기존에 "hello"값이 들어가있던 str이 "hello world"라는 값을 갖고 있는 새로운 메모리 영역을 가리키게 변경됨
- 처음 선언했던 "hello"로 값이 할당 되어 있던 메모리 영역은 Garbage 로 남아있다가, GC(Garbage Collection)에 의해 사라지게 됨
- String 클래스는 불변하기 때문에, 문자열을 수정하는 시점에 새로운 String 인스턴스가 생성됨

StringBuffer vs StringBuilder
- 동일한 API를 갖고 있지만
- "동기화의 유무"가 가장 큰 차이
- StringBuffer는 동기화 키워드를 지원하여 멀티쓰레드 환경에서 안전(thread-safe)
* String도 불변성을 갖기 때문에, 마찬가지로 멀티쓰레드 환경에서 안정성(thread-safe)
- StringBuilder는 동기화를 지원하지 않기 때문에, 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만, 동기화를 고려하지 않은 만큼 단일쓰레드에서의 성능은 StrinfBuffer보다 뛰어남
* 결론 *
- 단순히 성능만 놓고 본다면 StringBuilder > StringBuffer >>> String
- 각 클래스들은 성능 이슈 외에도 사용 편의성, 멀티 스레드 환경 등 여러가지 고려해야할 요인 존재
- String : 문자열 연산이 적고 멀티쓰레드 환경인 경우
- StringBuffer : 문자열 연산이 많고 멀티쓰레드 환경인 경우
- StringBuilder : 문자열 연산이 많고 단일 쓰레드이거나 동기화를 고려하지 않아도 되는 경우

접근 제한자
- 변수 또는 메소드의 접근 범위를 설정해주기 위해서 사용하는 Java의 예약어를 의미
1. public : 접근 제한이 없음 (같은 프로젝트 내 어디서든 사용 가능)
2. protected : 해당 패키지 내, 다른 패키지에서 상속받아 자손 클래스에서 접근 가능
3. (default) : 해당 패키지 내에서만 접근 가능
4. private : 해당 클래스에서만 접근 가능
클래스 멤버 변수 초기화 순서
1. static 변수 선언부 : 클래스가 로드 될 때 변수가 제일 먼저 초기화됨
2. 필드 변수 선언부 : 객체가 생성될 때 생성자 block보다 앞서 초기화됨
3. 생성자 block : 객체가 생성될 때 JVM 이 내부적으로 locking (thread-safe 영역)
static
- static 키워드는 사용한 변수나 메소드는 클래스가 메모리에 올라갈 때 자동으로 생성되며 클래스 로딩이 끝나면 바로 사용 가능, 즉 인스턴스(객체) 생성 없이 바로 사용 가능
- 모든 객체가 메모리를 공유한다는 특징
- GC 관리 영역 밖에 있기 때문에, 프로그램이 종료될 까지 메모리에 값이 유지된 채로 존재
static 을 사용하는 이유
- static은 자주 변하지 않는 값이나 공통으로 사용되는 값 같은 공용자원에 대한 접근에 있어서 매번 메모리에 로딩하거나 값을 읽어들이는 것보다 일종의 '전역변수'와 같은 개념을 통해 접근하는 것이 비용도 줄이고 효율을 높일 수 있음
- 인스턴스 생성 없이 바로 사용 가능하기 때문에, 프로그램 내에서 공통으로 사용되는 데이터들을 관리할 때 이용
Inner Class(내부 클래스)
1. 내부 클래스에서 외부 클래스의 멤버에 손쉽게 접근 가능
2. 서로 관련 있는 클래스를 논리적으로 묶어서 표현함으로써, 캡슐화를 증가시키고, 코드의 복잡성을 낮출 수 있음
3. 외부에서 내부 클래스에 접근할 수 없으므로, 코드의 보안성을 높일 수 있음
리플렉션(Reflection)
- 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
리플렉션이 사용되는 경우
- 코드 작성할 시점에는 어떤 타입의 클래스를 사용할지 모르지만, 런타임 시점에 지금 실행되고 있는 클래스를 가져와서 실행해야 하는 경우
- 프레임워크나 IDE에서 이런 동적인 바인딩을 이용한 기능 제공
- IntelliJ의 자동완성 기능, 스프링의 어노테이션이 리플렉션을 이용한 기능
Error와 Exception
Error
- 실행 중 일어날 수 있는 치명적인 오류
- 컴파일 시점에 체크할 수 없고, 오류가 발생하면 프로그램은 비정상 종료되며 예측 불가능한 UncheckedException에 속함
Exception
- Error보다 비교적 경미한 오류
- try-catch를 이용해 프로그램의 비정상 종료를 막을 수 있음
CheckedException vs UnCheckedException
- CheckedException은 실행하기 전에 예측 가능한 예외를 말하고, 반드시 예외 처리해야 함
- ex) IOException, ClassNotFoundException
- UncheckedException은 실행하고 난 후에 알 수 있는 예외, 따로 예외처리를 하지 않아도 됨
- ex) NullPointException, ArrayIndexOutOfBoundException
- RuntimeException은 UncheckedException을 상속한 클래스, RuntimeException이 아닌 것들은 CheckedException을 상속한 클래스
Optional API
- 개발할 떄 가장 많이 발생하는 예외 중 하나 NPE(NullPointerException)
- NPE를 피하려면, null 여부 검사를 필연적으로 하게 되는데 만약 null 검사를 해야하는 변수가 많은 경우, 코드가 복잡해지고 번거로움
- Java8부터 Optional<T>를 제공하여 null로 인한 예외가 발생하지 않도록 도와주고, Optional 클래스의 메소드를 통해 null 컨트롤 가능
컬렉션 프레임워크
- 다수의 데이터를 쉽고 효과적으로 관리할 수 있는 표준화된 방법을 제공하는 클래스의 집합
- 자바 컬렉션에는 List, Set, Map 인터페이스를 기준으로 여러 구현체가 존재하고, 이에 더해 Stack, Queue, 인터페이스도 존재
List, Set, Map, Stack, Queue
List
- 순서가 있는 데이터의 집합
- 데이터의 중복 허용
- 대표적인 구현체는 ArrayList -> Vector를 개선
- Vector, ArrayList, LinkedList, Stack, Queue
Set
- 순서가 없는 데이터의 집합
- 데이터의 중복을 허용하지 않음
- 대표적 구현체는 HashSet, 순서를 보장하기 위해서는 LinkedHashSet을 사용
(Map의 key-value 구조에서 key 대신 value가 들어가 value를 key로 하는 자료구조)
- HashSet, LinkedHashSet, TreeSet
Map
- 키와 값이 한 쌍으로 이뤄져 있고, 키를 기준으로 중복을 허용하지 않음
- 순서가 없음
- key의 순서를 보장하기 위해서 LinkedHashMap 사용
- HashMap, TreeMap, HashTable, Properties
Stack
- 직접 new 키워드로 사용할 수 있으며, Queue 인터페이스는 LinkedList에 new 키워드를 적용해 사용 가능
Set과 Map의 타입이 Wrapper Class가 아닌 Object를 받을 때 중복 검사 어떻게 할까?
- hashCode() 메소드를 오버라이딩하여 리턴된 해시코드 값이 같은지를 보고 해시코드 값이 다르다면 다른 객체로 판단
- 해시코드 값이 같으면 equals() 메소드를 오버라이딩하여 다시 비교, 이 2개가 모두 맞으면 중복 객체
Generic
- 제네릭은 데이터의 타입을 하나로 지정하지 않고, 사용할 때마다 범용적이고 포괄적으로 지정한다는 의미
- 제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있어 에러를 사전에 방지 가능
- 제네릭 코드를 사용하면 타입을 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어서 프로그램 성능 향상
// 제네릭을 사용하지 않을 경우
ArrayList list1 = new ArrayList();
list1.add("Choi");
// 타입 변환이 필요
String test = (String) list1.get(0);
// 제네릭을 사용한 경우
ArrayList<String> list2 = new ArrayList();
list2.add("Choi");
// 타입 변환 필요 없음
test = list2.get(0);
Generic 장점
1. 타입 안정성 제공
- 컴파일 타입에 타입 체크를 하기 때문에, 런타입에서 ClassNotFoundException과 같은 UncheckedException을 보장 받음
2. 타입체크와 형변환 생략 가능
- 코드 간결
Generic 특징
- 모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 타입 변수 T를 사용할 수 없음
- T는 인스턴스 변수로 간주되기 때문
- static 멤버는 인스턴스 변수를 참조 불가
- 제네릭 타입의 배열을 생성하는 것도 허용 불가
- 제네릭 배열 타입의 참조 변수를 선언하는 것은 가능하지만, new T[10]과 같이 배열 생성은 불가
- new 연산자 때문, 이 연산자는 컴파일 시점에 타입 T가 무엇인지 명확히 알아야 하기 때문
final / finally / finalize
1. final
- 클래스, 메소드 변수, 인자를 선언할 때 사용할 수 있으며, 한 번만 할당하고 싶을 때 사용
- 한 번 초기화되면 이후에 변경 불가
- final 메소드는 다른 클래스가 이 클래스를 상속할 때 메소드 오버라이딩을 금지
- 다른 클래스에서 이 클래스를 상속 불가
2. finally
- try-catch와 함께 사용되며, try-catch가 종료될 때 finally block이 항상 수행되기 때문
- 마무리 해줘야 하는 작어비 존재하는 경우, 해당하는 코드를 작성해주는 코드 블롷ㄱ
3. finalize
- Object 클래스에 정의되어 있는 메소드
- GC에 의해 호출되는 메소드로 절대 호출해서는 안되는 메소드
- GC가 발생하는 시점이 불분명하기 때문에 해당 메소드가 실행된다는 보장이 없음
- finalize() 메소드가 오버라이딩 되어 있으면 GC가 이뤄질 때 바로 Garbage Collecting되지 않음
- GC가 지연되면서 OOME(Out Of Memory Exception)이 발생할 수 있기 때문에 finalize() 메소드를 오버라이딩하여 구현하는 것을 권장하지 않음
직렬화(Serialize)
- 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술
- 반대로 직렬화된 바이트 형태의 데이터를 다시 객체로 변환하는 과정을 '역직렬화'
- JVM의 메모리에 상주(힙 or 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술