유난스런 개발 기록

[JAVA] Day-15 _ 다형성 (Polymorphism) & 추상 클래스 (Abstract Class) 본문

개념정리/JAVA

[JAVA] Day-15 _ 다형성 (Polymorphism) & 추상 클래스 (Abstract Class)

yourhwan 2023. 1. 29. 22:55

day-15

다형성 (Polymorphism)


다형성 (Polymorphism) 이란? 

  • 상속을 이용하여 여러 클래스 타입을 하나의 클래스 타입으로 다루는 기술
  • 쉽게 말하자면 큰 범주의 클래스를 생성하고, 큰 범주에 속하는 작은 범주의 클래스들이 큰 범주 안의 필드와 메소드를 상속 받아 사용할 수 있는 것. 
  • 자식클래스로 생성되어진 객체를 부모 클래스 타입으로 받을 수 있다.

 

자세한 내용은 코드를 통해 확인해보는 것이 이해하기 쉽다.

수업시간 동물을 주제로 실습한 코드를 첨부한다.

패키지의 클래스 구성은 아래와 같다.

부모 클래스 : Animal

자식 클래스 : Dog, Cat, Duck

실행을 위한 메인 클래스 : Main

 

Animal 

package my.day15.d.polymophism;

public class Animal {

	// Dog, Cat, Duck 의 공통 field (추상화)
	private String name;
	private int birthYear;
	
	// Dog, Cat, Duck 의 공통 method(기능) (추상화)
	//
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		if(name != null && !name.trim().isEmpty())
		this.name = name;
	}
	
	public int getBirthYear() {
		return birthYear;
	}
	
	public void setBirthYear(int birthYear) {
		if(birthYear > 0)
		this.birthYear = birthYear;
	}
	
	// === 동물들 (강아지, 고양이, 오리)의 정보를 출력해주는 메소드 === //
	protected void showInfo() {
		System.out.println("== 동물정보 == \n"
				+ "1. 성명 : " + name + "\n"
				+ "2. 생년 : " + birthYear + "년 \n");
		
	}
	
	protected void cry() {
		System.out.println(">>> 동물들이 소리를 냅니다. <<<");
	}
	
}

 

Dog, Cat, Duck 이 공통으로 가질 수 있는 필드를 생성해주고 

공통적으로 가질 수 있는 동적 특징인 울음소리를 메소드로 생성했다.

또한 해당 동물들의 정보를 볼 수 있는 showInfo() 메소드를 생성했다.

Cry() 메소드의 접근제한자를 protected로 설정한 이유는 다양한 접근제한자를 사용하기 위해 했을 뿐

별다른 의미는 없다.

이제 생성 되어진 데이터들을 각각의 동물들에게 상속해 줄 것이다. 

 

Dog

package my.day15.d.polymophism;

public class Dog extends Animal {

	// Dog 만 가지는 filed를 정의 (추상화)
	private int weight;

	
	
	// Dog 만 가지는 method를 정의 (추상화)
	public int getWeight() {
		return weight;
	}

	public void setWeight(int weight) {
		if(weight > 0)
			this.weight = weight;
	}
	
	public void run() {
		System.out.println(">>> 강아지가 빠르게 달립니다. <<<");
	}
	
	
	// 메소드의 오버라이딩(재정의) // 
	@Override //(부모클래스보다 범위가 크거나 같아야한다.)
	public void showInfo() {
		System.out.println("== 강아지 정보 ==\n"
				+ "1. 성명 : " + getName() + "\n"
				+ "2. 생년 : " + getBirthYear() + "년\n"
				+ "3. 몸무게  : " + weight + "kg\n");
	}
	
	// 메소드의 오버라이딩(재정의) //
	@Override
	protected void cry() {
		System.out.println(">>> 강아지는 멍멍 짖습니다. <<<");
	}
	
	
}

 

Dog 에는 강아지만이 가질 수 있는 필드와 메소드를 추가했다.

(몸무게랑 달리기는 강아지만의 특성이 아니지만 그냥 넣었다.)

해당 클래스에서는 메소드의 오버라이딩을 확인할 수 있는데, 

Animal 클래스의 showInfo() 그리고 Cry()를 상속 받아

Dog 만이 갖는 필드와 메소드를 추가시켜 오버라이딩 했다.

 

Cat

public class Cat extends Animal {

	// Cat 만 가지는 fiedl를 정의 (추상화)
	private String color;
	
	

	// Cat 만 가지는 method를 정의 (추상화)
	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		if(color != null && !color.trim().isEmpty())
			this.color = color;
	}
	
	public void jump() {
		System.out.println(">>> 고양이는 점프를 합니다. <<<");
	}
	
	
	// 메소드의 오버라이딩(재정의) // 
		@Override //(부모클래스보다 범위가 크거나 같아야한다.)
		public void showInfo() {

			System.out.println("== 고양이 정보 ==\n"
					+ "1. 성명 : " + getName() + "\n"
					+ "2. 생년 : " + getBirthYear() + "년\n"
					+ "3. 털색  : " + color + "\n");
		}
		
		// 메소드의 오버라이딩(재정의) //
		@Override
		protected void cry() {
			System.out.println(">>> 고양이는 야옹야옹 웁니다. <<<");
		}
		
}

 

Cat의 경우도 Dog와 같은 방향성으로 코드를 작성했다.

 

Duck

package my.day15.d.polymophism;

public class Duck extends Animal {

	// Duck 만 가지는 fiedl를 정의 (추상화)
	private int price;
	
	

	// Duck 만 가지는 method를 정의 (추상화)
	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		if(price > 0)
			this.price = price;
	}
	
	public void swim() {
		System.out.println(">>> 오리는 물가에서 헤엄을 칩니다. <<<");
	}
	
	
	@Override
	protected void cry() {
		System.out.println(">>> 오리는 '꽥꽥'하고 소리를 냅니다. <<<");
	}
	
}

Duck의 경우도 마찬가지다.

 

Main

package my.day15.d.polymophism;

public class Main {

	public static void main(String[] args) {

		Animal[] aniArr = new Animal[5];
		
		Dog dog = new Dog();
//		Animal anil = new Dog(); // 가능
//		aniArr[0] = new Dog();	 // 가능
		dog.setName("뽀삐");
		dog.setBirthYear(2010);
		dog.setWeight(5);
		
		aniArr[0] = dog;
		
		Cat cat = new Cat();
//		Animal ani2 = new Cat(); // 가능
//		aniArr[1] = new Cat(); 	 // 가능
		cat.setName("톰");
		cat.setBirthYear(2017);
		cat.setColor("검정");
		aniArr[1] = cat;
		
		Duck duck = new Duck();
//		Animal ani3 = new Duck(); // 가능
//		aniArr[2] = new Duck();	  // 가능	
		duck.setName("도날드");
		duck.setBirthYear(2018);
		duck.setPrice(5000);
		aniArr[2] = duck;
		
		
		for(int i =0; i<aniArr.length; i++) {
			if(aniArr[i] != null) 
			aniArr[i].showInfo();
		}
		
		System.out.println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		
		for(int i =0; i<aniArr.length; i++) {
			if(aniArr[i] != null) 
			aniArr[i].cry();
		}

		
		System.out.println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");

    for(int i =0; i<aniArr.length; i++) {
        if(aniArr[i] != null) {

            if(aniArr[i] instanceof Dog) {
            // aniArr[i] 가 Dog 클래스로 생성되어진 instance 입니까? 라는 의미
            // aniArr[i] 이 new Dog(); 로 생성되어진 것이라 true	
            // aniArr[i] 이 new Dog(); 로 생성되어진 것이 아니라면 false
            ((Dog) aniArr[i]).run();

            }
            else if(aniArr[i] instanceof Cat) {
            // aniArr[i] 가 Dog 클래스로 생성되어진 instance 입니까? 라는 의미
            // aniArr[i] 이 new Dog(); 로 생성되어진 것이라 true	
            // aniArr[i] 이 new Dog(); 로 생성되어진 것이 아니라면 false
                ((Cat) aniArr[i]).jump();
            }
            else if(aniArr[i] instanceof Duck) {
            // aniArr[i] 가 Dog 클래스로 생성되어진 instance 입니까? 라는 의미
            // aniArr[i] 이 new Dog(); 로 생성되어진 것이라 true	
            // aniArr[i] 이 new Dog(); 로 생성되어진 것이 아니라면 false
                ((Duck) aniArr[i]).swim();
            // 형변환 한 이유는 부모클래스로부터 오버라이딩 해왔기 때문에 해당 배열의 메소드를 사용할 수 있는지 없는지
            // 확인해야한다. 때문에 배열 앞에 해당하는 객체를 밝혀주어 형변환한다.
            }
        }


    }// end of for()--------------------------

	}// end of public static void main(String[] args)---------------------

}

Main 클래스에서는 각 동물들의 인스턴스를 생성해주고 해당하는 값들을 설정해주었다.

이후 부모 클래스인 Animal의 메소드들을 호출하여 동작하도록 코드를 작성한 것을 확인할 수 있다.

지난 포스팅인 상속을 첨부한다.

2023.01.29 - [국비지원 수업내용 정리/JAVA] - [JAVA] Day-14 _ 상속성 (Inheritance)

 

[JAVA] Day-14 _ 상속성 (Inheritance)

day-14 상속성 (Inheritance) 상속성 (Inheritance) 이란? 클래스의 재사용과 소스코드의 중복을 최소화하는 목적으로 사용 상속의 종류는 extends 와 implements 가 있다. 오늘은 extends 에 대해 알아볼 것이다.

yourhwan.tistory.com

 

첫번째 for문에서 생성한 인스턴스들의 정보를 showInfo() 메소드를 통해 호출해주고,

두번째 for문에서 instanceof를 통해 해당 객체가 어떤 클래스로부터 상속 받았는지 확인해준다.

Main 클래스의 제일 윗쪽 코드를 보면 새로운 동물들의 객체를 만들어 배열에 넣어주는데,

다양한 선언 방법이 주석처리 되어 있을 것이다.

 

Dog의 경우 

Dog dog = new Dog(); 로 생성했지만

Animal ani1 = new Dog();

=> 부모 클래스에게 상속 받았기 때문에  Animal 타입으로 생성 가능 (부모의 것은 자식의 것)

aniArr[0] = new Dog(); 

=> 해당 코드 또한 Animal[] aniArr = new Animal[5]; 에서 확인할 수 있듯이

부모  클래스에서 생성한 배열이기 때문에 자식 클래스가 사용할 수 있다.

그렇기 때문에 위의 두 가지 방법으로도 생성할 수 있다.

Cat 과 Duck 또한 마찬가지다.

instanceof가 이러한 내용들을 확인해주는 작업을 한다고 이해하면 되겠다.

 

이후 코드를 살펴보면

(Dog) aniArr[i]).run();와 같이 다운캐스팅(DownCasting)을 명시적 형변환으로 나타냈다.

컴파일러가 자동으로 형변환하지 못할 것을 방지하기 위해 해준 것이다.

(만약 필자가 제대로 이해하지 못한 것이라면 댓글로 지적 부탁드립니다.)

자식클래스가 하위 클래스이기 때문에 Animal[] aniArr 배열에 넣어주는 과정에서

업캐스팅(UpCasting)이 진행되어, 

자식 클래스의 메소드를 호출하기 위해 다시 다운캐스팅을 해준 것이다.

 


추상 클래스 (Abstract Class) 란? 

 

2023.01.23 - [국비지원 수업내용 정리/JAVA] - [JAVA] Day-12 _ 추상화 (abstraction)

 

[JAVA] Day-12 _ 추상화 (abstraction)

day-12 추상화 (abstraction) 추상화 (abstraction) 란? 프로그램이 필요로 하는 실제 데이터들을 모델링하는 기술 일반적인 의미의 추상화란 어떤 물체(object)에서 주된 특징을부각시켜 표현하고, 나머지

yourhwan.tistory.com

앞서 포스팅한 내용을 바탕으로 만든 클래스를 말한다.

 

추상 클래스의 특징

  • 미완성 메소드(== 추상 메소드, abstract method) 가 있는 클래스는
  • 반드시 미완성 클래스(== 추상 클래스, abstract class)로 만들어야한다.
  • 이유로는 자식 클래스에서 메소드를 오버라이딩 해주기 때문에 부모 클래스에서는 메소드를 정의할 필요가 없다.
  • 일반적으로 부모 클래스는 미완성 클래스로 만든다.

 

다형성에서 살펴본 코드 중 만약 새로운 Dog 인스턴스를 생성한다고 할 때, 위의 코드에 나온 방법이 아닌 다른 방법으로 만들 수 있을까?

예를 들면 부모 클래스인 Animal 클래스로 객체를 생성하는 것 말이다.

해당 경우는 Animal 클래스의 Cry() 메소드가 달라질 것이다.

 

Main

package my.day15.e.abstractClass;

// abstract public class Animal
// 또는
public abstract class Animal { // 미완성 클래스(== 추상 클래스, abstract class)

	// Dog, Cat, Duck 의 공통 field (추상화)
	private String name;
	private int birthYear;
	
	
	// Dog, Cat, Duck 의 공통 method(기능) (추상화)
	//
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		if(name != null && !name.trim().isEmpty())
		this.name = name;
	}
	
	public int getBirthYear() {
		return birthYear;
	}
	
	public void setBirthYear(int birthYear) {
		if(birthYear > 0)
		this.birthYear = birthYear;
	}
	
	// === 동물들 (강아지, 고양이, 오리)의 정보를 출력해주는 메소드 === //
	protected void showInfo() {
		System.out.println("== 동물정보 == \n"
				+ "1. 성명 : " + name + "\n"
				+ "2. 생년 : " + birthYear + "년 \n");
	}
	
//	abstract protected void cry();
// 	또는 	
	protected abstract void cry(); // 미완성 메소드(== 추상 메소드, abstract method)
// 미완성 메소드(== 추상 메소드, abstract method) 는 자식클래스에서 무조건 재정의(오버라이딩)를 꼭 해주어야만 한다.!!!
	
}

 

코드를 보면 클래스의 선언 부분 코드에서 추상 클래스임을 나타내는

public abstract class Animal  (또는 abstract public class Animal 로도 가능) 로 변한 것과

마지막 Cry() 메소드의 내용이 없는 것을 확인할 수 있다.

이것이 바로 추상 클래스의 특징인 미완성 메소드이다.

 

이렇게 되면 자식 클래스에서 무조건 미완성 메소드를 오버라이딩 해야한다.

즉, 메소드를 재정의 해줘야한다는 것이다.

자식 클래스별 Cry(); 메소드 부분을 확인하면 다음과 같다. 

 

 public class Dog extends Animal {
    @Override
    protected void cry() {
    // 메소드 오버라이딩(메소드 재정의)시 접근제한자는 부모클래스에서 정의해둔
    // 미완성(==추상) 메소드의 접근제한자와 같거나 접근허용이 더 큰 것으로 해주어야한다.
   		System.out.println(">>> 고양이는 '야옹야옹'하고 웁니다.");		
    }
 }


public class Cat extends Animal { 
    @Override
    protected void cry() {
    // 메소드 오버라이딩(메소드 재정의)시 접근제한자는 부모클래스에서 정의해둔
    // 미완성(==추상) 메소드의 접근제한자와 같거나 접근허용이 더 큰 것으로 해주어야한다.
   		System.out.println(">>> 고양이는 '야옹야옹'하고 웁니다.");		
    }
 }   
      
      
 public class Duck extends Animal {       
    @Override
    protected void cry() {
    // 메소드 오버라이딩(메소드 재정의)시 접근제한자는 부모클래스에서 정의해둔
    // 미완성(==추상) 메소드의 접근제한자와 같거나 접근허용이 더 큰 것으로 해주어야한다.
    	System.out.println(">>> 오리는 '꽥꽥'하고 웁니다.");
    }    
 }

 

그렇다면 추상 클래스의 경우 부모 클래스 즉 미완성 클래스의 타입으로 인스턴스를 생성할 수 있을까?

 

Main

package my.day15.e.abstractClass;

public class Main {

	// >>> 다형성(Polymophism) <<<
	// 상속을 이용하여 여러 클래스 타입을 하나의 클래스 타입으로 다루는 기술.
	// 자식클래스로 생성되어진 객체를 부모 클래스 타입으로 받을 수 있다. 이것이 다형성 (Polymophism) 이다.
	
	public static void main(String[] args) {

		Animal[] aniArr = new Animal[5];
		
		Dog dog = new Dog();
//		Animal anil = new Dog(); // 가능
//		aniArr[0] = new Dog();	 // 가능
		dog.setName("뽀삐");
		dog.setBirthYear(2010);
		dog.setWeight(5);
		
		aniArr[0] = dog;
		
		Cat cat = new Cat();
//		Animal ani2 = new Cat(); // 가능
//		aniArr[1] = new Cat(); 	 // 가능
		cat.setName("톰");
		cat.setBirthYear(2017);
		cat.setColor("검정");
		aniArr[1] = cat;
		
		Duck duck = new Duck();
//		Animal ani3 = new Duck(); // 가능
//		aniArr[2] = new Duck();	  // 가능	
		duck.setName("도날드");
		duck.setBirthYear(2018);
		duck.setPrice(5000);
		aniArr[2] = duck;
        
        }
   }

 

원래의 경우 우리는 위와 같이 인스턴스를 생성하고 고유한 값들을 넣어주었는데,

우리가 궁금한 것은 아래의 코드블럭과 같은 경우다. 

Animal ani_1 = new Animal();
// Animal은 미완성 클래스(== 추상 클래스, abstract class)이므로 객체(인스턴스)를 만들 수 없다.


Animal ani_2 = new Dog();
// 하지만 자식 클래스로 생성되어진 객체(인스턴스)를 저장하는 용도로는 사용 가능하다.

생성하고자 하는 인스턴스 ani_1은  new Animal()의 Animal이 미완성 클래스이므로 불가능하다.

하지만 

두번째 코드와 같이 자식 클래스인 new Dog()를 넣어준다면 저장소의 역할로 선언이 가능하다.

즉, 부모 클래스는 미완성 클래스이기 때문에 불가능하다.

 

오늘은 다형성과 추상 클래스에 대해 알아보았는데, 사실 코드로 확인을 해보면 어렵지 않은 개념이다.

특히나 다형성의 경우 복합적인 개념들이 모인 것이다보니 명확하게 이해할 필요가 있다.

추상 클래스도 마찬가지로 그 개념을 명확이 숙지하고 있어야 앞으로 프로그래밍을 하는 것에 있어서

어려움이 없을 것이다.