안녕하세요, 코린이의 코딩 학습기 채니 입니다.
개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.
Animal 부모 클래스
public class Animal {
public void say() {
System.out.println("안녕하세요, 동물 입니다.");
}
public void attack() {}
}
Lion 자식 클래스
public class Lion extends Animal {
@Override
public void say() {
System.out.println("안녕하세요, 라이언입니다.");
}
@Override
public void attack() {
punch();
}
public void punch() {
System.out.println("라이언 펀치!");
}
}
Tiger 자식 클래스
public class Tiger extends Animal {
@Override
public void say() {
System.out.println("안녕하세요, 타이거입니다.");
}
@Override
public void attack() {
kick();
}
public void kick() {
System.out.println("타이거 킥!");
}
}
메인 메소드
public void test6() {
Animal[] animals = new Animal[5];
for(int i = 0; i < animals.length; i++) {
animals[i] = animalGenerator();
}
for(Animal animal : animals) {
animal.say();
}
}
public Animal animalGenerator() {
Random rnd = new Random();
if(rnd.nextBoolean())
return new Lion();
else
return new Tiger();
}
@콘솔출력값
안녕하세요, 라이언입니다.
안녕하세요, 라이언입니다.
안녕하세요, 라이언입니다.
안녕하세요, 타이거입니다.
안녕하세요, 타이거입니다.
Animal 객체 배열을 만들고, 랜덤으로 Lion 혹은 Tiger 객체를 animals 객체에 요소로 대입 후 메소드 say()를 호출하였더니,
각 자식 클래스 별로 오버라이딩한 say()메소드가 호출 되는 것을 확인할 수 있습니다.
만약 여기서 Lion에 say()메소드를 깜빡하고 오버라이딩 하지 않았을 때, 어떻게 될까요?
public class Lion extends Animal {
// @Override
// public void say() {
// System.out.println("안녕하세요, 라이언입니다.");
// }
@Override
public void attack() {
punch();
}
public void punch() {
System.out.println("라이언 펀치!");
}
}
@콘솔출력값
안녕하세요, 타이거입니다.
안녕하세요, 타이거입니다.
안녕하세요, 동물 입니다.
안녕하세요, 동물 입니다.
안녕하세요, 동물 입니다.
Lion 객체의 say()가 호출 될 때는 부모의 say()메소드 "안녕하세요, 동물 입니다."가 출력됩니다.
문제는 없으나, 만약 반드시 각각의 인사말을 출력 해야 한다고 하면 이건 엄청난 실수겠죠?
이런 실수를 방지하고자 abstract 제어자를 사용해줍니다.
☞ abstract 제어자
- abstract이 붙은 메소드를 '추상 메소드', abstract이 붙은 클래스를 '추상 클래스'라고 정의 (미완성메소드라고 불림)
- 추상 메소드를 통해 자식 클래스의 메소드 구현을 강제화
- 객체화 할 수 없으므로 자식 클래스를 객체화하여 사용
- 일반 클래스 성격을 갖고 있으므로, 필드/메소드를 갖고 자식 클래스에게 상속 가능
abstract 메소드 (추상 메소드)
- 몸통부가 없음
- 몸통부는 자식 클래스에서 구현해야 하므로 부모 클래스에서는 작성하지 않음
- 동적 바인딩 메소드로 사용, 자식 클래스의 규격이 됨
public abstract class Animal {
//추상 메소드
public abstract void say();
public void attack() {}
}
say() 메소드를 추상 메소드로 정의하였더니, Lion 클래스에서 say()를 오버라이딩 하지 않으니 에러가 발생하는 것을 확인할 수 있습니다.
"The type Lion must implement the inherited abstract method Animal.say()"
추상 메소드인 Animal 클래스의 say()를 반드시 구현해야 하기 때문이죠.
public class Lion extends Animal {
@Override
public void say() {
System.out.println("안녕하세요, 라이언입니다.");
}
오버라이드 처리를 하였더니 에러 메세지가 사라진 것을 확인할 수 있었습니다.
abstract 클래스 (추상 클래스)
- 0개 이상의 추상 메소드를 가질 수 있음 (추상 메소드가 0개인 경우, 객체화를 막기 위한 목적)
- 자식 클래스는 반드시 부모 클래스의 추상 메소드를 구현해야 함
//추상 클래스
public abstract class Animal {
public abstract void say();
public void attack() {}
}
추상 메소드를 1개 이상 포함하고 있는 클래스는 반드시 추상 클래스로 정의해야 하기 때문에,
Animal 클래스를 추상 클래스로 정의하였습니다.
다만 추상 클래스는 객체화가 불가하기 때문에 자식 클래스를 객체화하여 사용해야 합니다.
//추상 클래스는 객체화 불가
//Animal animal = new Animal();
Animal animal = new Lion(); //가능
Animal animals = new Tiger(); //가능
추상 클래스 객체화 불가한 이유는?
추상 클래스는 내부의 미완성 메소드 때문에 객체를 직접 생성할 수 없습니다.
힙 메모리에 생성되는 객체는 내부 요소가 미완성된 상태로 들어갈 수 없기 때문입니다.
(힙 메모리에는 값이 비어 있는 필드가 저장 될 수 없으므로 초기화 하지 않은 필드를 힙 메모리에 저장하려고 하면 강제로 값을 초기화하기 때문)
추상 클래스 타입의 객체 생성 방법
① 추상 클래스를 상속한 일반 클래스 생성
- 위에서 설명한 자식 클래스를 통해 객체화 하는 방법
② 익명 이너 클래스 사용
- 컴파일러가 내부적으로 추상 클래스를 상속해 메소드 오버라이딩을 수행한 클래스를 생성하고, 그 클래스로 객체를 생성하는 방법
이 때! 내부적으로 생성된 클래스명을 알 수가 없으므로 '익명 이너 클래스'라고 합니다.
//익명 이너 클래스 사용
Animal ani = new Animal() {
@Override
public void say() {
System.out.println("난 Animal이야");
}
@Override
public void attack() {
System.out.println("Animal 공격!");
}
};
ani.say();
ani.attack();
@콘솔출력값
난 Animal이야
Animal 공격!
이때 Animal()은 Animal 클래스의 생성자를 호출한 것이 아닌, 컴파일러가 Animal 클래스를 상속받아 say(), attack() 메소드를 오버라이딩한 익명 클래스의 생성자를 호출한다는 것을 의미합니다.
두 방법의 장점!!
객체를 여러 개 만들어야 하는 상황이라면 자식 클래스를 직접 정의해주는 것이 좋습니다.
→ 익명 이너 클래스는 클래스명을 알 수 없기 때문에 객체를 정의할 때마다 익명 이너 클래스를 정의해줘야 합니다.
단 한 번만 만들어서 사용할 객체라면 익명 이너 클래스를 활용하는 것이 쉽고 간결합니다.
abstract 제어자의 장점
만일 오버라이딩 과정에서 메소드명의 오타, 혹은 깜빡하고 오버라이딩을 하지 않았을 때 등의 실수를 방지하고자 사용하기 좋습니다.
abstract 제어자를 사용하면, 부모 클래스가 갖고 있는 메소드를 오버라이딩 하지 않으면 문법적 오류가 발생하게 됩니다.
상속 받은 추상 메소드를 오버라이딩을 하거나 추상 클래스로 정의(추상 메소드를 상속 받았기 때문) 둘 중 하나는 반드시 해야 하기 때문이죠!
이렇게 실수를 사전에 방지하거나 Animal 클래스와 Lion, Tiger 클래스를 서로 다른 사람이 작성했다면 필요성은 더더욱 느껴질 것입니다.
'Java > Java' 카테고리의 다른 글
API) 문자열을 구분해주는 split, StringTokenizer (0) | 2022.03.25 |
---|---|
인터페이스) 인터페이스 클래스 (0) | 2022.03.25 |
다형성) 다형성의 활용(매개변수 선언부, 리턴 타입), 동적 바인딩, 정적 바인딩 (0) | 2022.03.24 |
다형성) instanceof 키워드(캐스팅 가능 여부 확인) (0) | 2022.03.24 |
객체 지향 3대 원칙) 다형성에 대하여, 업 캐스팅, 다운 캐스팅 (0) | 2022.03.24 |