# 직렬화 (Serializable)
- 객체 또는 데이터를 외부에서도 사용할 수 있도록 바이트의 형태로 변환하는 작업을 뜻한다.
- 반대로 바이트로 변환된 데이터를 다시 객체로 변환하는 작업을 역직렬화라고 한다.
- 모든 클래스가 직렬화하는 것은 불가능하고 Serializable 인터페이스를 구현하는 클래스만 직렬화가 가능하다.
# serialVersionUID
- 직렬화의 고유 버전
- 직렬화, 역직렬화를 진행할 때 이 값으로 클래스의 특정 버전이 맞는지를 판단한다.
- 값을 선언하지 않아도 default값을 생성하므로 사용에는 문제가 없지만 컴파일러에따라서 변경이 될 수 있으므로 작성을 권장한다.
public class 클래스명 implements Serializable {
private static final long serialVersionUID = 1L;
}
[참고] 자바에서의 직렬화는 Serializable 인터페이스를 클래스에서 구현하는 방식으로 사용한다.
# RspInfo.java
package ex1_object_stream;
import java.io.Serializable;
public class RspInfo implements Serializable{
/* 직렬화 (Serializable)
* - 객체 또는 데이터를 외부에서도 사용할 수 있도록 바이트 형태로 변환하는 작업
* - 인터페이스이므로, 클래스 implements Serializable 방식으로 구현한다.
* - 반대로 바이트로 변환된 데이터를 다시 객체로 변환하는 작업을 역직렬화라고 부른다.
*/
// - 이 클래스는 유저의 아이디와 전적을 관리한다.
// - Object Stream 을 사용하여 객체를 통으로 읽고 쓰므로 객체의 직렬화가 필수이다.
/* java.io.NotSerializableException
* - 직렬화할 수 없다. 라는 오류
* - heap 영역 안에 RspInfo 클래스 내에 있는 각각의 변수 주소 값이 다 흩어져있어서 생긴 오류이다.
* - Object Stream에서는 클래스의 id, win, lose, draw 변수를 기억하고자하지만 각 주소 값이 다르다보니 어떤 값을 참조해야하는지 알 수 없다.
*
* - 그래서, 하나의 배열 형태를 만들어서 그 곳에 승무패의 정보를 저장한 뒤
* - 그 배열의 주소 값을 Object Stream 에 전달해야한다.
*
* - 위의 작업이 직렬화이다. 이 작업이 없으면 객체를 직렬화 할 수 없다.
*/
// 유저의 아이디를 저장할 변수
private String id;
// 승무패를 저장할 변수
private int win, lose, draw;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getWin() {
return win;
}
public void setWin(int win) {
this.win = win;
}
public int getLose() {
return lose;
}
public void setLose(int lose) {
this.lose = lose;
}
public int getDraw() {
return draw;
}
public void setDraw(int draw) {
this.draw = draw;
}
}
# RspMain.java
package ex1_object_stream;
import java.util.Random;
import java.util.Scanner;
public class RspMain {
public static void main(String[] args) {
//
RspInfo info = new RspInfo();
Scanner sc = new Scanner(System.in);
String id = "";
int win = 0;
int lose = 0;
int draw = 0;
//
System.out.println("id : ");
id = sc.next();
info.setId(id);
//
try {
// 정보를 가져오는 클래스를 선언
ScoreLoader s1 = new ScoreLoader(id);
// 전적 변수로 저장
win = s1.getInfo().getWin();
lose = s1.getInfo().getLose();
draw = s1.getInfo().getDraw();
// 전적 세팅
info.setWin(win);
info.setLose(lose);
info.setDraw(draw);
} catch (Exception e) {
// TODO: handle exception
}
// 전적 확인
System.out.printf("%d승 %d패 %d무\n",win,lose,draw);
//
while(true) {
//
int random = new Random().nextInt(2);
//
System.out.println("가위(s) | 바위(r) | 보(p) : ");
String user = sc.next();
int usercnt = 0;
//
switch (user) {
case "s":
usercnt = 0;
break;
case "r":
usercnt = 1;
break;
case "p":
usercnt = 2;
break;
} // switch
// 경우의 수를 판단
if(usercnt-random==-2||usercnt-random==1) {
// 선행으로 집어넣는다.
info.setWin(++win);
System.out.println("이겼습니다!");
} else if(usercnt-random==0) {
info.setDraw(++draw);
System.out.println("비겼습니다.");
} else {
info.setLose(++lose);
System.out.println("졌습니다.");
}
//
System.out.printf("%d승 %d패 %d무%n",info.getWin(),info.getLose(),info.getDraw());
// 더할건지 질문
System.out.print("한판 더? y / n : ");
String exit = sc.next();
//
if(!exit.equalsIgnoreCase("y")) {
System.out.println("게임을 종료합니다.");
break;
} else {
}
} // while
// Object Stream 동작
// - 게임이 종료되었으므로 객체를 저장한다.
// - 저장은 한번만해주면 되니까 익명클래스로 선언
new ScoreWriter(info);
} //main
}
# ScoreLoader.java
package ex1_object_stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ScoreLoader {
// 정보 클래스
private RspInfo info;
// 정보를 가져오는 메소드
public RspInfo getInfo() {
return info;
}
// 생성자
public ScoreLoader(String id) {
String path = "C://JAVA1_0713_KTH/Game/"+id+"/Gamesav.sav";
File f = new File(path);
//
if(f.exists()) {
// 처음이 아닌 사람인 경우
FileInputStream fis = null;
ObjectInputStream ois = null;
// 파일 로드
try {
//
fis = new FileInputStream(f);
ois = new ObjectInputStream(fis);
// readObject 메소드로 객체 자체를 받아온다.
// Object가 더 큰 객체이므로 큰 객체를 작은 객체에 넣으니까 형변환을 해줘야한다.
info = (RspInfo)ois.readObject(); // New가 없어도, 주소 값을 받으니 heap 영역이 있다.
System.out.println("로드 완료");
} catch (Exception e) {
e.printStackTrace();
System.out.println("로드 실패");
} finally {
// 메인에서 다시 스로우 하지 않도록 try 처리
try {
ois.close();
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else {
// 첫 시작인 사람
System.out.println("아이디 생성을 환영합니다.");
}
}
}
# ScoreWriter.java
package ex1_object_stream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ScoreWriter {
public ScoreWriter(RspInfo info) {
//
String path = "C://JAVA1_0713_KTH/Game/"+info.getId()+"/Gamesav.sav";
// 전적 정보를 저장한다.
File dir1 = new File("C://JAVA1_0713_KTH/Game/");
//
if(!dir1.exists()) {
// 폴더가 없으면 생성한다.
dir1.mkdirs();
}
// 분류 폴더는 생성되었으니, 사용자별 폴더가 있는지 확인
File dir2 = new File(dir1,info.getId());
if(!dir2.exists()) {
// 없으면 생성
dir2.mkdirs();
}
/* ObjectOutputStream
* - 클래스를 통으로 저장할 수 있게해주는 보조적인 스트림
* - 클래스를 입출력하는 경우 반드시 바이트 기반으로 진행해야한다.
* - 전송하는 객체 파일은 반드시 직렬화 인터페이스를 구현해야한다.
*/
// RspInfo로 아이디, 명칭, 전적까지 전부 받았다.
FileOutputStream fos = null;
ObjectOutputStream oos = null;
//
try {
// 파일 쓰기
fos = new FileOutputStream(path);
oos = new ObjectOutputStream(fos);
// 객체를 저장하는 경우 writeObject를 사용한다.
oos.writeObject(info); // 아이디 폴더 안에 Gamesave.sav 로 저장한다.
} catch (Exception e) {
e.printStackTrace();
System.out.println("기록저장 실패");
} finally {
// 메인에서 throws를 사용하지 않도록 try - catch 사용
try {
oos.close();
fos.close();
} catch (IOException e) {
}
}
} // constructor
}
'Web > Java' 카테고리의 다른 글
[Java] 입출력 (0) | 2025.01.09 |
---|---|
[Java] 스레드 (0) | 2025.01.09 |
[Java] 에러 (0) | 2025.01.09 |
[Java] 인터페이스 (0) | 2025.01.09 |
[Java] 상속 (0) | 2025.01.09 |