본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석3-5 캡슐화(encapsulation)와 다형성(polymorphism)

by 이쟝 2022. 1. 5.

캡슐화(encapsulation)

접근 제어자를 사용하는 이유

1) 외부로부터 클래스 내부에 선언된 데이터를 보호하기 위해서

2) 데이터 감추기(data hiding): 데이터가 유효한 값을 유지하도록, 비밀번호 같은 데이터를 외부에서 함부로 변경하지 못하도록 하기 위해서 외부로부터 접근을 제한하는 것 

이러한 데이터 감추기 -> 객체지향개념의 캡슐화!!

 

대입연산자를 사용하는 외부 접근을 막고 메서드를 통해서 iv에 간접 접근할 수 있게 함

접근제어자의 범위는 좁으면 좁을수록 좋음(class 내부에서만 쓰는 메서드를 굳이 public으로 필요 없음)

 

예제) 매개변수에 입력할 값에 제한을 걸어야 하는 경우(getter와 setter)

 

private 자료형 변수명;  // private 변수 선언
public getter {
	return 변수명;  // 변수의 값 반환
}
public void setter (자료형 변수명); 
	if(변수의 조건) return;          // 조건에 맞는 변수만 반환
    this.변수명 = 변수명;            // 조건에 맞는 변수만 멤버변수의 값으로 변경

 

더보기

get으로 시작하는 메서드는 단순히 멤버변수의 값을 반환하는 일을 한다.

set으로 시작하는 메서드는 매개변수에 지정된 값을 검사해 조건에 맞는 값 일 때 멤버변수의 값을 변경하도록 작성되어 있다. 

-> 보통 멤버변수의 값을 읽는 메서드의 이름 'get멤버변수이름' / 멤버변수의 값을 변경하는 메서드의 이름 'set멤버변수이름'

-> get으로 시작하는 메서드 'getter', set으로 시작하는 메서드 'setter'

 

예제) getterSetter 예문 

 

public class Exercise {
	public static void main(String[] args) {
		Armor suit1 = new Armor();
		Armor suit2 = new Armor("mark6", 180);
		
		System.out.println(suit1.getName() + ":" + suit1.getHeight()); 
		System.out.println(suit2.getName() + ":" + suit2.getHeight());
	}
}
class Armor {
	private String name ; 
	private int height; 
	
	Armor() { // 생성자
		this.name = "mark0";
		this.height = 200;
	} 
	Armor(String name, int height) { //생성자 오버로딩
		setName(name);
		setHeight(height);
	}

	public void setName(String n) {
		this.name = n;
	}
	public String getName() {
		return name;
	}
	public void setHeight(int h) {
		this.height = h;
	}
	public int getHeight() {
		return height;
	}
}

 

예제) 시간

 

public class TimeClass {

	public static void main(String[] args) {
		Time2 t = new Time2(11, 23, 49);
		t.setHour(t.getHour()+1);  // 현재 시간보다 1시간 후로 변경한다. 
		System.out.printf("%d:%d:%d\n",t.getHour(),t.getMinute(),t.getSecond());
		System.out.println(t); // t -> t.toSting()과 같다.
	}
}

class Time2 {
	private int hour, minute, second;
	
	Time2(int hour, int minute, int second) {
		setHour(hour);       // 생성자에 setHour 불러오기!
		setMinute(minute);
		setSecond(second);
	}
	public int getHour() {
		return hour;
	}
	public void setHour(int hour) {  // 여기서 void인데 return이 나오는 이유는 return이 break;역할
		if (hour < 0 || hour > 23) return; // 0부터 23 사이의 숫자가 아니면 그냥 return(값 0)
			this.hour = hour;  // 0 ~ 23 사이의 숫자이면 hour 출력
	}
	
	public int getMinute() {
		return minute;
	}
	public void setMinute(int minute) {
		if (minute < 0 || minute > 59) return;
			this.minute = minute;
	}

	public int getSecond() {
		return second;
	}
	public void setSecond(int second) {
		if (second < 0 || second > 59) return;
			this.second = second;
	}
	public String toString() {
		return hour + ":" + minute + ":" + second;
	}
}

 

더보기
더보기

<결과값>

12:23:49
12:23:49

 

- Time클래스의 모든 멤버변수의 접근 제어자를 private으로 하고, 이 들을 다루기 위한 public 메서드를 추가했다.(set 메서드) 그래서 t.hour = 13;과 같이 멤버변수로의 직접적인 접근은 허가 되지 않음!

 

예제) 위의 if문을 isNotValidHour 메서드로 만들기!

 

class Time {
	private int hour;  // 0 ~ 23사이의 값을 가져야함
	
	public void setHour(int hour) {
		if(isNotValidHour(hour)) return; 
        this.hour = hour;
	}
	// 매개변수로 넘겨진 hour가 유효한지 확인해서 알려주는 메서드
        // 이 메서드는 Time 클래스 안에서만 쓰기 때문에 private으로!
	private boolean isNotValidHour(int hour) {
		return hour < 0 || hour > 23;
	}
	
	public int getHour() { return hour; }
}

다형성(polymorphism)

- 조상클래스 타입의 참조변수자손클래스의 인스턴스를 다루는 것

-> 이때까지는 Tv인스턴스를 다루기 위해서는 Tv타입의 참조변수를 사용했지만 Tv의 자손클래스인 SmartTv가 생기면서 Tv인 조상클래스 타입의 참조변수로 자손클래스 SmartTv의 인스턴스를 다룰 수 있다! => 다형성

-> 참조변수의 타입은 반드시 인스턴스 타입과 일치하지 않을 수도 있다.

-> 둘 다 같은 타입의 인스턴스라도 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다!(사진 참고)

 

 

SmartTv s = new SmartTv( );  // 참조 변수와 인스턴스의 타입이 일치
Tv      t = new SmartTv( );  // 조상 타입 참조변수로 자손 타입 인스턴스 참조

- 자손 타입의 참조변수로 조상 타입의 객체를 생성할 수 없다.
Tv      t = new SmartTv( );  // Ok, 허용
SmartTv s = new Tv( );       // 에러, 허용 안됨

 

SmartTv의 참조변수인 s로 사용할 수 있는 값은 7개, Tv의 참조변수인 t로 사용할 수 있는 값은 5개

 

더보기
더보기

실제 인스턴스 안에 들어있는 값보다 참조변수가 가리키는 값이 많으면 안 된다!!

-> 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다!

-> 가리키는 값이 5개(참조변수) -> 실제 객체에 존재하는 값 7OK

-> 가리키는 값이 7개(참조변수) -> 실제 객체에 존재하는 값 5개 에러!)


참조변수의 형변환(1)

- 형 변환을 하는 이유: 참조변수(리모컨)을 변경함으로써 사용할 수 있는 멤버의 개수를 조절하기 위해서

- 객체는 그대로 있고 타입만 일치시킴! (객체가 줄어들거나 하지는 않음)

- 조상 자손 관계의 참조변수만 서로 형변환 가능 ( 조상-자손은 되지만 / 자손 - 자손은 안됨 )  

 

자손 타입 -> 조상타입(Up-casting) : 형변환 생략가능

조상 타입 <- 자손타입(Down-cating) : 형변환 생략불가

 

- 기본형 변수의 형변화에서 작은 자료형 -> 큰 자료형의 형변환은 생략 가능하듯이 참조형 변수의 형변환도 동일!

-> 생략 가능한 이유(자손 타입의 참조변수를 조상타입의 참조변수로 형변환 하는 것은 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하기 때문에 생략!) ->> 조상클래스의 멤버개수가 자손클래스의 멤버개수보다 작거나 같다 항상!!!

 

예제) 조상 클래스인 Car와 자손 클래스인 FireEngine과 Ambulance

 

class Car {
	String color; 
	int door;

	void drive() { // 운전하는 기능
		System.out.println("drive");
	}
	void stop() {  // 멈추는 기능
		System.out.println("stop");
	}
}

class FireEngine extends Car {  //소방차
	void water() {  // 물을 뿌리는 기능
		System.out.println("water!");
	}
}

class Ambulance extends Car {  //구급차
	void siren() {  // 사이렌 기능
		System.out.println("siren");
	}
}

FireEngine f = new FireEngine(); 
Car c = (Car)f     // Ok. 조상인 Car타입으로 형변환(생략가능)
FireEngine f2 = (FireEngine)c;   // Ok. 자손인 FireEngine타입으로 형변환(생략불가)
Ambulance a = (Ambulance)f;      // 에러. 상속관계가 아닌 클래스 간의 형변환 불가

 

'

- 참조변수의 fFireEngine의 객체(color, door, drive(), stop(), water()를 다 출력할 수 있지만, 4개(Car의 멤버변수와 메서드인 color, door, drive( ), stop( ))만 쓰고 싶다면 참조변수의 형변환을 사용해서 변환!

 

- 참조변수 c로 FireEngine 객체를 가리키고 있지만 FireEngine의 객체를 다 가리킬 수는 없고 FireEngine이 상속된 멤버만(color, door, drive, stop)만 사용할 수 있음 ex) Car타입의 참조변수로는 FireEngine의 메서드인 water를 호출할 수 없음

 

참조변수의 형변환(2)

 

 

5(FireEngine) -> 4(Car) (감소) / 4(Car) -> 5(FireEngine) (증가)

-> 형변환 감소는 항상 안전하지만 증가는 불완전함!

 

Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
		
fe.water();   // water!
car = fe;  // car = (Car)fe; 에서 형변환이 생략
//car.water();  에러!
fe2 = (FireEngine)car;   // 자손타입 <- 조상타입. 형변환 생략 불가
fe2.water();  // water!

 

-> 형 변환할 때 실제 인스턴스가 무엇인지가 중요하다. 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인!

 

Car car = null;
FireEngine fe2 = null;
		
FireEngine fe2 = (FireEngine)car; // 조상 -> 자손으로 형변환
Car car2 = (Car)fe2;               // 자손 -> 조상으로 형변환
car2.drive();               // NullPointerException 밠생
-> 객체를 생성하지 않아서 에러!(car2 객체 xx)

 

Car c = new Car();
FireEngine fe = (FireEngine)c;  // 형변환 실행시 에러 java.lang.ClassCastException
fe.water();   // 컴파일 오케이

 

-> c가 만든 객체에는 (color, door, drive, stop)가 있는데 water를 불러오니까 에러!

-> 참조변수 car가 참조하고 있는 인스턴스가 car타입의 인스턴스이기 때문에! Car c = new FireEngine( ); 로 바꾸면 해결된다! Car보다 FireEngine이 멤버변수가 더 많기 때문에 에러!