『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처
Serializable 인터페이스의 API에는 선언된 변수나 메소드가 없다. 이 인터페이스를 구현하면 JVM에서 해당 객체를 저장하거나 다른 서버로 전송할 수 있도록 해 준다.
아래와 같은 경우 필수로 구현해야 한다.
Serializable 인터페이스를 구현한다면 serialVersionUID
라는 값을 지정하는 것을 권장한다.
static final long serialVersionUID = 1L;
이 값은 해당 객체의 버전을 명시하는 데 사용된다.
다른 서버로 객체를 전송할 경우, 전송하는 서버와 전송받는 서버 모두 동일한 클래스가 있어야만 그 클래스의 객체임을 알고 데이터를 받을 수 있다.
객체의 내용이 바뀌었음에도 아무런 예외가 발생하지 않는다면 운영 상황에서 데이터가 꼬일 수 있기 때문에, 권장하지 않는다.
따라서 데이터가 바뀌면 serialVersionUID를 바꿔줘야 한다.
아래 클래스를 사용하면 객체를 저장하거나 읽을 수 있다.
클래스 | 설명 |
---|---|
ObjectOutputStream | 객체를 저장할 수 있다. |
ObjectInputStream | 저장해 놓은 객체를 읽을 수 있다. |
객체는 바이너리 파일로 저장된다.
public static main(String[] args) {
String fullPath = separator + "testPath" + separator + "serial.obj";
SerialDTO dto = new SerialDTO("테스트DTO");
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(fullPath);
oos = new ObjectOutputStream(fos);
oos.writeObject(dto);
} catch (Exception e) {
...
} finally {
...
}
}
객체는 바이너리 파일로 저장된다.
public static main(String[] args) {
String fullPath = separator + "testPath" + separator + "serial.obj";
FileInputStream fis= null;
ObjectInputStream ois = null;
try {
fos = new FileInputStream(fullPath);
oos = new ObjectInputStream(fis);
// Object로 반환하기 때문에 형 변환
SerialDTO dto = (SerialDTO) ois.readObject();
} catch (Exception e) {
...
} finally {
...
}
}
transient 예약어를 사용하여 선언한 변수는 Serializable 대상(저장 대상)에서 제외된다.
transient private int saveValue;
New IO를 뜻하며, 속도 개선을 위해 JDK 1.4에서부터 추가되었다.
아래와 같은 경우 사용된다.
NIO에서 제공하는 Buffer는 java.nio.Buffer 클래스를 확장하여 사용한다.
버퍼는 CD처럼 위치가 있다. 버퍼에 데이터를 담거나 읽는 작업을 수행하면 현재의 위치가 이동하고, 다음 위치에 있는 데이터를 바로 쓰거나 읽을 수 있다.
리턴 타입 | 메소드 | 설명 |
---|---|---|
int | capacity() | 버퍼에 담을 수 있는 크기(capacity) 리턴 |
int | limit() | 버퍼에서 읽거나 쓸 수 없는 첫 위치 리턴 (기본값: capacity 값) |
int | position() | 현재 버퍼의 위치 리턴 |
Buffer | flip() | limit 값을 현재 position으로 지정한 후, position을 0(가장 앞)으로 이동 |
Buffer | mark() | 현재 position을 mark |
Buffer | reset() | 버퍼의 position을 mark한 곳으로 이동 |
Buffer | rewind() | 현재 버퍼의 position을 0으로 이동. limit 값은 변경하지 않는다. |
int | remaining() | 계산 결과를 리턴 |
boolean | hasRemaining() | positon와 limit 값에 차이가 있을 경우 true 리턴 |
Buffer | clear() | 버퍼를 지우고 현재 position을 0으로 이동하며, limit 값을 버퍼의 크기로 변경 |
Buffer | get() | 현재 position의 데이터(한 바이트)를 읽는다. |
// 데이터를 저장할 경우
public void writeFile() throws Exception{
String fileName= separator + "testPath" + separator + "nio.txt";
String data = "test save data";
byte[] byteData = data.getBytes();
// 1. getChannel()로 FileChannel 생성
FileChannel channel =new FileOutputStream(fileName).getChannel();
// 2. wrap() 이라는 static 메소드를 호출해 ByteBufer 생성
ByteBuffer buffer = ByteBuffer.wrap(byteData);
// 3. buffer 객체를 넘겨주면 파일에 쓴다.
channel.write(buffer);
channel.close();
}
public void readFile() throws Exception {
String fileName= separator + "testPath" + separator + "nio.txt";
// 1. getChannel()로 FileChannel 생성
FileChannel channel = new FileInputStream(fileName).getChannel();
// 2. allocate() 메소드로 buffer 객체 생성
// 매개 변수는 데이터가 저장되는 크기이다.
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 3. 데이터를 buffer에 담는다.
channel.read(buffer);
// 4. buffer에 담겨있는 데이터의 가장 앞으로 이동
buffer.flip();
// 5. 데이터가 남아있는지 확인
while(buffer.hasRemaining()) {
// 6. 한 바이트씩 데이터를 읽는다.
System.out.print((char)buffer.get());
}
channel.close();
}
Me: JVM에서 해당 객체를 파일로 저장하거나 다른 서버로 전송할 수 있도록 해준다.
Me: 해당 객체의 버전을 명시하는 것이다. 다른 서버로 객체를 전송할 때 같은 객체인지 확인할 수 있도록 해준다.
Me: FileInputStream, FileOutputStream, ObjectInputStream, ObjectOutputStream
Me: 해당 예약어로 선언한 변수는 Serializable (저장) 대상에서 제외된다.
Me: 속도 성능 개선을 위해 추가되었다.
Me: 물건을 중간에서 처리하는 도매상의 역할을 한다.
Me: 도매상에서 물건을 사고, 소비자에게 물건을 파는 소매상의 역할을 한다. 버퍼에 데이터를 담을 수 있다.
Me: position(), limit(), capacity()
Me: flip(), mark(), reset(), rewind(), remaining(), hasRemaining(), clear()
💡 책에 있는 내용이 아닙니다.
책을 읽으며 설명이 더 필요하거나, 추가로 궁금한 점에 대해 질문 형식으로 작성 후, 답을 구해보고 있습니다.
참고한 사이트나 영상은 [출처]로 달아두었으며, 오류 지적은 언제나 환영합니다.
객체에 많은 속성이 포함되어 있는 경우, 인스턴스를 생성할 때 기존 패턴에는 문제가 있었다.
객체를 생성할 때 인스턴스를 만드는 과정이 복잡하거나 매개 변수의 수가 많을 때 사용하는 디자인 패턴 중 하나이다.
빌더 패턴은 다음과 같은 주요 구성 요소로 이루어져 있다.
💡 빌더 패턴은 GOF의 디자인 패턴에서 소개하는 빌더 패턴과 이펙티브 자바 책에서 소개하는 빌더 패턴 구조로 나뉜다고 하는데, 위 구성 요소는 GOF의 디자인 패턴이며 아래 코드는 이펙티브 자바에서 소개하는 빌더 패턴의 예이다.
```java
// Product
public class Person {
private String name;
private int age;
private String address;
// Constructor is private to enforce the use of the builder
private Person() {}
// Getters
public static class Builder {
private String name;
private int age;
private String address;
public Builder(String name) {
this.name = name;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Person build() {
Person person = new Person();
person.name = this.name;
person.age = this.age;
person.address = this.address;
return person;
}
}
}
// Client
public class Client {
public static void main(String[] args) {
Person person = new Person.Builder("John")
.age(30)
.address("123 Main St")
.build();
}
}
```
단, 불변성의 경우 추가적인 규칙이 필요하다.
public class Person {
private final String name;
private final int age;
private final String address;
private Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public static class Builder {
...
public Person build() {
// 여기서 불변성을 강제
return new Person(name, age, address);
}
}
}
빌더 패턴이 객체 생성의 복잡성을 다루기 위한 것이라면, DTO는 데이터를 저장하고 전송하는 데 중점을 두고 있다.
DTO가 불변성을 가져야 하거나, 가독성을 높이기 위해 빌더 패턴을 사용하고는 한다. lombok을 사용하면 빌더 패턴을 쉽게 구현할 수 있다.
그러나, 모든 DTO에 빌더 패턴을 사용해야 하는 것은 아니다. 오히려 오버 스펙이라는 의견도 있으며, 이건 취향의 문제라는 사람도 있었다.
빌더 패턴은 코드 복잡도가 증가하는 이슈도 있으니, DTO에 꼭 빌더 패턴이 필요한 지 고민해보고 적용하도록 하자.
지양해야 한다는 의견도 있지만, DTO는 단순히 데이터를 전달하는 용도로 사용하기 때문에 자유롭게 사용해도 된다는 의견도 있다.
결국 빌더 패턴 때와 마찬가지로 데이터의 일관성 및 불변성을 고려해 작성할 것이냐 말 것이냐가 주요 포인트가 되는 거 같다.
여러 이유가 있겠지만, 결국 효율을 위해서 그런 것이다. 아래는 ChatGPT의 대답을 기반으로 찾아본 내용이다.
NIO에서도 채널과 함께 스트림을 사용할 수 있는 클래스들이 있다. 그 중에서도, 특히 Channels 클래스를 사용해 스트림과 채널을 연결할 수 있다.
Selector는 비동기 입출력을 지원하기 위한 핵심 클래스 중 하나다.
두 메소드의 차이는 메모리 할당 방식에 있다.
일반적으로는 allocate()를 사용하는 것이 편리하고 안전하며, allocateDirect()는 성능 최적화를 위해 사용한다.
Builder Method Design Pattern in Java - GeeksforGeeks
Should i use builder pattern in DTO?
How to use builders efficiently when mapping Dto, Entity