본문 바로가기
☁︎KOSTA/☂︎KOSTA-JAVA

[KOSTA] Spring 기반 Cloud 서비스 구현 개발자 양성 (Day 19) - Object Serialization (객체 직렬화), mini project에 직렬화 적용, Thread

by 하_센세 2022. 8. 25.

2022.08.25 [THU]

- Day 19- 

 

수업 주요 목차

  • Object Serialization (객체 직렬화)
  • mini project에 직렬화 적용
  • Thread (다음주 화~수요일까지)

🤖Review

 

 

- interface의 다양한 계층구조 형성 예제

package step1;

interface Player{}
class GomPlayer implements Player{}
class Youtube implements Player{}
class CdPlayer{}
public class TestInterface {
	public static void main(String[] args) {
		GomPlayer gom=new GomPlayer();
		Youtube you=new Youtube();
		CdPlayer cd=new CdPlayer();
		System.out.println(gom instanceof Player);
		System.out.println(you instanceof Player);
		System.out.println(cd instanceof Player);
	}
}

true
true
false

 

transient : 직렬화 대상에서 제외시키는 것

 

1. Serialization&DeSerialization  복습 + Transient

 

- Account class (계좌 정보에 대한 매개변수 담당 클래스로 Serializable implements 함)

package step2;

import java.io.Serializable;

public class Account implements Serializable {
	private static final long serialVersionUID = -2566446155356415019L;
	private long accountNo;
	private String name;
	private long balance;
	private transient String password; // transient keyword : 직렬화 대상에서 제외
	public Account(long accountNo, String name, long balance, String password) {
		super();
		this.accountNo = accountNo;
		this.name = name;
		this.balance = balance;
		this.password = password;
	}
	public long getAccountNo() {
		return accountNo;
	}
	public void setAccountNo(long accountNo) {
		this.accountNo = accountNo;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public long getBalance() {
		return balance;
	}
	public void setBalance(long balance) {
		this.balance = balance;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public static long getSerialversionuid() {
		return serialVersionUID;
	}
	@Override
	public String toString() {
		return "Account [accountNo=" + accountNo + ", name=" + name + ", balance=" + balance + "]";
	}
	
}

 

 

- Test 1 : Serialization

package step2;

import java.io.IOException;

public class TestSerializeAccountService2 {
	public static void main(String[] args) {
		SerializeAccountService service=new SerializeAccountService();
		Account account=new Account(1,"아이유",100,"javaking");
		try {
			service.executeSerialize(account);
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("계좌객체 직렬화하여 파일에 저장");
	}
}

 

- Test 2 : Deserialization

 

package step2;

import java.io.IOException;

public class TestSerializeAccountService {
	public static void main(String[] args) {
		SerializeAccountService service=new SerializeAccountService();
		Account account;
		try {
			account = service.executeDeSerialize();
			System.out.println("파일에 저장된 계좌객체를 역직렬화하여 계좌정보 확인");
			System.out.println("계좌번호: "+account.getAccountNo()); //1
			System.out.println("계좌주명: "+account.getName()); //아이유
			System.out.println("잔액: "+account.getBalance()); // 100
			System.out.println("비밀번호: "+account.getPassword()); //null : transient이기 때문
		} catch (ClassNotFoundException | IOException e) {
			e.printStackTrace();
		}
	}
}

 

-Service 클래스

package step2;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import common.Path;

public class SerializeAccountService {
	private String filePath=Path.TEST_DIR+"account.obj";
	public void executeSerialize(Account account) throws FileNotFoundException, IOException {
		new File(filePath).getParentFile().mkdirs();
		ObjectOutputStream oos=null;
		try {
			oos=new ObjectOutputStream(new FileOutputStream(filePath));
			oos.writeObject(account);
		} finally {
			if(oos!=null)
				oos.close();
		}
	}
	
	public Account executeDeSerialize() throws FileNotFoundException, IOException, ClassNotFoundException {
		ObjectInputStream ois= null;
		Account account=null;
	try {
		ois=new ObjectInputStream(new FileInputStream(filePath));
		account=(Account) ois.readObject();
		} finally {
			if(ois!=null)
				ois.close();
		}	
		return account;
	}
}

(* new File(filePath).getParentFile().mkdirs(); -> 이부분은 어제와 마찬가지로 디렉토리가 상위 디렉토리에 생성이 안되어서 만들어주고 시작해야 오류가 안뜸)

 

- Test 1 : Serialization 결과

계좌객체 직렬화하여 파일에 저장

- Test 2 : Deserialization 결과

파일에 저장된 계좌객체를 역직렬화하여 계좌정보 확인
계좌번호: 1
계좌주명: 아이유
잔액: 100
비밀번호: null

 

 

2. ArrayList 직렬화/역직렬화 예제

  • ArrayList<Car> 리스트 객체를 직렬화하여 파일에 저장
  • Collection 계열의 List, Map, Set 구현체들은 implements Serializable 처리가 되어 있다.Serializable 계층구조 하에 있다)
  • Elements 즉 요소로 저장되는 Car class 또한 implements Serializable 해야함

- Car class

package step3;

import java.io.Serializable;

public class Car implements Serializable {
	private static final long serialVersionUID = 8377067551146320109L;
	private String model;
	private String color;
	public Car(String model,String color) {
		this.model=model;
		this.color=color;
	}
	public String getModel() {
		return model;
	}
	public void setModel(String model) {
		this.model = model;
	}
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	@Override
	public String toString() {
		return "Car [model=" + model + ", color=" + color + "]";
	}
}

 

- Test 1 : 파일에 객체를 직렬화 하여 저장하는 예제 

package step3;

import java.io.IOException;
import java.util.ArrayList;

public class TestSerialListService {
	public static void main(String[] args) {
		SerialListService service=new SerialListService();
		ArrayList<Car> list=new ArrayList<>();
		list.add(new Car("소나타","white"));
		list.add(new Car("sm6","red"));
		list.add(new Car("k5","black"));
		try {
			service.serializaCarList(list);
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("자동차 리스트를 직렬화하여 파일에 저장");
		
	}
}

 

- Test 2 : 파일에 직렬화 되어 저장된 리스트 객체를 역직렬화하여 확인하는 예제

package step3;

import java.io.IOException;
import java.util.ArrayList;

public class TestSerialListService2 {
	public static void main(String[] args) {
		SerialListService service=new SerialListService();
		ArrayList<Car> list;
		try {
			list = service.deSerializationCarList();
			for(Car car:list) //list의 첫번째 요소부터 마지막 요소까지 car변수에 할당
				System.out.println(car.toString());	
				//System.out.println(car.getModel()+" "+car.getColor());
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		}
	// for(int i=0;i<list.size();i++) -> 위와 같은 동작
	// 		System.out.println(list.get(i).getModel()+""+list.get(i).getColor());
	}
}

 

- SerialListService

package step3;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import common.Path;

public class SerialListService {
	private String filePath=Path.TEST_DIR+"carList.obj";
	public void serializaCarList(ArrayList<Car> list) throws FileNotFoundException, IOException {
		ObjectOutputStream oos=null;
		try {
			oos=new ObjectOutputStream(new FileOutputStream(filePath));
			oos.writeObject(list);
		} finally {
			if(oos!=null)
				oos.close();
		}
	}
	@SuppressWarnings("unchecked") // 체크하지 않겠다는 어노테이션
	public ArrayList<Car> deSerializationCarList() throws FileNotFoundException, IOException, ClassNotFoundException {
		ObjectInputStream ois=null;
		ArrayList<Car> list=null;
		try {
			ois=new ObjectInputStream(new FileInputStream(filePath));
			list=(ArrayList<Car>) ois.readObject();
		} finally {
			if(ois!=null)
				ois.close();
		}
			return list;
	}

	
}

- Test 1 : 결과물

자동차 리스트를 직렬화하여 파일에 저장

- Test 2 : 결과물

Car [model=소나타, color=white]
Car [model=sm6, color=red]
Car [model=k5, color=black]

 

 

 

 

Thread : 프로세스 내부의 세부적 실행단위, 스레드의 사전적 의미는 실

Process : 현재 실행 중인 프로그램을 말함

ex) 동영상 플레이어가 실행 중이면 프로세스이고 그 동영상 플레이어 프로세스 내부의 세부적 실행단위인 영상, 음향, 자막과 같은 것이 스레드이다.

이들이 동시에 실행되는 것을 멀티 스레딩(Multi Threading)이라고 한다. 

 

여러개의 실(thread)가 모여 옷(process)을 구성한다.

 

 

- Thread  생성방법

1. extends thread

2. implements Runnable 


//2번을 더 추천하는 이유는 java는 단일상속이기 때문

 

- TestThread 1

  • 한눈에 보기 위하여 하나의 파일에서 진행
package step4;
class WorkerThread extends Thread{
	public void run() {
		// thread의 실행내용을 정의 : ex) 영상을 제공하다, 음향을 들려주다.
		for(int i=0;i<10;i++) 
		System.out.println(Thread.currentThread().getName()+"스레드 실행");
	}
}
class Worker{
	public void go() {
		for(int i=0;i<10;i++)
			System.out.println("worker go");
	}
}
public class TestThread1 {
	public static void main(String[] args) {
		//Test1 : thread가 아닌 일반 클래스 객체 생성해서 go() 메서드를 호출해본다.
		Worker worker=new Worker();
		worker.go();
		// 여러번 실행해도 단일 스레드 환경이므로 go메서드가 다 실행된 후에 메인이 종료된다.
		
		//Test2: thread를 생성해서 start 시켜서 첫번째 테스트와 비교해본다.
		WorkerThread wt=new WorkerThread();
		wt.setName("자비스 ");
		wt.start(); // 현 스레드를 실행가능상태(runnable)로 보내서 JVM이 실행하게 한다.
		System.out.println("main thread 종료");
		//
	}
}

worker go
worker go
worker go
worker go
worker go
worker go
worker go
worker go
worker go
worker go
main thread 종료
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행
자비스 스레드 실행

 

 

 

- TestThread 2

package step5;

class VideoWorker{
	public void video() {
		for(int i=0;i<10;i++)
		System.out.println("영상을 제공하다");
	}
}
public class TestThread2 {
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName()+"스레드 시작");
		VideoWorker worker=new VideoWorker();
		worker.video();
		System.out.println(Thread.currentThread().getName()+"스레드 종료");
	}
}

main스레드 시작
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
main스레드 종료

 

 

- TestThread 3

package step6;

class VideoWorker implements Runnable{
	@Override
	public void run() { // JVM에 의해 스케줄링되면 run 메서드가 실행
		video();
	}
	
	public void video() {
		for(int i=0;i<10;i++)
		System.out.println(Thread.currentThread().getName()+"스레드가 영상을 제공하다");
	}

}
public class TestThread3 {
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName()+"스레드 시작");
		VideoWorker worker=new VideoWorker();
		Thread thread=new Thread(worker,"영상일꾼");
		thread.start(); // 스레드를 실행가능상태로 보낸다.
		System.out.println(Thread.currentThread().getName()+"스레드 종료");
	}
}

main스레드 시작
main스레드 종료
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다

 

TestThread2와 비교해보면,

TestThread2는 단일 thread, TestThread3은 multi thread(여러개의 스레드)를 실행하는 예제

 


TestThread2는 단일스레드 환경이므로 VideoWorker의 video method가 반드시 모두 수행된 후에 main이 종료되었다.
TestThread3는 멀티스레딩 환경이므로 VideoWorker Thread를 main thread가 start 즉 실행 가능 상태로 보낸 후 자신은 종료되고 이후 스케줄링을 받은 VideoWorker Thread가 run 즉 실행하고 종료된다..


- TestThread 4

package step7;

public class TestThread4 {
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName()+"스레드 시작");
		Thread audioThread=new Thread(new AudioWorker(), "오디오 일꾼");
		audioThread.start(); // 오디오 스레드를 실행 가능 상태로 보낸다.
		Thread videoThread=new Thread(new VideoWorker(), "비디오  일꾼");
		videoThread.start(); // 비디오 스레드를 실행 가능 상태로 보낸다.
		System.out.println(Thread.currentThread().getName()+"스레드 종료");
	}
}

-video

package step7;

public class VideoWorker implements Runnable {

	@Override
	public void run() {
		try {
			video();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void video() throws InterruptedException {
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+" 스레드가 영상을 재생하다");
			Thread.sleep(1000);
		}
	}
}

-audio

package step7;

public class AudioWorker implements Runnable {

	@Override
	public void run() {
		try {
			audio();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void audio() throws InterruptedException {
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+" 스레드가 음향을 재생하다");
			Thread.sleep(2000); // 2초간 스레드가 실행을 중지한 후 다시 재개
		}
	}
}

main스레드 시작
오디오 일꾼 스레드가 음향을 재생하다
main스레드 종료
비디오  일꾼 스레드가 영상을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
비디오  일꾼 스레드가 영상을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
오디오 일꾼 스레드가 음향을 재생하다
오디오 일꾼 스레드가 음향을 재생하다

main thread는 video, audio thread 실행환경으로 보내주고 할일 끝, 스케줄링된 audio, video 가 실행되고 종료된다.

 


🔎 Eclipse 실습 내용 (mini project)

 

19day-schoolproject-IO-Serialization


-요구사항-
콘솔 ui 기반 학교 구성원 관리 프로그램에 추가적으로 시스템 종료시점에 구성원 정보를 직렬화하여 파일에 저장한다.


시스템 시작시점에 구성원 정보를 역직렬화하여 메모리에 저장, 동작시키도록 한다.


만약 경로에 해당하는 디렉토리 및 파일이 없다면 자동 생성하도록 한다.
(저장하는 경로 : kosta250\iotest\school\member-list.obj)





-설계-
직렬화/역직렬화 method를 UML class diagram 상에서 어디에 추가할 것인지 토의

 

- Member class

public abstract class Member implements Serializable {
	private static final long serialVersionUID = 1780013482668466302L;

+ Serializable implements 해주고 시리얼버전UID 생성해주기 (부모에 생성 해준거라 자식들도 다 생성해줘야함)

 

 

-SchoolService class에 추가한 method들

public SchoolService() { // 직렬화 할 파일 경로에 해당하는 디렉토리가 존재하지 않는다면 동적으로 디렉토리를 생성한다.
	File f1=new File(path);
	File f2=f1.getParentFile();
	if(f2.isDirectory()==false)
		f2.mkdirs();
}
	
/*
 * 객체 역직렬화 작업을 하는 method
 */
@SuppressWarnings("unchecked")  
public void loadData() throws FileNotFoundException, IOException, ClassNotFoundException {
	File f1=new File(path);
	if(f1.isFile()) { 
		// 여기에 list새로 만들어줬었는데 위에 매개변수로 선언되어 있기 때문에 새로운 걸 만들면 안됨
		ObjectInputStream ois=null; // 얘 위치 위 였는데 여기로 내려줌
	try {
		ois=new ObjectInputStream(new FileInputStream(f1)); // path로 써줘도 되고 f1으로 위에서 선언된 참조변수로 써줘도 된다.
		this.list=(ArrayList<Member>) ois.readObject(); // this. 안써줬었음 근데 정상작동 됐었음
	} finally {
		if(ois!=null)
			ois.close();
		}
	}
}
	

/*
 * 객체 직렬화 작업을 하는 method
 */
public void saveData() throws FileNotFoundException, IOException{
	ObjectOutputStream oos=null;
	try {
		oos=new ObjectOutputStream(new FileOutputStream(path));
		oos.writeObject(list);
	} finally {
		if(oos!=null)
			oos.close();
	}
}

 

- ConsoleUI execute() method 수정사항

public void execute() throws FileNotFoundException, IOException, ClassNotFoundException {
	ss.loadData(); // 
	System.out.println("*******학사관리프로그램을 시작합니다******");
	exit: while (true) {
		System.out.println("1. 추가 2. 삭제 3. 검색 4. 전체회원보기 5.종료");
		String info = scanner.nextLine();
		switch (info) {
		case "1":
			addView();
			break;
		case "2":
			deleteView();
			break;
		case "3":
			findView();
			break;
		case "4":
			ss.printAll();
			break;
		case "5":
			System.out.println("******학사관리프로그램을 종료합니다******");
			break exit;
		default:
			System.out.println("잘못된 입력값입니다.");
			break;
		}
	}
	ss.saveData();
	scanner.close();
}

 

-Test class (try~catch 여기서)

package step2;

import java.io.IOException;

import step1.ConsoleUI;

public class TestMenu {
	public static void main(String[] args) {
		try {
			new ConsoleUI().execute();
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

*******학사관리프로그램을 시작합니다******
1. 추가 2. 삭제 3. 검색 4. 전체회원보기 5.종료
4
Student [tel=123, name=아이유, adress=서울, stuId=2323]
1. 추가 2. 삭제 3. 검색 4. 전체회원보기 5.종료
5
******학사관리프로그램을 종료합니다******

 

 

🤮error🤮

1. loadDate()가 saveData() 보다 위에 있기때문에 Object파일이 없으면 에러가 난다.그래서 loadData()에 만약 파일이 있는 경우에만 동작하라는 조건을 넣어줘야 함

2. return값이 있는 형태로 썼다가 void로 바꿈 (리턴했을 때 사용하는 메소드가 없기 때문에 이 상황에서는 void가 맞음)

 

강사님께 여쭤보니 ConsoleUI에 method따로 만들어서 해줘도 됨!( 지금은 그냥 직관적인게 좋아서 ss.loadData();를 써준 것이고 나중에다양한 곳에서 활용할 거면 예쁘고 깔끔하게 method 안에 정리해주는 것도 좋다고 하셨다.)

 

 

 

 

//오늘의 숙제

복습