Serializable 과 transient


(1) Serializable


데이터를 파일에 쓰거나, 네트워크를 타고 다른 곳에 전송할 때는 데이터를 바이트 단위로 분해하여 순차적으로 보내야 한다. 이것을 직렬화(Serialization)라고 한다.


기본 자료형(boolean, char, byte, short, int ,long, float, double)은 정해진 바이트의 변수이기 때문에 바이트 단위로 분해하여 전송한 후 다시 조립하는데 문제가 없다.


하지만 객체의 크기는 가변적이며, 객체를 구성하는 자료형들의 종류와 수에 따라 객체의 크기는 다양하게 바뀔 수 있다. 이런 객체를 직렬화 하기 위해서 Serializable 인터페이스를 구현하게 된다.


[JAVA] 객체 직렬화 ObjectInputStream / ObjectOutputStream


* 직렬화가 가능한 객체의 조건


① 기본형 타입(boolean, char, byte, short, int, long, float, double)은 직렬화가 가능

② Serializable 인터페이스를 구현한 객체여야 한다. (Vector 클래스는 Serializable 인터페이스구현)

③ 해당 객체의 멤버들 중에 Serializable 인터페이스가 구현되지 않은게 존재하면 안된다.

④ transient 가 사용된 멤버는 전송되지 않는다. (보안 변수 : null 전송)


객체 직렬화는 객체에 implements Serializable 만 선언해 주면 된다.


(2) transient


하지만, 객체의 데이터 중 일부의 데이터는(패스워드와 같은 보안) 여러가지 이유로 전송을 하고 싶지 않을 수 있다. 이러한 변수는 직렬화에서 제외해야 되며, 이를 위해서 변수에 transient를 선언한다.


또한, 직렬화 조건 중 객체의 멤버들 중에 Serializable 인터페이스 구현되지 않은 객체가 있으면, 직렬화 할 수 없다.(NonSerializableException) 직렬화 해야 되는 객체 안의 객체 중 Serializable 인터페이스가 구현되지 않으면서 전송하지 않아도 되는 객체 앞에는 transient 를 선언해준다. 그러면 직렬화 대상에서 제외되므로 해당 객체는 직렬화가 가능해진다.


* Serializable 과 transient 사용 예제


UserClass.java

 


import java.io.Serializable;

// 직렬화 한다.
public class UserClass implements Serializable{
	
	private static final long serialVersionUID = 4220461820168818967L;
	
	String name;
	
	// age 비 전송
	transient int age;
	
	// NonSerializable 클래스
	NonSerializableClass nonSerializable;
	
	
	public UserClass() {
		
	}
	
	public UserClass(String name, int age){
		
		this.name = name;
		this.age = age;
		
		this.nonSerializable = new NonSerializableClass(false);
		
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	

	public NonSerializableClass getNonSerializable() {
		return nonSerializable;
	}

	public void setNonSerializable(NonSerializableClass nonSerializable) {
		this.nonSerializable = nonSerializable;
	}

	@Override
	public String toString() {
		return "UserClass [name=" + name + ", age=" + age
				+ ", nonSerializable=" + nonSerializable + "]";
	}
	
}


NonSerializableClass 보기


ObjectStream.java 보기



- UserClass.java 의 변수를 보면 transient int age; 로 age 변수는 직렬화에서 제외했다.

- NonSerializableClass 객체는 Serializable 인터페이스를 구현하지 않은 클래스이다.

- 따라서 UserClass.java 로 직렬화를 시도하면, 위와 같이 NonSerializableClass Exception이 발생한다.


- 위의 문제를 해결하기 위해서는 NonSerializableClass.java 에 Serializable 인터페이스를 구현하여 직렬화를 할 수 있게 하는 방법과

- NonSerializableClass 를 전송하지 않아도 되면, 또는 않아야 한다면 transient 를 앞에 붙여주는 것이다.

- 그러면 NonSerializableClass 객체는 직렬화 대상에서 제외되면서 UserClass 가 정상적으로 직렬화되어 처리될 것이다.


* NonSerializableClass 객체 선언 앞에 transient 선언 결과


 

import java.io.Serializable;

// 직렬화 한다.
public class UserClass implements Serializable{
	
	private static final long serialVersionUID = 4220461820168818967L;
	
	String name;
	
	// age 비 전송
	transient int age;
	
	// NonSerializable 클래스
	transient NonSerializableClass nonSerializable;
	
	
	public UserClass() {
		
	}
	
	public UserClass(String name, int age){
		
		this.name = name;
		this.age = age;
		
		this.nonSerializable = new NonSerializableClass(false);
		
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	

	public NonSerializableClass getNonSerializable() {
		return nonSerializable;
	}

	public void setNonSerializable(NonSerializableClass nonSerializable) {
		this.nonSerializable = nonSerializable;
	}

	@Override
	public String toString() {
		return "UserClass [name=" + name + ", age=" + age
				+ ", nonSerializable=" + nonSerializable + "]";
	}
	
}



- 객체가 정상적으로 직렬화되어 전송되고, 가져와 출력되는 것을 볼 수 있다.

- 당연히 transient가 붙은 age 변수와 nonSerializable 은 직렬화 되지 않기에 데이터가 없다.




상속받은 CheckingAccount 클래스의 슈퍼클래스인 Account 클래스에 생성자가 있었다면 어떻게 될까?

예)


public class Account {
 String accountNo;
 String ownerName;
 int balance;
 
 Account(String accountNo, String ownerName, int balance)
 {
  this.accountNo = accountNo;
  this.ownerName = ownerName;
  this.balance = balance;
 }
 
 void deposit(int amount){
  balance += amount;
 }
 
 int withdraw(int amount) throws Exception{
  if (balance < amount)
   throw new Exception("잔액이 부족합니다.");
  balance -= amount;
  return amount;
 }
}



public class CheckingAccount extends Account {
 String cardNo;
 
 CheckingAccount (String accountNo, String ownerName, int balance, String cardNo) {
  this.accountNo = accountNo;
  this.ownerName = ownerName;
  this.balance = balance;
  this.cardNo = cardNo;
 }
 
 int pay(String cardNo, int amount) throws Exception {
  if(!cardNo.equals(this.cardNo) || (balance < amount))
  {
   throw new Exception("지불이 불가능합니다");
  }
  else
   return withdraw(amount);
 }
}




컴파일 에러 발생

Implicit super constructor Account() is undefined. Must explicitly invoke another constructor 라는 에러메시지가 뜬다.

위의 에러 메시지는 Account()라는 생성자를 찾을 수 없다고 말하고 있습니다. Account 클래스 no-arg constructor(파라미터를 받지 않는 생성자)를 말하는 거죠. 그리고 이런 컴파일 에러가 CheckingAccount.java의 3행 마지막 부분, 다시 말해서 CheckingAccount 클래스의 생성자 본체가 시작되는 부분에서 발생했다고 표시 하고 있습니다.
왜 이런 컴파일 에러가 발생한 걸까?

이런 컴파일 에러가 발생한 이유는 자바 컴파일러가 컴파일을 할 때 생성자의 첫 번째 명령문이 슈퍼클래스의 생성자 호출문이 아니면 자동으로 슈퍼클래스의 no-arg constructor 호출문을 그 위치에 추가하기 때문 입니다. 그런데 Account 클래스에는 no-arg constructor가 없었기 때문에 컴파일 에러가 발생한 것입니다.

이런 에러가 발생하지 않도록 하려면 두 가지 방법이 있습니다. 첫 번째 방법은 Account 클래스에 no-arg constructor를 추가하는 것입니다. 하지만 객체지향 프로그래밍에서는 슈퍼클래스의 소스 코드를 건드리지 않고 상속받는 것을 원칙으로 하기 때문에 이런 방법은 해결책이라고 할 수 없습니다.

또 다른 방법은 CheckingAccount 클래스의 생성자 안에 슈퍼 클래스의 Account 클래스의 생성자 호출문을 명시적으로 써넣는 것입니다. 자바 컴파일러는 생성자 안에 슈퍼클래스의 생성자 호출문이 있으면 no-arg constructor 호출문을 추가하지 않기 때문입니다. 이 방법은 Account 클래스의 소스 코드를 건드리지 않고도 할 수 있는 방법이기 때문에 올바르 해결책이라고 할 수 있습니다.

그러면 이제 슈퍼클래스의 생성자 호출문 작성 방법을 배워보자.

서브클래스의 생성자 안에서 슈퍼 클래스의 생성자를 호출할 때는 슈퍼클래스의 이름 대신 super 키워드를 써야 합니다.
예를 들면..

super(accountNo, ownerName, balance);


※ 슈퍼클래스의 생성자 호출문은 반드시 생성자의 첫 번째 명령문이어야 합니다.


출처 : 뇌를 자극하는 JAVA 프로그래밍

'프로그램 > Java' 카테고리의 다른 글

public 상속과 private 상속에 대해  (0) 2017.03.20
[JAVA] Serializable 과 transient  (0) 2017.02.24
자바/Java 자바의 다형성이란?  (0) 2017.02.06

자바/Java 자바의 다형성이란?




객체지향 개념에서의 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현 하였습니다.


이를 좀 더 구체적으로 말하자면, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것 입니다.


1) CarTest.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package arabiannight.tistory.com.java.test;
 
public class CarTest {
 
    public static void main(String[] args) {
 
        Car c = new FireEngine();
        Car c2 = new Ambulance();
 
        c.drive();
        c2.drive();
         
    }
}
 
class Car {
    String color;
    int door;
 
    void drive() { //운전하는 기능
        System.out.println("drive, brrrr~");
    }
 
    void stop() { // 멈추는 기능
        System.out.println("stop!!!");
    }
}
 
 
class FireEngine extends Car {  // 소방차
    void water() {              // 물 뿌리는 기능
        System.out.println("warter!!!");
    }
 
    @Override
    void drive() {
        super.drive();
        System.out.println("소방차는 급해요~!");
    }
}
 
class Ambulance extends Car {  // 앰뷸런스
    void siren() {             // 사이렌을 울리는 기능
        System.out.println("siren~~~~");
    }
}


실행결과 :

1
2
3
drive, brrrr~
소방차는 급해요~!
drive, brrrr~





<정리>

1) 조상의 참조변수로 자손의 인스턴스는 무조건 참조가 가능하다.

(조상의 참조변수의 멤버수가 자손의 인스턴스의 멤버수보다 적기 때문에)

(자손 인스턴스는 조상의 멤버를 모두 상속 받기 때문에 조상의 멤버보다 같거나 많을수 밖에 없다.)

2) 자손의 참조변수로 조상의 인스턴스는 무조건 참조가 불가능 하다.

(자손의 참조변수의 멤버수가 조상의 인스턴스의 멤버수보다 많기 때문에)






출처 : 자바의 정석

+ Recent posts