Java - 기본기

21. 접근제어자와 상속, 오버라이드

TerianP 2022. 3. 1.
728x90

1. 접근 제어자

- 접근 제어자는 Java 에서 변수나 메소드의 사용 권한을 설정하기 위해 사용하는 방법이다.

- 접근 제어자는 변수나 메서드 앞에 올 수 있으며 아래 표에 있는 4가지를 사용할 수 있다.

  • 단, 클래스의 경우 public 과 default 만 올 수 있다.
  • 단, 지역 변수의 경우 어떤 것도 올 수 없다. 이는 메소드나 반복문 등 특정 모듈안에서만 사용되고 사라지는 지역 변수의 특성상 굳이 올 필요가 없기 때문이라고 할 수 있다.

- 위에서 아래로 pivate -> defalult -> protected -> public 순으로 많은 접근을 허용한다.

- 이러한 접근 제어자를 통해서 데이터를 감추고 보고하는 것을 객체지향개념에서 캡슐화(encapsulation)라고 한다.

접근제어자 종류 내용
private 같은 클래스에서만 접근 가능 - 외부에서는 getter , setter 사용해서 접근
defalult 같은 패키지에서만 접근 가능 - 접근 제어자를 생략하는 경우 기본은 DEFAULT
protected 같은 패키지 & 상속관계에 있는 곳에서만 접근 가능
public 어디에서나 접근 가능

2. 접근제어자에 따른 접근 방법 - SETTER & GETTER

1) Private

- 사실 접근자에서 가장 중요한 것은 바로 private 가 아닐까 생각한다.

- Spring 을 공부하거나 다른 곳을 찾아보면서 보았을 때 가장 많이 중요한 곳에 사용하는 것은 바로 private 였다고 생각한다.

- private 가 public 와 엑세스 범위가 다르다는 점도 있지만 둘의 가장 큰 차이점은 바로 외부에서 private 접근제어자가 달린 변수에 접근하기 위해서 GETTER 와 SETTER 메서드를 사용한다는 점일 것이다.

- GETTER 와 SETTER 메서드는 PRIVATE 으로 정해진 변수에 외부에서 값을 할당하거나 외부에서 해당 값을 가져오기 위해 사용된다 => 기본형은 아래와 같다.

// 변수값 지정
public void set변수명(자료형 변수명){ 
		this.필드변수명 = 매개변수명;
}

// 변수값 호출
public 자료형 get변수명(){
	return 필드변수명; // 혹은 this.필드변수명 을 사용해도 무방하다
}

 

- 아래의 ATM 클래스가 있다. 해당 클래스에는 현재 잔액 balance 가 선언되어 있고, private 으로 접근제어자를 지정해주었다. 이후 외부에서 balance 에 값을 넣을때 어떻게 나오는지 확인해보겠다.

- 보면 아래 사진과 같이 'THE FIELD ATM.BALANCE IS NOT VISIBLE' 라는 에러를 내보내고 있다. 즉 balance 라는 필드 변수를 확인 할 수 없다는 의미이다.

- 이는 private 으로 감춰진 변수는 외부에서 일반적으로 대입하는 방식으로는 값을 넣을 수 없다는 의미라고 할 수 있다.

public class ATM {
	
	private int balance;
	String name;

	void deposit(int a) {
		balance +=a;
		System.out.println(a+" 원 입금 완료");
	}
}

### 외부 클래스 ###

    public static void main(String args[]) {
		ATM atm = new ATM();
		atm.balance = 50; // private 으로 설정된 변수에 바로 값을 넣을 때
    }

에러 발생!

 

- 다음으로 balance 변수에 getter 와 setter 메서드를 만들고 해당 메서드를 통해 값을 삽입해보겠다.

- 먼저 setter 를 사용해서 매개변수에 값을 넣고, getter 를 이용해서 필드변수의 값을 출력해보았다.

- 이렇게 하니 이전과는 다르게 정상적으로 동작하는 것을 알 수 있다.

public class ATM {
	
	private int balance;
	String name;

	void deposit(int a) {
		balance +=a;
		System.out.println(a+" 원 입금 완료");
    }
        
    // getter, setter 메서드
	public int getBalance() {
		// private 대상 변수를 외부에서 접근하기 위해서 사용
        // balance 즉 필드변수를 리턴함
		return balance;
	}
	
	public void setBalance(int balance) { 
		// private 대상 변수를 외부에서 접근하기 위해서 사용
        // 해당 매개 변수로 들어오는 값이 this.balance 즉, 필드변수에 대입됨
		this.balance = balance;
	}
}

### 외부 클래스 ###

    public static void main(String args[]) {
		ATM atm = new ATM();
		atm.setBalance(5000);
		System.out.println("balance : "+atm.getBalance());
    }

정상 출력!


3. 상속 Extends

  • 부모 클래스의 속성과 메서드를 보다 확장하여 자식 클래스에서 사용하기 위한 방법
    • [자식 클래스명] extends [부모 클래스명]
  • 상속받은 자식 클래스에서 부모 클래스의 생성자와 부모 잠조변수 호출
    • 부모 클래스 생성자 호출 : super()
    • 부모 클래스를 가리키는 참조변수 : super.
    • 자식 클래스에서 사용되는 super() 는 따로 정의해놓지 않더라도 마치 기본생성자처럼 인스턴스 생성 시 항상 실행
    • 다중 상속도 가능!! ⇒ person 을 상속받는 superman을 상속받는 hero 가 있다면 Hero는 person 과 superman 의 멤버 변수와 메서드 모두 사용 가능하다
// 자식 클래스 superMan에서 부모 클래스 Person 으로부터 메서드와 속성을 상속받아서 사용함
// 1. 부모클래스
public class Person{
			public Person(){
				name = "홍길동";
				age = 20;
				System.out.println("Person 클래스의 기본 생성자");
		}
	
	void eating(String str) {
		System.out.println(str+"을/를 냠냠");
}

// 2. 자식 클래스
public class SuperMan extends Person {
	
SuperMan(){
		super(); // 이 부분은 부모 클래스의 기본 생성자를 부르는 생성자로 생략 하더라도 무조건 항상 실행됨!!!!
		System.out.println("SuperMan 클래스의 기본 생성자");
	}
	
	public void razer() {
		System.out.println("레이저 발싸!");
	}
	
	public void fly() {
		System.out.println("날아라!");
	}

}

## 메인 메서드 ##
SuperMan sm = new SuperMan();
		
sm.eating("사과");
System.out.println(sm.age);
		
sm.fly();
sm.razer();

## 출력 결과 ##
Person 클래스의 기본 생성자
SuperMan 클래스의 기본 생성자
사과을/를 냠냠
20
날아라!
레이저 발싸!

 

2) 상속과 클래스 형변환

  • 자바에서의 상속은 매우 중요하다. 특히나 상속은 자바에서의 클래스 형변환과도 관련이 있다.
  • 상속에서 중요한 점 중 하나는 상속한 부모 클래스의 참조변수에 자식 클래스의 참조값을 담을 수 있다는 것이다. 반면 자식 클래스의 잠조 변수에는 부모 클래스의 참조값을 담을 수는 없다.
    • 이는 큰 것에는 작은 것을 담을 수 있으나, 작은 것에는 큰 것을 담을 수 없다는 것을 의미한다.
    • 이전의 type 별 형변환도 비슷한 맥락이다 => float 에 int 를 담을 수 있으나, int 에 float 를 담으려면 반드시 형변환이 필요하다.
int a = 10;
float b = a; // 묵시적 형변환 => 큰 것에 작은 것을 담는 것은 따로 형변환 필요없음
		
float c = 20;
int d = (int)c; // 명시적 형변환 => 작은 것에 큰 것을 담을때는 반드시 명시적으로 표시해야함

 

  • 이를 확인하기 위해서 StarCraft 클래스와 Tank 클래스를 보자
  • StarCraft 클래스
public class StarCraft {
	String name;
	String weapon;
	int x, y; // 위치
	int hp, at_point, de_point; // hp, 공격력, 방어력
	int mv_spd, at_spd; // 이동속도, 공격속도
	
	
	// 정보 출력을 위한 메소드
    // 메소드에서는 StarCfaft 객체를 매개변수로 갖는다.
	public void status(StarCraft unit) {
		System.out.println(unit+" 의 현재 정보를 출력합니다");
		System.out.println("이름 : "+unit.name);
		System.out.println("hp : "+unit.hp);
		System.out.println("공격력 : "+unit.at_point);
		System.out.println("방어력 : "+unit.de_point);
		System.out.println("이동 속도 : "+unit.mv_spd);
		System.out.println("공격 속도 : "+unit.at_spd);
	}
}
  • Tank 클래스
public class Tank extends StarCraft{
	
	boolean mod; // 시즈모드는 boolean 을 사용하여 true면 공성모드 false면 일반모드
	
	Tank(){ // 기본생정자를 통해서 Tank 인스턴스 생성시 변수 값 초기화
		System.out.println("시즈 탱크 등장!");
		this.name = "시즈 탱크";
		this.weapon = "아크라이트 포 && 아크라이트 중격포";
		this.x = 50;
		this.y = 50;
		this.hp = 150;
		this.at_point = 40;
		this.de_point = 1;
		this.mv_spd = 2;
		this.at_spd = 50;
		boolean mod = false;

	}
 }

 

  • 이제 아래의 Game 클래스에서 상속에 따른 형변환을 확인해보자
    • 부모 클래스인 StarCraft 의 참조변수인 star 안에는 tank 를 담을 수 있다. 
    • 반대로 자식 클래스의 tank 에는 부모 클래스의 참조변수인 StarCraft 를 담을 수 없다
    • 이 때문에 자식 클래스안에 부모클래스를 담는 경우 명시적으로 형변환을 해줘야 한다.
Tank tank = new Tank();
StarCraft star = new StarCraft();
// 부모클래스의 잠조변수인 star 에는 자식클래스의 인스턴스를 담을 수 있다
star = tank; 
// 아래처럼 묵시적 형변환이 된 것이다
star = (StarCraft)tank;
        
// 위의 내용을 최종적으로 아래처럼 사용 가능하다
// 이는 부모클래스의 참조변수에 자식클래스의 참조값을 담을 수 있기 때문에 가능하다  
StarCraft star2 = new Tank(); 

// 반대로 자식클래스에 부모 클래스를 담는 것은 불가능!!!
// tank = star;

// 때문에 명시적으로 작은 타입을 형변환해줘야 작은 클래스의 참조변수에 큰 값을 담을 수 있다
tank = (Tank)star

 

  • 부모 클래스에 자식 클래스를 담을 수 있기 때문에 아래 status 메소드가 사용 될 수 있다.
    • 부모 클래스 StarCraft 를 매개변수로 갖는 status 메소드가 있다.
    • 부모 클래스의 객체 안에 자식 클래스의 참조값을 담을 수 있다는 것을 활용하면 부모 클래스의 객체를 매개변수로 갖는 메소드의 매개변수안에 자식 클래스의 객체가 담겨도 괜찮다는 것이다!!
public class StarCraft {
	String name;
	String weapon;
	int x, y; // 위치
	int hp, at_point, de_point; // hp, 공격력, 방어력
	int mv_spd, at_spd; // 이동속도, 공격속도
	
	
	// 정보 출력을 위한 메소드
    // 메소드에서는 StarCfaft 객체를 매개변수로 갖는다.
	public void status(StarCraft unit) {
		System.out.println(unit+" 의 현재 정보를 출력합니다");
		System.out.println("이름 : "+unit.name);
		System.out.println("hp : "+unit.hp);
		System.out.println("공격력 : "+unit.at_point);
		System.out.println("방어력 : "+unit.de_point);
		System.out.println("이동 속도 : "+unit.mv_spd);
		System.out.println("공격 속도 : "+unit.at_spd);
	}
    
    	public static void main(String[] args) {
		
		Tank tank = new Tank();
		StarCraft star = new StarCraft();
		
        // 부모 클래스인 StarCraft 에는 StarCraft 를 담을 수 있다.
        // 즉, 매개변수가 StarCraft 인 경우 자식 클래스인 Tank 의 객체가 들어와도 괜찮다!!
		tank.status(tank); 
}

 

3) 오버라이딩 - 상속의 꽃

  • 개인적으로 상속의 꽃은 오버라이드가 아닐까하고 생각한다. 뭔가 이름은 어딘가 게임의 스킬에서 나올법하다
  • 오버라이드는 부모클래스의 메소드의 이름, 매개변수, 순서를 그대로 유지하되 메소드 내용 - method body - 만 다르게 재정의하는 것이다.
  • 즉 오버라이드 된 메소드는 이름, 매개변수, 순서는 동일하지만 메서드의 기능은 완전히 다르게 변할 수 있다. 
  • 오버라이드 된 메소드에는 @Override 어노테이션이 붙는다. 

 

  • 다시 한번 StarCraft 로 예시를 보겠다
    • 오버라이딩 예시를 위해 attack 메소드를 만들었다. 이때 StarCraft 는 실제로 객체가 생성되어서 StarCraft 로 뭔가를 할 일은 없으니 method body 는 빈 내용을 두었다.
public class StarCraft {
	String name;
	String weapon;
	int x, y; // 위치
	int hp, at_point, de_point; // hp, 공격력, 방어력
	int mv_spd, at_spd; // 이동속도, 공격속도
	
	
	// 정보 출력을 위한 메소드
    // 메소드에서는 StarCfaft 객체를 매개변수로 갖는다.
	public void status(StarCraft unit) {
		System.out.println(unit+" 의 현재 정보를 출력합니다");
		System.out.println("이름 : "+unit.name);
		System.out.println("hp : "+unit.hp);
		System.out.println("공격력 : "+unit.at_point);
		System.out.println("방어력 : "+unit.de_point);
		System.out.println("이동 속도 : "+unit.mv_spd);
		System.out.println("공격 속도 : "+unit.at_spd);
	}
	
	// 공격을 위한 메서드 정의.
	// StarCraft 클래스에는 사실 쓸 필요가 없음으로 내용은 -method body는- 빈 내용을 두었다.
	public void attack(StarCraft unit) {}; 
}

 

  • Tank 클래스
    • 아래의 attack 메소드를 보자. 분명 상위 클래스의 메소드와 메소드명, 매개변수 갯수, 타입 모두 일치한다.
    • 다만 메소드를 재정의 - overriding - 하였고, 이를 통해 해당 메소드의 기능이 완전히 달라지게 되었다. 
public class Tank extends StarCraft{
	
	boolean mod; // 시즈모드는 boolean 을 사용하여 true면 공성모드 false면 일반모드
	
	Tank(){ // 기본생정자를 통해서 Tank 인스턴스 생성시 변수 값 초기화
		System.out.println("시즈 탱크 등장!");
		this.name = "시즈 탱크";
		this.weapon = "아크라이트 포 && 아크라이트 중격포";
		this.x = 50;
		this.y = 50;
		this.hp = 150;
		this.at_point = 40;
		this.de_point = 1;
		this.mv_spd = 2;
		this.at_spd = 50;
		boolean mod = false;

	}
	
	@Override // 오버라이드 메소드는 @Override 어노테이션을 붙인다
    // 메소드 내용이 달라졌다...!! => 재정의
	public void attack(StarCraft unit) {
		System.out.println(unit+" 를 공격합니다!");
		unit.hp -=at_point; // unit 의 hp에서 전차의 공격력만큼 감소
		if(mod == true) {
			System.out.println("시즈 모드로 공격!! 일부는 펑펑펑");
		}else {
			System.out.println("일반 모드로 공격!! 일부는 퉁퉁퉁");
		}
	}

4. 메소드 오버로딩과 오버라이딩 : Overloading , Overriding

사실 오버로딩은 옆집 친구인 오버라이딩과 이름도 비슷하고 헷갈린다. 

이를 확실히 하기 위해 표로 정리해두었다.

overriding - 재정의 overloading - 다중정의 -
재정의 다중정의
method명 동일 method명 동일
매개변수 순서, 자료형, 갯수 모두 일치 매개변수 순서, 자료형, 갯수 다르게
상속받은 경우에만 가능! 상속과는 관련 없다
부모의 접근지정자보다 자식의 접근지정자가 더 허용적이여야만 오버라이딩 가능  

- 참고자료

Chapter 5-4. 객체 지향 : 클래스 형변환(is, as)

 

Chapter 5-4. 객체 지향 : 클래스 형변환(is, as)

인프런에 있는 Rookiss님의 강의 Part1: C# 기초 프로그래밍 입문 를 듣고 정리한 필기입니다. 😀

ansohxxn.github.io

 

[5/22] 오버라이드와 객체 형변환

 

[5/22] 오버라이드와 객체 형변환

1. 메소드 오버라이드 : 상속관계에서만 가능함 정의 : 부모클래스의 메소드를 자식 클래스에서 재선언하는것 사용방법 : 부모클래스의 메소드와 같은 형태의 머릿부를 동일하게 선언 기능 : 부

joodaram.tistory.com

 

댓글