티스토리 뷰
1. 객체 입출력 보조 스트림
- 객체를 파일 또는 네트워크로 입/출력할 수 있는 기능을 제공한다.
- 객체 직렬화 : 객체는 문자가 아니므로 바이트 기반 스트림으로 데이터를 변경할 필요가 있다. ( 객체를 바이트로 만드는 것을 직렬화, 바이트를 객체로 만드는 것을 역직렬화라고 한다. )
- 해당 보조 스트림 : ObjectInputStream / ObjectOutputStream
- 직렬화 가능한 클래스 ( Serializable ) :
- 자바에서는 Serializable 인터페이스를 구현한 클래스만 직렬화 할 수 있도록 제한
- 단, transient 필드는 제외한다. ( transient(일시적인) 필드는 직렬화에서 제외가 된다. )
- Transient 필드는 직렬화로 보내지 않았기에, 역직렬화 할 필요도 없다.
- 객체 직렬화를 할 때, private 필드를 포함한 모든 필드를 바이트로 변환할 수 있다.
2. serialVersionUID 필드 ( p. 1047 )
- 직렬화된 객체를 역직렬화 할 때는 직렬화 했을 때에 같은 클래스를 사용해야 한다.
- 클래스의 이름이 같더라도 클래스의 내용이 변경된 경우, 역직렬화에 실패한다.
- 모든 객체는 클래스를 통해서 생성되기 때문에, 동일한 클래스를 가지고 있지 않다면 객체를 역직렬화 할 수 없다.
- serialVersionUID :
- 서로 같은 클래스임을 알려주는 식별자 역할을 해준다.
- 서로 필드가 다르다 하더라도 serialVersionUID가 서로 같으면 같은 클래스로 간주
- Serializable 인터페이스 구현하면, 컴파일 시 자동적으로 serialVersionUID 정적 필드를 추가해 준다.
- 재컴파일하면, serialVersionUID의 값이 변경된다.
- 불가피한 수정이 있을 경우에는 명시적으로 serialVaersionUID를 선언하면 된다.
- Ex. static final long serialVersionUID = 정수값;
3. writeObject( )와 readObject( ) 메소드
- 1) writeObject( ObjectOutputStream out ) 메소드 :
- 직렬화 직전에 자동 호출되며, 추가 직렬화 할 내용을 작성할 수 있다.
- 2) readObject( ObjectInputStream in ) 메소드 :
- 역직렬화 직전에 자동으로 호출되며, 추가적으로 역직렬화 내용을 작성할 수 있다.
- 3) 추가 직렬화 및 역직렬화가 필요한 경우 :
- 부모 클래스가 Serializable을 implements 하지 않고, 자식 클래스가 Serializable을 구현했을 때 필요하게 된다.
4. 네트워크
- 네트워크 ( Network ) : 여러 대의 컴퓨터를 통신 회선으로 연결된 망
- + 홈 네트워크 : 컴퓨터가 방마다 있고, 이들 컴퓨터를 유/무선 등의 통신 회선으로 연결
- + 지역 네트워크 ( LAN : Local Area Network ) : 회사, 건물, 특정 영역에 존재하는 컴퓨터를 통신회선으로 연결
- + 인터넷 ( 광역 네트워크 : WAN ( Wild Area Network ) ) : 지역 네트워크를 통신 회선으로 연결한 것
5. 서버와 클라이언트
- 서버와 클라이언트는 모두 프로그램을 의미하지만, 서로의 역할이 다르다.
- 클라이언트 : 처리 요청을 보내는 프로그램 ( 서비스를 받는 프로그램 )
- 네트워크 데이터를 필요로 하는 모든 애플리케이션이 클라이언트에 해당된다.
- 서버 : 클라이언트의 요청을 처리하고, 그 처리결과를 다시 요청을 보낸 클라이언트에게 보내주는 프로그램 ( 서비스를 제공하는 프로그램 )
- 순서 :
- 클라이언트의 연결요청 - > 서버가 연결을 수락 -> 클라이언트가 처리 요청을 보냄 - > 서버가 처리요청을 처리 - > 서버가 처리결과(응답)를 클라이언트로 보냄
- 종류 :
- (1) 클라이언트( 고객 ) – 서버 ( 서비스를 제공하는 자 ) 모델 : C / S 모델
- (2) P2P ( Peer – To – Peer ) : ex. Torrent
6. IP주소와 포트 ( port )
- IP주소 ( Internet Protocol ) :
- 네트워크 상에서 컴퓨터를 식별하는 번호로 네트워크 어뎁터(Lan 카드)마다 할당
- IP주소 확인 방법 : 명령 프롬프트를 사용 - > c:\>ipconfig /all ( xxx.xxx.xxx.xxx 형식으로 표현된다. )
- 포트 ( Port : 항구 ) :
- 같은 컴퓨터 내에서 프로그램을 식별하는 번호
- 클라이언트는 서버 연결 요청 시 IP주소와 Port번호를 같이 제공한다.
- 0 – 65535 범위의 값을 가진다.
- 명령 프롬프트에 nc -L -p 포트번호 작성하여 활용할 수도 있다.
- telnet 서버 IP주소 서버포트번호
- 포트의 범위 :
- 1 ) Well Know Port Numbers
- 2 ) Registered Port Numbers
- 3 ) Dynamic Or Private Port Numbers
01. 객체 입출력 보조 스트림 - 직렬화
package objectinput_objectoutput_stream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class ObjectInputOutputStreamExample {
public static void main (String[] args) throws Exception { // 직렬화 (***)
// ===============================================================================================
// 1. 지정된 파일에 객체를 저장하기 위한 바이트 기반의 파일출력 스트림 생성
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
// ===============================================================================================
// 2. 파일출력스트림에 객체출력보조스트림 연결 ( 생성자 매개변수로 )
ObjectOutputStream oos = new ObjectOutputStream(fos);
// ===============================================================================================
// 3. 다양한 참조차입의 객체를 출력 ( 이때, 내부적으로 객체의 " 직렬화 "가 발생 ) (***)
// 조건 : 출력할 객체를 찍어낸 클래스는 기본적으로 Serializable 태그 인터페이스를 반드시 implements 해야 한다!!(****)
oos.writeObject(new Integer(10)); // Integer 객체 저장
oos.writeObject(3.14); // double 객체 저장
oos.writeObject(new int [] { 1,2,3}); // 배열 객체를 저장
oos.writeObject(new String ("홍길동")); // 문자열 객체를 저장
// Wrapper 타입, String, 배열은 모두 Serializable 태그 인터페이스를 implements하고 있다.(***)
// 이를 통해서 직렬화 및 역직렬화가 가능하다.
// ===============================================================================================
ClassC cobj = new ClassC();
oos.writeObject(cobj);
// Serializable를 implements 할 때에는 오류가 발생하지 않지만, Serializable를 지워 버리면 예외가 발생한다.
// ===============================================================================================
// 4. 출력스트림은 기본 출력버퍼를 가지고 있으니, 강제 flush 수행!!
oos.flush();
// ===============================================================================================
// 5. 자원해제 : 보조스트림부터 먼저 닫고, 다음으로 기반 스트림을 닫는다!! ( 순서 )
oos.close();
fos.close();
} // main
} // end class
02. 객체 입출력 보조 스트림 - 역직렬화
package objectinput_objectoutput_stream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.Arrays;
public class ObjectInputOutputStreamExample2 {
public static void main (String[] args) throws Exception { // 역직렬화 (***)
// ===============================================================================================
// 1. 여러 참조 타입 객체를 저장한 파일에 대한 바이트 기반의 파일입력스트림 생성
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
// ===============================================================================================
// 2. 객체 입력보조스트림 생성 ( by the argument of the constructor )
ObjectInputStream ois = new ObjectInputStream(fis);
// ===============================================================================================
// 3. 객체입력보고스트림의 readObject() 메소드로,
// 파일에 쓴 순서대로 다시 객체를 읽어 들인다.
// 이때 내부적으로 파일에 저장된 객체의 " 역직렬화 " 수행한다.
// 조건 : 객체를 읽어들이는 JVM메소드 영역에, 해당 참조타입의 클래스(clazz 객체)가 있어야 하고,
// 파일에 객체를 저장할 때 사용된 클래스와
// 현재 파일로부터 객체를 읽어들일 때 사용하는 클래스( 즉, 양쪽 시점의 클래스가 )가 같아야 한다.
Integer obj1 = (Integer) ois.readObject();
Double obj2 = (Double) ois.readObject();
int[] obj3 = (int [] ) ois.readObject();
String obj4 = (String) ois.readObject();
// ===============================================================================================
// 4. 자원 해제 : 보조스트림을 먼저 닫고, 그 후에 기본 스트림을 닫는다.
ois.close();
fis.close();
// ===============================================================================================
// 5. 제대로 역직렬화를 통해, 파일로부터 객체가 잘 복원되었는지 확인하기 위해 출력한다.
System.out.println(obj1);
System.out.println(obj2);
System.out.println(Arrays.toString(obj3));
System.out.println(obj4);
// ===============================================================================================
// 출력 :
// 10
// 3.14
// [1, 2, 3]
// 홍길동
} // main
} // end class
03. Serializable의 효과
package objectinput_objectoutput_stream;
import java.io.Serializable;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class ClassC implements Serializable { // Serializable의 효과 / serialVersionUID 생성 (***)
// 직렬화가 필요있는 클래스를 선언할 때에는 자바컴파일러에게 자동추가를 맡기지 않고,
// 개발자가 직접 필드와 초기값을 지정해야지 유연성을 이용할 수 있다. (***)
private static final long serialVersionUID = 7777777L; // 디폴트 값으로 serialVersionUID을 생성했을 때
// private static final long serialVersionUID = ???
// 컴파일할 때마다, 무작위 정수값으로 serialVersionUID를 생성한다.
// serialVersionUID을 넣어 주지 않으면 컴파일할 때마다 계속 serialVersionUID가 바뀌게 된다.
int field1;
// 객체의 직렬화 이후에, 새로운 필드 추가 -> 컴파일 -> serialVersionUID 변경 ( 무작위 )
int field2;
// serialVersionUID 변경되자, java.io.InvalidClassException 예외가 발생하게 된다.
// 그렇기 때문에 serialVersionUID를 직접 지정해야 한다. (***)
} // end class
package objectinput_objectoutput_stream;
import java.io.Serializable;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class ClassB implements Serializable {
int field1;
} // end class
package objectinput_objectoutput_stream;
import java.io.Serializable;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@NoArgsConstructor
public class ClassA implements Serializable { // 직렬화에서 제외되는 존재 / serialVersionUID의 필요성
int field1;
// 집합 ( 부품 )관계 필드
// 이 클래스 역시 Serializable 한다.
ClassB field2 = new ClassB();
// 정적필드도 직렬화에 포함되지 않는다. (***)
static int field3 = 1000;
// 직렬화에서 제외할 필드는 transient 키워드를 붙이면 된다. (****)
transient int field4;
} // end class
04. 역직렬화가 불가능한 존재
package objectinput_objectoutput_stream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class Serializable_Writer {
public static void main (String [] args) throws Exception {
// 1. 파일에 댜한 출력 스트림 생성
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
// ===================================================================
// 2. 객체를 파일에 출력하기 위한, 객체 출력 보조 스트림 생성
ObjectOutputStream oos = new ObjectOutputStream(fos);
// ===================================================================
// 3. 직렬화할 객체를 생성학고, 필드값을 초기화
ClassA classA = new ClassA();
classA.field1 = 1;
classA.field2.field1 = 2;
// ===================================================================
// 직렬화에서 제외된다. ( 직렬화된 값에 저장되어 있지 않다. )
classA.field3 = 3; // 정적멤버는 정적 멤버답게 사용하는 것이 좋다.
classA.field4 = 4;
// 위의 두 필드의 값이 초기값인 1으로 출력된다. Why?
System.out.println(classA);
// 출력 : ClassA(field1=1, field2=objectinput_objectoutput_stream.ClassB@27716f4, field4=4)
// 이를 통해서 정적 필드인 field3은 출력이 안되고 있음을 알 수 있는데, 이를 통해 다른 곳에 저장되어있음을 알 수 있다.
// 그렇기에 이 파일에서 정적필드의 값을 바꿔도 역직렬화에 영향을 주지 않는다. (****)
// 즉, 정적멤버는 정적멤버로 사용하면 된다.
// ===================================================================
// 4. 파일에 객체를 출력 ( 직렬화 발생 )
oos.writeObject(classA);
// ===================================================================
// 5. 자원 정리
oos.flush();
oos.close();
fos.close();
// ===================================================================
} // main
} // end class
public class Serializable_Reader {
public static void main (String [] args) throws Exception {
System.out.println(">>>> ClassA.field3 : " + ClassA.field3);
// static 필드이기에 역직렬화와는 전혀 상관 없이 값이 출력된다.
// 출력 : >>>> ClassA.field3 : 1000
// 1. 객체가 저장된 파일에 대한 입력스트림 생성
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
// 2. 객체입력 보조스트림 생성
ObjectInputStream ois = new ObjectInputStream(fis);
// 3. 보조스트림을 통해, 파일에 저장된 객체의 복원 ( 이때, " 역직렬화 "가 수행된다. )
ClassA v = (ClassA) ois.readObject();
// 4. 과연 복원된 객체의 다양한 유형의
System.out.println("field1 : " + v.field1);
System.out.println("field2.filed1 : " + v.field2.field1);
// 정적멤버 / transient 필드는 직렬화 / 역직렬화가 불가능하기에 값 복원이 불가능 하다.
System.out.println("field3 : " + v.field3); // 정적 필드 - 정적멤버답게 해줘야 한다.
System.out.println("filed4 : " + v.field4); // transient 필드
// 출력 :
// >>>> ClassA.field3 : 1000
// field1 : 1
// field2.filed1 : 2
// field3 : 1000
// filed4 : 0
} // main
} // end class
05. SerialVersionUID
package objectinput_objectoutput_stream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerialVersionUIDExample {
public static void main ( String [] args ) throws Exception {
// 1. 파일에 출력할 출력 스트림 생성
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
// 2. 객체 출력 보조스트림 생성
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 3. 직렬화할 객체 생성 ( Serializable 함 )
ClassC classC = new ClassC();
classC.field1 = 1;
// 4. 객체 출력
oos.writeObject(classC);
// 5. 자원 해제
oos.flush();
oos.close();
fos.close();
} // main
} // end class
package objectinput_objectoutput_stream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class SerialVersionUIDExample2 {
public static void main ( String [] args ) throws Exception {
// 1. 파일에 출력할 출력 스트림 생성
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
// 2. 입력 스트림에 객체 입력 보조스트림 연결
ObjectInputStream ois = new ObjectInputStream(fis);
// 3. 객체 복원 ( 역직렬화 수행 )
ClassC classC = (ClassC) ois.readObject();
System.out.println("field1 : " + classC.field1); // 출력 : field1 : 1
System.out.println("field2 : " + classC.field2); // 출력 : field2 : 0
// 이를 통해서 직렬화 이후에 새롭게 작성한 필드도 serialVersionUID를 지정해주자 사용이 가능해졌다. (***)
// 직렬화 이후에 있던 필드를 삭제하게 된다면, 삭제한 필드는 사용할 수 없고 그 외의 필드는 사용할 수 있다.
// 그렇지만 serialVersionUID는 같기 때문에 예외가 발생하지는 않는다.
} // main
} // end class
06. 부모 자식관계에서의 Serializable
package objectinput_objectoutput_stream;
public class Parent { // Serializable 하지 않는다
public String field1; // Serializable하지 않기에, 직렬화에서 제외된다.
} // end class
package objectinput_objectoutput_stream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Child extends Parent implements Serializable { // 부모 : Serialzable X , 자식 : Serializable O
// 상속과 객체의 (역)직렬화의 관계 : (****)
// (1) 만약 부모 클래스가 Serializable 하면, 자식 클래스가 Not - Serializable 해도 직렬화 및 역직렬화가 가능하다.
// (1) 만약 부모 클래스가 Serializable하지 않고, 자식 클래스만 Serializable하면 직렬화 및 역직렬화가 불가능하다!
public String field2;
// 객체의 직렬화 수행 시, 아래의 메소드를 재정의하면 이 메소드가 대신 호출되게 된다. (***)
private void writeObject(ObjectOutputStream out) throws IOException{
System.out.println("Child::writeObject(out) invoked.");
out.writeUTF(field1); // 부모로부터 상속 받은 필드의 값을 직접 출력시킨다. ( 직렬하는 것이 아니라, 단순히 출력하는 것 )
out.defaultWriteObject(); // 직렬화를 시켜준다. (****)
} // writeObject
// 객체의 역직렬화 수행 시, 아래의 메소드를 재정의하면 이 메소드가 대신 호출되게 된다. (***)
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println("Child::readObject(in) invoked.");
field1 = in.readUTF(); // 부모로부터 상속받은 field1를 읽어 들인다는 의미이다. ( 역직렬하는 것이 아니라, 출력된 것을 읽는 것 )
in.defaultReadObject(); // 역직렬화를 해준다. (****)
} // readObject
// 메소드 writeObject와 readObject는 오버 라이딩한 것이 아니다!!!
} // end class
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 28일차 - 네트워킹 (0) | 2022.04.06 |
---|---|
KH - 2차 시험 + 국비 1개월차 후기 (0) | 2022.04.05 |
KH 26일차 - Scanner / File 클래스 (0) | 2022.04.04 |
KH 25일차 - 콘솔 (0) | 2022.04.01 |
KH 24일차 - 자바 입출력 ( Reader / Writer ) (0) | 2022.03.31 |