지난 포스팅에서 추상화에 대해 언급했다면 오늘은 추상화보다 더 심각하게 추상적인 인터페이스에 대해 정리한다.

 

인터페이스란 간단히 정리하자면 객체의 사용 방법을 정의한 타입이다. 또한 객체의 교환성을 높여주기 때문에

다형성에 매우 중요한 역활을 차지한다. 자바8 에서는 기능이 추가되어 람다식에 대한 기능까지 추가되며 중요성이

더욱 커지게 되었다.

 

※ 이번 정리에서 언급할 인터페이스 내용 중에서 디폴드 메소드와 람다식에 대해서는 아직 내용을 배우지 못하였다.

따라서 이번 인터페이스에서는 기본 개념만 언급하며 추후 익명 인터페이스 등 다양한 방면으로 여러번에 걸쳐 정리하고자 한다.

 

1. 인터페이스의 장점

1) 코드 수정이 필요없다.

인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역활을 한다. 개발 코드가 인터페이스의 메소드를 호출하면

인터페이스는 객체의 메소드를 호출시킨다. 이런 특징을 이용하면 개발 코드를 수정하지 않아도, 사용 중인 객체를

변경할 수 있게 된다.

 

2) 여러 객체에 대해 이식이 편리하다.

인터페이스는 여러 객체들과 사용이 가능하여 객체의 내용에 따라 실행 내용과 리턴값이 다르게 된다.

따라서 코드상에서는 코드의 수정 없이도 실행 내용과 리턴값을 다양하게 사용할 수 있다.

 

2. 인터페이스 선언 / 정의

인터페이스는 ~.java 의 형태로 작성되며 ~.class 형태로 컴파일되어 물리적으로는 클래스와 비슷하지만 선언하는

방법은 다르다.

[접근 지정자] interface 이름 { ... }

interface Fightable {
	public void attack(Unit u);
}
 

메소드를 살펴보면 추상화와 비슷하게 메소드의 이름과 반환형, 시그니처는 선언되었으나 기능에 대한 정의가 전혀

이루어지지 않았다. 하지만 abstract 또한 붙어있지 않다. 이 점을 잘 봐두자.

 

3. 인터페이스의 멤버

인터페이스의 특징은 추상 클래스처럼 인스턴스를 만들 수 없다. 하지만 추상클래스는 생성자가 존재하지만

인터페이스는 생성자가 존재하지 않는다.

이처럼 일반적인 클래스와 유사한 형태를 가졌으나 멤버로 가질 수 있는 종류는 많지 않다.

 

1) 상수

=> 상수는 인터페이스에 고정된 값으로 런타임 중에는 값을 바꿀 수 없기에 사용이 가능하다.

상수를 사용하기 위해서는 선언 시 반드시 초기값을 넣어야 한다. public 이 기본 형이며 생략 가능하다.

 

2) 추상 메소드

=> publc abstract 의 기본 특성을 갖기 때문에 publc abstract을 생략하더라고 컴파일시 자동으로 붙는다.

 

3) 디폴트 메소드 (자바 8 부터 추가)

=> default 가 리턴 타입 앞에 붙는다. public 이 기본 특성이기에 public 을 생략 가능하다.

 

4) 정적 메소드 (자바 8 부터 추가)

=> 일반적인 클래스 메소드와 형태가 동일하다. 기본 형태는 public 이며 생략 가능하다.

interface Member {
	// 1. 상수
	final int final_Num = 10;			
	
	// 2. 추상 메소드
	public void Add(int num1, int num2);		
	
	// 3. 디폴트 메소드
	default void minus(int num1, int num2) {	
	
		//메소드 내용까지 정의 가능!
		int result = num1 - num2;
	}
	
	// 4. 정적 메소드
	static void multiple(int num1, int num2) {	
		
		//메소드 내용까지 정의 가능!
		int result = num1 * num2;
	}
}
 

 

4. 인터페이스 직접 사용해보기

우선 메소드를 클래스와 사용하기 위해서는 implements 예약어를 알아야 한다.

implement 는 "실행하다" 라는 뜻을 갖는 단어이다.

즉, 클래스에서 다음 인터페이스를 실행한다. 라는 의미를 담아 implements 의 뒤에 인터페이스 이름을 넣는다.

또한 클래스는 상속과 달리 여러 인터페이스를 다중으로 사용이 가능하다.

public class Childclass extends Parentclass implements Interface1 , Interface2 {
	// 인터페이스 내용
}
 

 

이번 수업시간에 인터페이스 사용방법을 익히기 위해 스타크래프트 유닛을 코드로 구현하여 봤다.

우선 어떤 기능을 갖는지 정의한 인터페이스 코드이다.

// 인터페이스 : 클래스의 그룹화
interface Fightable {
	public void attack(Unit u);
}

interface Mechanic {
	public void repair(Unit u);
}
 

우선 Fightable 인터페이스는 공격 가능 기능을 갖으며 attack 추상 메소드를 갖는다.

Mechanic 은 수리가 된다는 특징을 갖는 reapir 추상 메소드를 갖는다.

 

<Unit Class>

가장 처음으로 유닛의 기본 정보를 갖는 부모 클래스이다. 또한 형태는 추상 클래스이다.

public abstract class Unit {
	private int x;
	private int y;
	private int hp;
	private int maxHp;
	
	public Unit(int maxHp) {
		this.hp = maxHp;
		this.maxHp = maxHp;
	}
	
	public Unit dead(Unit u) {
		System.out.println(u + "사망!");
		u = null;   // 사망한 유닛에 null을 넣어 소멸
		return u;
	}
	
	// 유닛별로 이동 형태가 다르기 때문에 추상 메소드로 정의
	 public abstract void move(int x, int y);
	 
	 public void stop() {
		 System.out.println(x + " , " + y + "에 정지");
	 }

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getMaxHp() {
		return maxHp;
	}

	public void setMaxHp(int maxHp) {
		this.maxHp = maxHp;
	}
}
 

<Marine Class>

다음은 테란의 마린이다.

마린은 지상 유닛이며 공격이 가능하다. 따라서 공격이 가능하도록 공격 기능을 선언한 인터페이스를 붙인다.

// 마린 클래스
class Marine extends Unit implements Fightable {   // 공격에 대한 인터페이스를 붙인다.
	int attackSpeed = 10;	// 공격 속도
	
	@Override
	public Unit dead(Unit u) {
		super.dead(u);
		return u;
	}

	public Marine(int hp) {
		super(40);
	}

	@Override
	public void move(int x, int y) {
		System.out.println(x + " , " + y + "로 뛰어서 이동");
		
	}
	
	public void stimpack() {
		attackSpeed += 5;
		int hp = super.getHp();
		hp -= 10;
		super.setHp(hp);
	}

	@Override
	public String toString() {
		return "마린!";
	}

	@Override
	public void attack(Unit u) {
		int hp = u.getHp();
		hp -= 200;	// 공격력 : 2 , 대상 유닛의 Hp를 2 씩 감소
		u.setHp(hp);
		System.out.println(u + "공격!");
		
		if(u.getHp() <= 0 ) {
			
			u.dead(u);
		}
	}
}
 

코드를 보면 추상화된 부모 클래스에서 메소드를 상속받아 사용하는 것을 볼 수 있다.

중요한 것은 attack() 메소드이다. 부모 클래스에 없는 attack() 은 위에서 본 인터페이스에서 가져온 메소드임을

알 수 있다.

 

<Tank Class>

다음은 테란의 상징인 시즈탱크의 코드이다.

시즈탱크는 마린처럼 공격이 가능하지만 수리가 가능한 메카닉 유닛이다.

이에 Fightable 인터페이스와 Mechanic 인터페이스를 다중으로 사용하는 것을 볼 수 있다.

// 탱크 클래스
class Tank extends Unit implements Fightable, Mechanic {
	
	boolean mode = false;	// F : 탱크모드 , T : 시즈모드
	
	public Tank(int hp) {
		super(hp);
	}

	@Override
	public void move(int x, int y) {
		System.out.println(x + " , " + y + "로 굴러서 이동");
	}
	
	public void changeMode() {
		mode = !mode;
	}
	
	@Override
	public String toString() {
		return "시즈탱크";
	}

	@Override
	public void attack(Unit u) {
		int hp = u.getHp();
		hp -= 30;	// 공격력 : 30 , 대상 유닛의 Hp를 30 씩 감소
		u.setHp(hp);
		System.out.println(u + "공격!");
	}

	@Override
	public void repair(Unit u) {
		int hp = u.getHp();
		while(u.getHp() < u.getMaxHp()) {
			u.setHp(hp += 1);
		}	
	}
}
 

마린과 마찬가지로 추상화된 부모클래스와 인터페이스의 메소드를 오버라이딩한 것을 볼 수 있다.

 

<DropShip Class>

다음은 수송선인 드랍쉽의 코드이다.

드랍쉽은 수송은 가능하지만 공격 기능이 없다. 또한 메카닉 이므로 수리가 가능하다.

따라서 Mechanic 인터페이스를 사용한다.

class Dropship extends Unit implements Mechanic {

	public Dropship(int hp) {
		super(hp);
	}

	@Override
	public void move(int x, int y) {
		// TODO Auto-generated method stub
		System.out.println(x + " , " + y + "로 날아서 이동");
	}
	
	public void load() {
		System.out.println("수송!");
	}
	
	public void unload() {
		System.out.println("하역!");
	}
	
	@Override
	public String toString() {
		return "드랍쉽";
	}

	@Override
	public void repair(Unit u) {
		int hp = u.getHp();
		
		while(u.getHp() < u.getMaxHp())
			u.setHp(hp += 1);
	}
}
 

 

<Main Class>

아래는 실제 위의 클래스를 사용한 메인 코드이다.

public class StarMain {

	public static void main(String[] args) {

		Marine m1 = new Marine(40);
		
		System.out.println(m1);
		
		int a = 10;
				
		Marine m2 = new Marine(40);
		Tank t1 = new Tank(150);
		Dropship d1 = new Dropship(150);
		
		m1.attack(t1);
		System.out.println(t1 + "의 HP : " + t1.getHp() + "/" + t1.getMaxHp());
		
		t1.attack(m1);
		System.out.println(m1 + "의 HP : " + m1.getHp() + "/" + m1.getMaxHp());
		
		m1.attack(d1);
		System.out.println(d1 + "의 HP : " + d1.getHp() + "/" + d1.getMaxHp());
	}
}
 

 

'JAVA' 카테고리의 다른 글

JAVA - ORACLE 연동  (0) 2022.08.28
싱글톤 패턴  (0) 2022.08.28
추상화  (0) 2022.08.28
다형성과 instanceof  (0) 2022.08.28
오버라이딩  (0) 2022.08.28

 

추상이란 실체 간에 공통되는 특성을 추출한 것을 말한다. 이를 해석하자면 어떤 실체에 대한 개념은 존재하지만,

실체에 대해 구체적인 내용은 없는 것을 말한다. 그리고 그 개념을 정의하는 것을 '추상화' 라 한다.

 

클래스로 추상의 개념을 확장해보면 객체를 직접 생성할 수 있는 클래스를 실체 클래스라 하고 실체 클래스의

공통적인 특성을 추출하여 특성에 대해 개념을 정리한 것을 추상 클래스라 한다.

 

즉, 추상 클래스를 부모로, 자식을 실체 클래스로 상속할 수 있다.

하지만 추상 클래스는 오직 개념만 존재하며 실체가 없기 때문에 인스턴스를 생성할 수 없다.

 

1. 추상 클래스를 쓰는 이유

추상 클래스를 사용하는 이유는 다음과 같다.

1) 자식에서 정의될 메소드의 형태를 강제시킬 수 있다.

=> 팀 작업에서 원활한 작업을 위해 전체적 플로우를 제시(사내 표준 정의가 가능)

2) 실체 클래스를 작성할 시간을 절약 (상속의 장점)

 

여기서 중요한 것은 1번이다.

메소드의 형태를 강제시킨다는 것은 메소드의 이름, 반환형, 매개변수를 고정시킨다는 얘기로, 추상클래스를

상속받아 사용하는 자식 클래스에서는 추상 클래스에 정의된 메소드의 시그니처 등을 반드시 따라야 한다.

이 특성을 살리기 위해 부모(추상) 클래스의 메소드를 반드시 자식에서 오버라이딩해야 한다.

(왜 오버라이딩이어야 하는지는 오버라이딩할 수 있는 조건을 생각해 보자.)

 

2. 추상 클래스 만들기

먼저 아래의 소스 코드는 동물에 대해 정의한 클래스이다.

package com;

//추상화 클래스
public abstract class Abstract_Class {	
	// 동물 클래스
	int name;
	int cate;	//
	
	abstract void sound();
}

class Dog extends Abstract_Class {

	@Override
	void sound() {
		// TODO Auto-generated method stub
		System.out.println("댕댕");
	}
}
 

부모의 클래스는 동물에 대한 개념이며, 자식 클래스는 동물을 상속받은 강아지 클래스이다.

먼저 부모 클래스의 선언을 보면 abstract 라는 예약어가 보인다.

abstract 는 [추상] 이라는 뜻으로 abstract 클래스를 붙이면 추상 클래스로 선언된다.

public abstract class Abstract_Class {	
 

부모 클래스의 메소드를 보면 메소드 반환형 앞에 abstract 가 붙은 것을 볼 수 있다. 이를 추상 메소드라 한다.

abstract void sound();
 

메소드의 형태를 보면 이름은 있지만 메소드의 기능이 전혀 정의되지 않은 것을 확인할 수 있다.

즉, 이름(개념) 만 존재하며 실체는 없다.

 

마지막 자식 클래스를 보면 부모에서 상속받은 추상 메소드를 오버라이딩하여 기능을 정리한 것을 확인할 수 있다.

@Override
	void sound() {
		System.out.println("댕댕");
	}
 

 

'JAVA' 카테고리의 다른 글

싱글톤 패턴  (0) 2022.08.28
인터페이스  (0) 2022.08.28
다형성과 instanceof  (0) 2022.08.28
오버라이딩  (0) 2022.08.28
상속  (0) 2022.08.28

 

오늘은 다형성과 instanceof 예약어에 대해 정리한다.

 

지난 포스팅에서 정리한 클래스의 4가지 특성은 다음과 같다.

1. 캡슐화(정보은닉)

2. 상속

3. 다형성

4. 추상화

 

다형성이란 다양한 형태를 가지는 성질을 의미한다.

따라서 클래스에서 말하는 다형성은 인스턴스가 다른 클래스의 형태로 변할 수 있다는 말이다.

즉, 형태가 바뀌기 때문에 '형변환' 에 대해서도 언급하고자 한다.

 

1. 다형성 개념

우선 클래스란 변수 + 메소드를 하나의 객체로 묶어주는 하나의 사용자 자료형 이라는 점을 이해해야 한다.

즉, 클래스란 int, long, float 과 같이 자료형으로써 사용할 수 있다. 이 점을 꼭 기억하자.

 

다형성이란 부모 클래스의 변수로 자손 클래스의 인스턴스를 저장할 수 있다는 개념이다.

즉, 하나의 변수에 서로 다른 인스턴스를 저장할 수 있다.

따라서 다형성의 특징을 살리기 위해서는 꼭 상속관계에 있는 부모 클래스와 자식 클래스가 있어야 한다.

상속관계에 있다 하더라도 다형성을 만족하기 위해서는 아래의 조건을 만족해야한다.

클래스 A를 상속받은 B 클래스가 있다고 한다면, 부모의 클래스 변수에 자식 클래스 타입의 대입은 가능하다.

 

반대로 자식 클래스의 변수에 부모 클래스의 타입은 대입이 불가능하다.

 

여기서 주목해야할 것은 우측에 있는 타입이다.

클래스를 만드는 것은 하나의 자료형을 만드는 것이기에 상속을 받은 자식 클래스여도 부모 클래스에 대입되기 위해서는 반드시 자료형이 바뀌어야 한다. 즉, 형변환이 필요하게 된다.

 

상속관계를 잘 생각해보면 부모는 여러 자식 클래스를 가질 수 있지만, 자식은 여러 부모를 가질 수 없다. 그렇기에

개념상으론 부모 클래스는 항상 자식 클래스보다 큰 개념이다. (물론 메모리상에선 자식이 부모보다 크거나 같다.)

따라서 작은 것을 큰 것에 넣는 것은 별다른 문제가 발생하지 않는다. 따라서 컴파일러는 문제가 발생하지 않으니

자동으로 형변환을 진행하여 대입을 할 수 있다. 이를 자동 타입 변환(Promotion) 이라 한다.

 

하지만 case 2. 와 같이 큰 것을 작은 것에 넣게 되면 에러가 발생한다. 따라서 작은 것에 들어갈 수 있도록 형태를 바꿔야 하는데 이를 '강제 형변환(Casting)' 이라 한다.

 

즉, 다형성이랑 "자동 타입 변환" 과 "강제 형변환" 을 통해 클래스 본래의 형태를 바꾸어 사용하는 것을 말한다.

 

프로그램의 수많은 객체들은 서로 연결되고 각각의 역활을 맡는데 이 객체들이 다른 객체로 교체되며 더 뛰어난

성능을 가진 코드가 될 수 있다. 마치 자동차의 부품을 더 좋은 부품으로 교체하는 것과 비슷하다.

 

그리고 이런 다형성은 상속, 오버라이딩, 형변환을 통해 구현할 수 있다.

 

2. 다형성 사용하기

다형성을 사용하기 전에 왜 다형성이 필요한가를 먼저 살펴보자.

아래의 코드는 국가와 도시의 정보를 입력하는 예제이다.

 

<부모 클래스>

package com;

import java.util.Scanner;

// 부모 클래스
public class Nation {
	protected String name;	// 이름
	protected int people;	// 인구
	
	Scanner scan = new Scanner(System.in);

	public void output() {
		System.out.println("** 국가 정보 출력 **");
		System.out.println("국가 : " + name);
		System.out.println("인구 : " + people);
	}
	
	public void input() {
		System.out.println("** 국가 정보 입력 **");
		
		System.out.print("국가 : ");
		name = scan.nextLine();
		System.out.print("인구 : ");
		people = scan.nextInt();
		scan.nextLine();
	}
}
 

<자식 클래스>

package com;

// 자식 클래스
public class City extends Nation{
	public String cityName;
	public String famousArea;
	public String famousFood;
	
	@Override
	public void output() {
		// TODO Auto-generated method stub
		super.output();
		System.out.println("** 도시 정보 출력 **");
		System.out.println("도시 : " + cityName);
		System.out.println("관광지 : " + famousArea);
		System.out.println("대표음식 : " + famousFood);
	}
	
	@Override
	public void input() {
		// TODO Auto-generated method stub
		super.input();
		System.out.println("** 도시 정보 입력 **");
		System.out.print("도시 : ");
		cityName = scan.nextLine();
		System.out.print("관광지 : ");
		famousArea = scan.nextLine();
		System.out.print("대표음식 : ");
		famousFood = scan.nextLine();
	}
}
 

<main 클래스>

package com;

import java.util.Scanner;

public class PolyManager {
	static Scanner scan = new Scanner(System.in);
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Nation[] nArr = new Nation[5];
		City[] cArr = new City[5];
		
		int nCnt = 0;
		int cCnt = 0;
		
		while(true) {
		
			switch(printMenu()) {
			case 1:
				Nation n = new Nation();
				n.input();
				nArr[nCnt] = n;
				nCnt++;
				break;
				
			case 2:
				City c = new City();
				c.input();
				cArr[cCnt] = c;
				cCnt++;
				break;
				
			case 3:
				for(int i = 0; i < nCnt; i++) {
					nArr[i].output();
				}
				break;
			case 4:
				for(int i = 0; i < cCnt; i++) {
					cArr[i].output();
				}
				break;
			}
		}
	}

	public static int printMenu() {
		System.out.println("=============================");
		System.out.println("1. 국가 정보 입력, 2. 도시 정보 입력");
		System.out.println("3. 국가 정보 출력, 4. 도시 정보 출력");
		System.out.println("=============================");
		System.out.print("입력>> ");
		int select = scan.nextInt();
		return select;
	}
}
 

 

위 코드에서 주목해야하는 점은 main 클래스 쪽에서 각 클래스의 배열이다.

서로 다른 배열을 잡은 이유는 당연히 상속을 하였어도 다른 형태의 객체를 만들기 때문이다.

즉, Nation 클래스의 인스턴스는 Nation형, City 클래스의 인스턴스는 City 형이다.

만약, Nation을 상속받은 자식클래스가 더욱 더 많아진다면 계속하여 각 인스턴스를 담을 배열을 만들어야 한다.

그렇다면 배열을 각각 만들지 말고 하나의 배열에 넣어 통합적으로 관리할 순 없을까?

 

이때 다형성을 사용하면 간단하게 문제를 해결할 수 있다.

아래의 코드는 main 클래스 부분을 변경한 것이다. (다른 클래스는 변경되지 않았다.)

package com;

import java.util.Scanner;

public class PolyManager {
	static Scanner scan = new Scanner(System.in);
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Nation[] nArr = new Nation[10];
		//Nation[] nArr = new Nation[10];
		//City[] cArr = new City[5];
		
		int nCnt = 0;
		//int cCnt = 0;
		
		while(true) {
		
			switch(printMenu()) {
			case 1:
				Nation n = new Nation();
				n.input();
				nArr[nCnt] = n;
				nCnt++;
				break;
				
			case 2:
				City c = new City();
				c.input();
				nArr[nCnt] = c;
				nCnt++;
				break;
				
			case 3:
				for(int i = 0; i < nCnt; i++) {
					if(nArr[i] instanceof Nation) {
						Nation nation = (Nation)nArr[i];
						nation.output();
					}	
				}
				break;
				
			case 4:
				for(int i = 0; i < nCnt; i++) {
					if(nArr[i] instanceof City) {
						City city = (City) nArr[i];
						city.output();
					}
				}
				break;
			}
		}
	}

	public static int printMenu() {
		System.out.println("=============================");
		System.out.println("1. 국가 정보 입력, 2. 도시 정보 입력");
		System.out.println("3. 국가 정보 출력, 4. 도시 정보 출력");
		System.out.println("=============================");
		System.out.print("입력>> ");
		int select = scan.nextInt();
		return select;
	}
	
}
 

주목할 부분은 case 3 과 case 4 이다. (보기 좋게 아래에 같은 내용을 적어 놨다.)

                       case 3:
				for(int i = 0; i < nCnt; i++) {
					if(nArr[i] instanceof Nation) {
						Nation nation = (Nation)nArr[i];
						nation.output();
					}	
				}
				break;
				
//			case 3:
//				for(int i = 0; i < nCnt; i++) {
//					nArr[i].output();
//				}
//				break;
			

			case 4:
				for(int i = 0; i < nCnt; i++) {
					if(nArr[i] instanceof City) {
						City city = (City) nArr[i];
						city.output();
					}
				}
				break;
				
//			case 4:
//				for(int i = 0; i < cCnt; i++) {
//					cArr[i].output();
//				}
//				break;
 

주석된 부분은 다형성을 사용하기 전의 코드이다. 변경된 코드와 비교를 해보자면 다음과 같이 변경됐다.

1. for문 안에 if문 instanceof 가 생겼다.

2. case 4 에서 cArr[i] -> nArr[i]  cCnt -> nCnt 로 바뀌었다.

 

우선 2번의 경우를 먼저 살펴본다면 각 클래스별 배열이 아닌 하나의 배열로 사용되는 것을 알 수 있다.

또한 배열 인덱스를 카운팅하기 위한 변수도 nCnt 한 개만 사용된다.

이렇게 하나의 배열로 두 클래스의 값을 넣었어도 실행결과는 동일할까?

우측의 출력결과를 보면 3번 국가정보 출력에서는 일본의 도시정보까지 출력이 되고 있다.

그리고 4번에서는 기존 결과와 마찬가지로 도시의 정보가 나오고 있다.

 

3번의 경우 신기한 점은 각 클래스에서는 출력하는 내용이 다르며 배열은 Nation 형으로 정의가 되었어도

Nation을 상속받은 City 클래스의 정보까지 함께 출력이 되었다는 것이다.

즉, City 인스턴스가 Nation 형으로 변환되었지만 오버라이딩된 자신의 메소드로 출력된 것을 볼 수 있다.

 

4번의 경우는 Nation 형의 배열을 출력하는데 이상하게도 같은 배열에 있던 한국의 정보는 출력되지 않았다.

이는 instanceof 라는 예약어를 사용하였기 때문이다.

 

3. instanceof

instanceof란 어떤 클래스의 인스턴스인지 확인하기 위한 키워드이다.

즉, 인스턴스와 클래스의 형을 비교하여 같다면 true, 틀리면 false 가 된다.

                                case 2:
				City c = new City();
				c.input();
				nArr[nCnt] = c;
				nCnt++;
				break;

                                case 4:
				for(int i = 0; i < nCnt; i++) {
					if(nArr[i] instanceof City) {
						City city = (City) nArr[i];
						city.output();
					}
				}
				break;
 

위 코드를 다시 보면 nArr[i] 배열은 Nation 형이다. 하지만 입력할때(case 2) City형 인스턴스를 만들어 배열에

넣은 것을 확인할 수 있다. 즉 nArr[] 에는 City형의 인스턴스가 들어간 것(자동 타입 변환)이다.

이 때 nArr[i] 과 City 의 형이 같다면 if문은 true가 되므로 안의 내용을 실행하게 된다.

if문 안에서는 City형 인스턴스를 생성하고 nArr[i]의 인스턴스를 강제로 City 형으로 형변환을 하고 있다.

이는 City형이 nArr 에 들어가기 위해 Nation 형으로 변환된 것을 다시 City 형으로 변환해 준 것이다.

이 때 Nation 형은 부모 클래스이므로 자식인 City에 들어가기 위해 강제로 형변환 한 것을 볼 수 있다.

 

이번 포스팅에서는 상속과 형변환을 이용하여 다형성을 구현했다. 다음 포스팅에서는 오버라이딩을 활용하여

매개변수 다형성에 대해 정리한다.

 

'JAVA' 카테고리의 다른 글

인터페이스  (0) 2022.08.28
추상화  (0) 2022.08.28
오버라이딩  (0) 2022.08.28
상속  (0) 2022.08.28
오버로딩  (0) 2022.08.28

 

지난 포스팅에서는 메소드 오버로딩에 대해 정리하였다면, 이번 포스팅에선 오버라이딩에 대해 정리한다.

 

 

1. 오버라이딩

먼저 오버라이딩의 뜻을 사전에서 찾아보면 다음과 같다.

프로그래밍에서 오버라이딩은 "덮어 씌운다" 라는 의미로 풀이될 수 있다.

즉, 메소드 오버라이딩은 기존의 메소드에 덮어 씌운다고 생각하면 된다.

 

오버라이딩을 사용하면 상속을 보다 유연하게 사용할 수 있다.

아래의 예제를 보자.

package com.inheritance;

public class InhMain {

	public static void main(String[] args) {
		 //TODO Auto-generated method stub
		Point p1 = new Point();
		Point3D p3d1 = new Point3D();
		
		p1.printNum();
		p3d1.printNum();
	}
}

class Point {
	protected int x = 100;
	protected int y = 200;
	
	static int number;
	
	public void printNum() {
		System.out.println("x = " + x);
		System.out.println("y = " + y);	
	}
}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{	
	int z = 300;
}
 

위 소스에서보면 부모인 Point 클래스에 printNum() 메소드에서 멤버변수의 값을 출력하는 것을 볼 수 있다.

main() 에서는 똑같이 printNum() 메소드를 호출하고 있다.

만약 자식클래스의 값을 출력해 보면 어떻게 될까?

 

당연히 부모클래스의 메소드를 상속받아도 정의된 내용에 z 를 출력하는 부분이 없기에 z는 출력되지 않는다.

이런 상황에서 z를 출력하는 방법은 2가지가 있다.

1. 당연히 자식클래스에서 메소드를 새로 만든다.

2. 부모클래스의 메소드를 오버라이딩 한다.

 

1번의 경우는 자식 클래스를 다음과 같이 메소드를 추가한다.

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{	
	int z = 300;
	public void printNum() {
		System.out.println("x = " + x);
		System.out.println("y = " + y);
		System.out.println("z = " + z);
	}
}
 

물론 정상적으로 실행결과를 볼 수 있다. 하지만 메모리에서는 추가된 메소드 만큼 손해가 발생할 것이다.

 

그럼 2번, 오버라이딩을 사용하여 보자.

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{	
	int z = 300;

	@Override
	public void printNum() {
		// TODO Auto-generated method stub
		super.printNum();
		System.out.println("z = " + z);
	}
	
}
 

1번 소스와 비교해보면 두 가지가 달라진 것을 볼 수 있다.

첫 째는 메소드 위에 @Override 라는 문구가 생긴 것.

둘 째는 메소드 내부에 x,y 를 출력하는 문장 대신에 super.printNum(); 이 생겼다는 것이다.

코드를 보면 부모클래스의 printNum() 메소드를 호출하고 추가적으로 z 를 출력하는 것을 볼 수 있다.

당연히 부모클래스의 메소드에서 x, y 를 출력하기 때문에 실행 결과는 1번과 같이 출력된다.

※ @ 에 대해서는 추후 자세하게 다룰 예정이다.

이처럼 오버라이딩은 자식클래스가 부모클래스의 메소드에 추가적인 기능을 만들고 싶어 부모의 메소드에 추가된 기능으로 덮어 씌운 것이다.

다시 말해 부모 클래스에서 선언된 메소드를 자식 클래스에서 똑같이 선언하여 부모의 메소드를 덮어 씌운 것이다.

 

이 경우를 다시 생각해보면 자식 클래스에는 상속받은 printNum 메소드와 자신의 printNum 메소드, 총 2개의

같은 이름을 가진 메소드가 있다는 것을 알 수 있다.

하지만 자신의 메소드로 상속받은 메소드를 덮어 씌웠기에 main()에서 호출한 p3d1.printNum; 부분은 자식의

메소드만 호출이 되는 것이다.

 

이렇게 오버라이딩을 사용하여 더 유연하게 상속을 사용할 수 있지만 몇 가지 지켜야하는 규칙이 있다.

1. 반환형이 동일해야 한다.

2. 메소드 이름이 같아야 한다.

3. 매개변수의 자료형이 같아야 한다.

4. 매개변수의 갯수가 같아야 한다.

※ 3번과 4번처럼 매개변수의 갯수와 자료형은 메소드 시그니쳐 라고 한다.

 

부모 :: public void printNum(int a)

오버라이딩 가능!

자식 :: public void printNum(int a)

 

 

2. super

super는 부모클래스의 멤버를 지정할 때 사용하는 예약어이다.

위의 소스에서 자식의 메소드를 다시 한 번 보자.

@Override
	public void printNum() {
		// TODO Auto-generated method stub
		super.printNum();
		System.out.println("z = " + z);
	}
	
 

메소드 내부를 보면 super 가 있는 것을 확인할 수 있다.

즉, 해당 문장은 부모 클래스에 있는 printNum() 메소드를 나타내는 것이란 걸 super 예약어를 통해 알 수 있다.

이전에 비슷한 개념으로 사용한 this 와 비교해 본다면

this :: 자신의 멤버를 지정하는 지시자

super :: 자식 클래스에서 부모 클래스의 멤버를 지정하는 지시자.

라고 정리할 수 있다.

 

또한 this에서 사용한 this.(dot) 와 this( a, b, c) 의 의미로 사용할 수 있듯이 super 또한 가능하다.

package com.inheritance;

public class InhMain {

	public static void main(String[] args) {
		 //TODO Auto-generated method stub
		Point p1 = new Point();
		Point3D p3d1 = new Point3D();
		
		p1.printNum();
		p3d1.printNum();
	
	}
}

class Point {
	protected int x = 100;
	protected int y = 200;
	public int k = 0;
	public int a;
	public int b;
	public int c;
	static int number;
	
	
	public Point() {
	
	}
	
	public Point(int a, int b, int c) {
		this.a = a;
		this.b = b;
		this.c = c;
	}
	
	public void printNum() {
		System.out.println("x = " + x);
		System.out.println("y = " + y);
		
		System.out.println("a = " + a);
		System.out.println("b = " + b);
		System.out.println("c = " + c);
	}

}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{	
	int z = 300;
	public Point3D() {
		super(770,880,990);
	}
	
	@Override
	public void printNum() {
		// TODO Auto-generated method stub
		
		super.printNum();
		super.k = 50000;
		
		System.out.println("z = " + z);
		System.out.println("k = " + k);
	}
}
 

 

위 소스에서 자식 클래스의 생성자와 printNum() 메소드에서 super 가 쓰인 것을 볼 수 있다.

this 와 마찬가지로 super( ) 를 붙이게 되면 부모 클래스의 생성자를 나타내며,

super. 를 사용하면 부모 클래스의 메소드 또는 멤버 변수에 접근할 수 있다.

 

당연한 얘기지만 super(770,880,990); 부분은 자식의 생성자 에서만 사용이 가능하다.

 

실행 결과를 보면 a,b,c 의 값이 처음 0,0,0 에서 자식 클래스의 생성자가 실행되며 770, 880, 990 으로

출력된 것을 확인할 수 있다.

 

'JAVA' 카테고리의 다른 글

추상화  (0) 2022.08.28
다형성과 instanceof  (0) 2022.08.28
상속  (0) 2022.08.28
오버로딩  (0) 2022.08.28
생성자  (0) 2022.08.28

 

상속이란 한자를 풀어보면 "뒤를 이어준다" 라는 뜻이 된다.

객체지향프로그래밍에 맞춰서 다시 생각해 보면 어떤 객체가 가지고 있던 변수나 메소드를 다른 클래스가 물려받아

사용할 수 있다.

 

 

1. 상속의 개념

상속을 사용하기 위해서는 두 개 이상의 클래스가 필요하다.

최소한 물려줄 클래스 1개, 물려준 걸 받을 클래스 1개가 필요한데 이런 관계를 '조상, 후손' 또는 '부모, 자식' 으로

부른다. 이번 포스팅에서는 부모, 자식을 주 명칭으로 사용하고자 한다.

 

우선 상속을 사용하는 문법은 다음과 같다.

class Point {
	int x;
	int y;
}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{
	int z;

}
 

아래쪽 클래스의 extends 라는 문장을 눈여겨 살펴보자.

extend란 확장하다 라는 뜻으로 사용된다. 즉, Point 클래스를 확장하여 Point3D 클래스를 만들겠다는 뜻이다.

여기서 관계를 살펴보면 아래와 같다.

부모 : Point

자식 : Point3D

 

위의 소스를 좀 더 확장하여 좀 더 알아보자.

package com.inheritance;

public class InhMain {

	public static void main(String[] args) {
		//TODO Auto-generated method stub
		Point p1 = new Point();
		p1.x = 100;
		p1.y = 200;
		
		System.out.println("x = " + p1.x);
		System.out.println("y = " + p1.y);
		
		Point3D p3d1 = new Point3D();
		p3d1.x = 10;
		p3d1.y = 20;
		p3d1.z = 50;
		
		System.out.println("x = " + p3d1.x);
		System.out.println("y = " + p3d1.y);
		System.out.println("z = " + p3d1.z);
	}
}

class Point {
	int x;
	int y;
	public Point() {
		System.out.println("Point 생성자 입니다.");
	}
}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{
	int z;
	public Point3D() {
		System.out.println("Point3D 생성자 입니다.");
	}
}

 

소스코드 아래쪽에 Point 클래스와 Point3D 클래스가 있다.

그리고 코드 위의 main 쪽을 보면 각 클래스의 인스턴스를 만들어 갑을 입력하는 것을 볼 수 있다.

 

그런데 Point3D 형 p3d1 인스턴스를 보면 Point3D 의 인스턴스 변수는 z 밖에 없지만 실제 사용된건 x, y, z 를 모두 사용하고 있다. 즉, x, y 를 가진 Point 클래스의 멤버변수를 물려받아 사용하고 있다는 것을 알 수 있다.

아래의 결과를 보면 바로 확인할 수 있다.

그런데 결과를 보면 이상한 점이 있다.

코드에서 만들어진 인스턴스는 [p1, p3d1] 2개인데 Point 클래스의 생성자가 두 번 호출된 것을 확인할 수 있다.

즉, 자식 클래스의 인스턴스가 생성되면 우선 부모의 생성자가 먼저 호출되고 그 다음 자식 생성자가 호출되어

부모 클래스의 변수의 메소드들이 먼저 메모리에 올라가고 그 다음으로 자식 클래스의 변수와 메소드가 메모리에

올라간다는 것을 확인할 수 있다.

 

 

2. protected (접근 지정자와 상속)

이번에는 위의 예제를 수정하여 상속으로 어디까지 받을 수 있을까 살펴보려한다.

※ public 으로 선언되면 의미가 없기에 public 은 정리에서 제외한다.

 

1) 부모 클래스의 맴버 변수가 private

package com.inheritance;

public class InhMain {

	public static void main(String[] args) {
		 //TODO Auto-generated method stub
		Point p1 = new Point();
		p1.setX(100);
		p1.setY(200);
		
		System.out.println("x = " + p1.getX());
		System.out.println("y = " + p1.getY());
		
		Point3D p3d1 = new Point3D();
		p3d1.setX(10);
		p3d1.setY(20);
		p3d1.setZ(30);
		
		System.out.println("x = " + p3d1.getX());
		System.out.println("y = " + p3d1.getY());
		System.out.println("z = " + p3d1.getZ());
	}

}

class Point {
	private int x;
	private int y;
	
	public Point() {
		System.out.println("Point 생성자 입니다.");
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{
	private int z;
	
	public Point3D() {
		System.out.println("Point3D 생성자 입니다.");
	}

	public int getZ() {
		return z;
	}

	public void setZ(int z) {
		this.z = z;
	}	
}
 

위의 예제를 보면 각 클래스의 멤버변수는 private 로 선언되어 있다. 그리고 main() 에서 getter/setter 메소드로

각 인스턴스 변수에 값을 넣어주는 것을 확인할 수 있다.

실행 결과를 보면 전혀 이상 없이 접근할 수 있는 것을 확인할 수 있다.

즉, 부모 클래스의 멤버 변수를 private 로 선언해도 상속받은 자식 클래스도 사용할 수 있다.

 

이번에는 자식 클래스에 새로 메소드를 추가하여 보자.

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{
	private int z;
	
	public Point3D() {
		System.out.println("Point3D 생성자 입니다.");
	}

	public int getZ() {
		return z;
	}

	public void setZ(int z) {
		this.z = z;
	}
	
	public void inputData() {
		// x = 100;  // error!
		// y = 100;  // erroe!
		setX(100);
		setY(100);
	}
}
 

inputData() 라는 자식 클래스의 메소드에서 직접 부모의 x와 y 를 사용하는 것은 불가하여 에러가 발생된다.

즉, 부모의 private 변수에 접근하기 위해서는 getter / setter 메소드로 접근해야만 한다.

2) 부모 클래스의 맴버 변수가 protected 로 선언

지난 포스팅에서 private와 public에 대해서는 정리를 하였지만 protected 는 정리하지 않았다.

드디어 상속을 정리하게 되며 protected가 등장하게 되었다.

 

우선 protected란 말 그대로 보호하다란 의미이다.

protected 는 자신 외에는 무조건 안되는 private와 모두 허용해주는 public 의 중간단계의 성격을 지닌다.

즉, protected는 같은 패키지 내에 있거나 상속받은 경우에만 접근할 수 있다.

package com.inheritance;

public class InhMain {

	public static void main(String[] args) {
		 //TODO Auto-generated method stub
		Point p1 = new Point();
		p1.setX(100);
		p1.setY(200);
		
		System.out.println("x = " + p1.getX());
		System.out.println("y = " + p1.getY());
		
		Point3D p3d1 = new Point3D();
		p3d1.setX(10);
		p3d1.setY(20);
		p3d1.setZ(30);
		
		p3d1.x = 10000;
		p3d1.y = 20000;
		p3d1.z = 30000;
		
		System.out.println("x = " + p3d1.getX());
		System.out.println("y = " + p3d1.getY());
		System.out.println("z = " + p3d1.getZ());
	}
}

class Point {
	protected int x;
	protected int y;
	
	public Point() {
		System.out.println("Point 생성자 입니다.");
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{
	int z;
	
	public Point3D() {
		System.out.println("Point3D 생성자 입니다.");
	}

	public int getZ() {
		return z;
	}

	public void setZ(int z) {
		this.z = z;
	}
	
	public void inputData() {
		x = 100;  // 부모에서 protected로 선언되어 문제없다!
		y = 100;
		setX(100);
		setY(100);
	}
}
 

소스코드에서 보면 우선 자식클래스의 inputData 메소드의 에러가 사라지는 것을 확인할 수 있다.

그 이유는 부모클래스의 멤버 변수가 protected로 선언되었기 때문이다.

또한 main() 에서 보면 클래스 외부에서 값을 직접 접근하여 입력하여도 문제가 없는 것을 확인할 수 있다.

즉, 같은 패키지 내에서는 접근을 허용해 주는 것을 볼 수 있다.

 

 

3) static 으로 선언된 변수( 메소드 )

만약 부모 클래스에서 static으로 선언된 클래스 변수와 클래스 메소드도 상속이 가능할까?

이번 소스에서는 메소드 상속 여부와 클래스 변수, 클래스 메소드 상속 여부를 확인해본다.

package com.inheritance;

public class InhMain {

	public static void main(String[] args) {
		 //TODO Auto-generated method stub
		Point3D p3d1 = new Point3D();
		
		p3d1.x = 10000;
		p3d1.y = 20000;
		p3d1.z = 30000;
		p3d1.number = 7777;	// 부모의 클래스 변수에 값 입력
	
		p3d1.Hi();		// 부모의 클래스 메소드 호출
	        p3d1.printNum(p3d1);
      }
}

class Point {
	protected int x;
	protected int y;
	
	static int number;
	
	public static void Hi() {
		System.out.println("Hi~");
	}

}

class Point3D extends Point		// Point 클래스를 상속받은 Point3D 클래스 선언
{	
	int z;
	
	public void printNum(Point3D p3d1) {
		System.out.println("x = " + p3d1.x);
		System.out.println("y = " + p3d1.y);
		System.out.println("z = " + p3d1.z);
		System.out.println("(static)number = " + p3d1.number);
	}
}
 

예상외로 static 으로 선언된 부모의 변수, 메소드까지 문제없이 상속되는 것을 확인할 수 있었다.

 

 

 

앞의 내용을 정리하자면 다음과 같다.

 

· 부모 클래스에서는 기본 생성자를 만드는 것 외에 아무런 작업이 필요없다.

· 상속 관계를 만들기 위해서는 extends 라는 예약어를 사용한다.

· 자식 클래스의 생성자가 호출되면, 자동으로 부모 클래스의 기본 생성자가 실행된다.

· 자식은 부모 클래스의 public, protected 로 선언된 인스턴스 변수 및 클래스 변수와 메소드에 접근 가능하다.

 

'JAVA' 카테고리의 다른 글

다형성과 instanceof  (0) 2022.08.28
오버라이딩  (0) 2022.08.28
오버로딩  (0) 2022.08.28
생성자  (0) 2022.08.28
배열 / foreach 사용  (0) 2022.08.28

지난 포스팅에서 생성자에 관해 다루면서 잠시 '오버로딩'에 대해 잠깐 언급하였다.

이번 포스팅에서는 그 오버로딩에 대해 다루며 this 에 대해 함께 정리한다.

 

1. 오버로딩이란?

오버로딩을 네이버 사전에 검색해보면 다음과 같이 나온다.

 

대표사진 삭제

사진 설명을 입력하세요.

사전에 정의된 내용처럼 메소드 오버로딩이란 동일한 이름을 가진 메소드를 정의하는 것을 얘기한다.

 

 

2. 오버로딩의 조건

메소드를 오버로딩하기 위해서는 다음과 같은 조건을 만족해야 한다.

 

1. 동일한 클래스에 있어야 한다.

2. 메소드 이름이 같아야 한다.

3. 메소드 매개변수의 개수가 달라야 한다.

4. 메소드 매개변수의 타입이 달라야 한다.

5. 반환형은 무관하다.

6. 접근 제한자는 무관하다.

 

<소스코드>

package com;

public class Add {
	
	// 1번 : 정수형 매개변수 1개
	public void add(int a) {
		System.out.println("실행 결과 : "+ a);
	}
	
	// 2번 : 정수형 매개변수 2개
	public void add(int a, int b) {
		System.out.println("덧셈 결과 : "+ (a+b));
	}
	
	// 3번 : 실수형 매개변수 2개
	public void add(float a, float b) {
		System.out.println("덧셈 결과 : "+ (a+b));
	}
	
	// 4번 : 정수형 매개변수 3개
	public int add(int a, int b, int c) {
		System.out.println("덧셈 결과 : "+ (a+b+c));
		return a+b+c;
	}
	
	// 5번 : 정수형 매개변수 2개 + 반환형
	//public int add(int a, int b) {	// error!
	//	System.out.println("덧셈 결과 : "+ (a+b));
	//	return a + b;
	//}
}
 

코드에서 보면 add( ) 메소드를 총 5개 만들었다. (주석까지 5개)

중요한 것은 매개변수의 갯수와 타입이다.

각 메소드의 이름은 같지만 매개변수의 개수와 자료형의 차이로 인해 오버로딩된 것을 확인할 수 있다.

만약 5번 메소드를 주석 해제하면 에러가 발생된다.

그 이유는 반환형의 차이는 있지만 매개변수의 자료형과 갯수가 완전히 동일하기 때문이다.

즉, 오버로딩에 중요한 것은 매개변수이며 반환에 따른 차이는 전혀 없다는 것이다.

 

package com;

public class AddManager {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Add add = new Add();
		
		add.add(10); 			// 매개변수가 1개
		add.add(1, 5);			// 정수형을 받는 매개변수
		add.add(1.1f, 5.0f);	        // 실수형을 받는 매개변수
		add.add(1, 5, 6);		// 반환형 + 정수형을 3개 받는 매개변수
		System.out.println("덧셈 결과 : " + add.add(1, 5, 6));	// 반환 결과
	}

}
 

위의 메인 메소드와 함께 실행해보면 다음의 결과를 얻을 수 있다.

 

3. this

C++ 에서의 this 의미는'자기 자신을 가리키는 포인터' 였다.

JAVA 에서도 비슷한 개념으로 자기 자신을 가리킬 때 this 가 사용된다.

 

JAVA 에서는 클래스 내부에서 다른 생성자를 호출할 때 사용되는 지시자로도 사용할 수 있다.

또한 자신의 멤버를 가리키는 용도로도 사용될 수 있다.

 

먼저 다른 생성자를 호출할 때 사용을 살펴보자.

package com;

public class CarManager {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Car car1 = new Car();
		car1.showInfo();
		
		Car car2 = new Car("Red");
		car2.showInfo();
		
		Car car3 = new Car("White", "manual");
		car3.showInfo();
		
		Car car4 = new Car("Black", "manual", 2);
		car4.showInfo();
	}

}
 

main 메소드 부분이다. 이번 예제에서는 main에 대한 변경은 없으니 이 소스만 참고해도 무관하다.

package com;

public class Car {
	String color;		// 색상
	String gearType;	// auto , manual
	int door;		// 문의 갯수
	
	Car(){
		color = "Grey"; // 기본컬러 회색 설정
		gearType = "auto";
		door = 4;
	}
	
	Car(String c) { 		
                color = c;
		gearType = "auto";
		door = 4;
	}

	Car(String c, String g) {			
                color = c;
		gearType = g;
		door = 4;
	}

        Car(String c, String g, int d){			
                color = c;			
		gearType = g;	
		door = d;
	}
	public void showInfo() {
		System.out.println("색상 : " + color + " , " + "변속기 : " + 
				    gearType + " , " + "문 : " + door);
	}
}
 

위 예제는 자동차의 색상, 기어 종류, 문의 갯수를 인스턴스 변수로 하는 클래스이다.

총 4개의 생성자 메소드가 오버로딩된 것을 확인할 수 있다.

각 생성자를 보면 color 와 gearType, door 멤버 변수들이 반복적으로 나오고 있는 것을 확인할 수 있다.

이런 중복적인 코드는 라인의 수만 지나치게 많아지게 하여 코드 작성에 불편함을 초래한다.

코드는 조금 지저분하지만 실행 결과는 정상적으로 나온다.

이를 this를 통하여 다시 정리해보자.

package com;

public class Car {
	String color;		// 색상
	String gearType;	// auto , manual
	int door;		// 문의 갯수
	
	Car(){
		this("grey");	
        }
	
	Car(String c) { 		
               this(c, "auto");
	}

	Car(String c, String g) {			
               this(c, g, 4);
	}

        Car(String c, String g, int d){			
                color = c;			
		gearType = g;	
		door = d;
	}
	public void showInfo() {
		System.out.println("색상 : " + color + " , " + "변속기 : " + 
				    gearType + " , " + "문 : " + door);
	}
}
 

많은 양의 코드가 아니라 차이는 적어보이지만 this 를 통해 깔끔하게 정리된 것을 확인할 수 있다.

코드의 의미상으론 동일하다. 물론 실행결과도 동일한다.

 

이제 생성자를 보자.

잘 보면 각 생성자에서 자신의 매개변수와 this 에 들어간 값의 갯수가 1개씩 차이나는 것을 확인할 수 있다.

즉, this 에 들어간 값의 갯수에 따라서 갯수가 맞는 다른 생성자를 호출하는 것을 확인할 수 있다. 이는 각 생성자를

호출시 입력된 매개변수와 함께 최종 형태 ( Car(String c, String g, int d) 생성자 ) 까지 각 변수에 조립하듯 갚이

넣어지는 것을 볼 수 있다. (맨 위의 소스인 main 에서 인스턴스 생성 시 입력된 값을 참고해 보자.)

 

this의 용도는 다른 생성자를 호출하는 외에도 한 가지 더 있다.

바로 멤버변수를 지칭하는 것이 또 하나의 용도이다.

 

위의 예제를 다시 수정하여 봤다.

package com;

public class Car {
	String color;		// 색상
	String gearType;	// auto , manual
	int door;			// 문의 갯수
	
	Car(){
		this("grey");
	}
	
	Car(String c) {	
		this(c, "auto");
	}
	
	Car(String c, String g) {
		this(c, g, 4);
	}
	
	Car(String color, String gerType, int door){
		this.color = color;			
		this.gearType = gerType;	
		this.door = door;
	}
	
	public void showInfo() {
		System.out.println("색상 : " + color + " , " + "변속기 : " + 
							gearType + " , " + "문 : " + door);
	}
}
 

주목할 곳은 마지막 생성자 부분이다.

잘 보면 매개변수와 다른 변수의 이름이 같으며 변수 앞에 this. 가 붙은 것을 확인할 수 있다.

여기서 this가 붙은 변수는 클래스 멤버 변수라는 것을 확인할 수 있다. (이클립스에서는 색상의 차이로 식별가능)

즉 this 를 통해 멤버 변수라는 것을 확인시켜 이름이 같아도 서로 다른 변수임을 알려준다.

 

이러한 기능은 setter 메소드를 자동완성해 보면 확인해 볼 수 있다.

 

※ 이런 this의 기능은 이클립스의 코드 자동완성 기능에서 (다른 언어, 프로그램 에서도) 변수의 명칭이 같아져

발생하는 문제를 해결하기 위해 만들어 둔 듯 하다.

실행 결과는 전혀 다름 없음을 볼 수 있다.

 

'JAVA' 카테고리의 다른 글

오버라이딩  (0) 2022.08.28
상속  (0) 2022.08.28
생성자  (0) 2022.08.28
배열 / foreach 사용  (0) 2022.08.28
import  (0) 2022.08.28

+ Recent posts