사전적 의미로 추상은 실체 간에 공통되는 특성을 추출한 것을 말한다.
추상 클래스
객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면
이 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다.
즉, abstract 키워드가 붙는 클래스는 객체로 인스턴스를 생성할 수 없다는 뜻이다.
그리고 실체 클래스와 추상 클래스는 상속 관계를 가지고 있다.
이러한 추상 클래스의 예로 동물을 들 수 있다.
동물 클래스는 현실 세계에 실체가 없으므로 abstract를 사용한 추상 클래스라고 할 수 있고,
실제로 존재하는 물고기, 새 등이 실체 클래스라고 할 수 있다.
public abstract class AbstractCalc {
int add(int x, int y) {
return x+y;
}
int sub(int x, int y) {
return x-y;
}
int mul(int x, int y) {
return x*y;
}
int div(int x, int y) {
return x/y;
}
}
public class DataCalc extends AbstractCalc{
int x, y, result;
@Override
public String toString() {
return "x = " + x + ", " +
"y = " + y;
}
}
package inherite.abstrctClass;
public class AbstractCalcApp {
public static void main(String[] args) {
// 계산기1
DataCalc calc1 = new DataCalc();
int x = 1000;
int y = 500;
int result = calc1.add(x, y);
System.out.println(x + " + " + y + " = " + result);
// 계산기2
DataCalc calc2 = new DataCalc();
x = 2000;
y = 1000;
result = calc1.sub(x, y);
System.out.println(x + " + " + y + " = " + result);
// 계산기3
AbstractCalc calc3 = new DataCalc();
}
}
위의 코드는 결과가 중요한 것이 아니라 내부적으로 코드 의미가 중요하다.
더하기와 빼기, 곱하기, 나누기 기능은 뽑아낼 수 있는 공통된 기능이라고 할 수 있기 때문에
abstract 클래스로 만들 수 있다.
그리고 계산기3의 코드를 보면 계산기1과 계산기2가 다른 것을 볼 수 있다.
계산기3의 실체 형태는 DataCalc형이나 AbstractCalc형 변수에 집어 넣었다는 뜻이다.
이런 것을 업캐스팅이라고 하는데
자식 클래스를 부모 클래스로 승격하는 기능으로 자식 클래스가 부모 클래스처럼 동작하게 된다.
즉, 부모 클래스가 가진 기능만 쓸 수 있게 되는 것이다.
그래서 AbstractCalc에 있는 기능만 보이기 때문에
아래처럼 코드를 짠다면 calc3의 x, y, result는 보이지 않는다.
// 보이지 않음
calc3.x;
calc3.y;
calc3.result;
하지만 ((DataCalc) calc3)과 같이 강제로 형변환을 한다면
DataCalc형이 되기 때문에 calc3의 x, y, result은 보이게 된다.
추상 메소드
모든 실체들이 가지고 있는 메소드의 실행 내용이 동일하다면 추상 클래스에 메소드를 작성하면 된다.
하지만 메소드 선언만 동일하고 실행 내용은 실체 클래스마다 달라야 한다면 어떻게 할까?
이런 경우에 사용하는 것이 추상 메소드인데 추상 메소드는 abstract 키워드와 함께
메소드의 선언부만 있고 메소드 실행 내용인 중괄호 { }가 없는 메소드를 말한다.
추상 클래스에 추상 메소드를 선언해주고, 자식 클래스가 추상 메소드를 재정의(오버라이딩)해서
실행 내용을 작성해야 한다. 아래의 코드를 보자.
public abstract class Animal {
private String name;
Animal(String name){
this.name = name;
}
public void move() {
System.out.println("동물이 움직이고 있습니다.");
}
public abstract void eat(); // 추상 메소드
public abstract void sound(); // 추상 메소드
public String getName() {
return name;
}
// set 속성 함수를 사용하지 않는다.
// public void setName(String name) {
// this.name = name;
// }
}
public class Dog extends Animal{
int leg;
// set 속성 함수의 역할을 생성자가 한다.
// 단, 1번 이름이 부여되면 바꾸지 못한다. (set 속성 함수가 없기 때문에)
Dog(String name, int leg){
super(name); // 부모의 생성자 호출
this.leg = leg;
}
// 추상 메소드 재정의
@Override
public void eat() {
System.out.println("사료를 먹습니다.");
}
// 추상 메소드 재정의
@Override
public void sound() {
System.out.println("멍멍!");
}
}
public class Fish extends Animal{
Fish(String name){
super(name); // 부모의 생성자 호출
}
// 추상 메소드 재정의
@Override
public void eat() {
System.out.println("작은 물고기를 먹습니다.");
}
// 추상 메소드 재정의
@Override
public void sound() {
System.out.println("팔닥팔닥!");
}
}
public class AnimalApp {
public static void main(String[] args) {
Dog dog = new Dog("강아지", 4);
System.out.println(dog.getName() + "의 다리 갯수는 " + dog.leg+"개 입니다.");
dog.move();
dog.eat();
dog.sound();
System.out.println();
Fish fish = new Fish("물고기");
System.out.println("이름 : " + fish.getName());
fish.move();
fish.eat();
fish.sound();
}
}
// 결과
강아지의 다리 갯수는 4개 입니다.
동물이 움직이고 있습니다.
사료를 먹습니다.
멍멍!
이름 : 물고기
동물이 움직이고 있습니다.
작은 물고기를 먹습니다.
팔닥팔닥!
움직이고(move), 먹고(eat), 소리내는(sound) 공통된 기능을 가진
실체가 없는 동물(Animal)을 추상 클래스로 만들 수 있다.
그리고 모든 동물은 소리를 내지만 다른 소리를 내고, 동물마다 먹이 또한 다르다.
그래서 추상 메소드로 선언해 자식 클래스에서 추상 메소드를 재정의(오버라이딩)하면
동물마다 다른 소리를 낼 수 있고, 적절한 먹이를 먹을 수 있게 된다.
위의 코드를 조금 변형해보도록 하자.
public abstract class Animal {
private String name;
Animal(String name){
this.name = name;
}
public void move() {
System.out.println("동물이 움직이고 있습니다.");
}
// 변경된 부분
public void eat() {
System.out.println("동물이 먹이를 먹고 있습니다.");
}
public abstract void sound();
public String getName() {
return name;
}
}
public class Dog extends Animal{
int leg;
Dog(String name, int leg){
super(name);
this.leg = leg;
}
// 변경된 부분
@Override
public void eat() {
System.out.println("사료를 먹습니다.");
}
@Override
public void sound() {
System.out.println("멍멍!");
}
}
public class AnimalApp {
public static void main(String[] args) {
// 업캐스팅
Animal animal = new Dog("멍멍이", 4);
animal.eat();
}
}
// 결과
사료를 먹습니다.
변경된 부분은 Animal 클래스의 eat() 메소드를 추상 메소드에서 일반 메소드로 변경해주고,
Dog 클래스의 eat() 메소드를 수정해주었다.
그러고나서 실체는 Dog 클래스형이나 Animal 클래스형으로 업캐스팅해준 객체로 eat() 메소드를 출력해보자.
그 결과 오버라이딩에 의해 부모 클래스(Animal)의 메소드가 아닌
자식 클래스(Dog)의 메소드로 출력이 되는 것을 볼 수 있다.
그렇다면 Dog 클래스의 run() 메소드를 주석 처리해보자.
public class Dog extends Animal{
int leg;
Dog(String name, int leg){
super(name);
this.leg = leg;
}
// 변경된 부분
// @Override
// public void eat() {
// System.out.println("사료를 먹습니다.");
// }
@Override
public void sound() {
System.out.println("멍멍!");
}
}
// 결과
동물이 먹이를 먹고 있습니다.
부모 클래스에는 run() 메소드가 존재하나 자식 클래스에서 run() 메소드를 주석 처리함으로써
부모 클래스에만 run() 메소드가 존재하게 되었다. 그렇다면 다시 결과를 출력해보자.
이번엔 자식 클래스의 메소드가 없어지면서 부모 클래스의 run() 메소드의 결과가 출력되는 것을 볼 수 있다.
마지막으로 부모 클래스에 run() 메소드가 없는데 호출한다면 에러가 발생할 것이다.
그 이유는 잘 알다싶이 Animal 클래스에 run() 메소드가 없는데 run() 메소드를 호출했기 때문이다.
일반 메소드와 추상 메소드 차이
일반 메소드 | 추상 메소드 | |
형태 | public int add(int x, int y){retrun x + y;} | public abstract void sound(); |
abstract | X | O |
구현부 | O | X |
'국비 지원 > JAVA' 카테고리의 다른 글
[JAVA] 다중 상속과 추상 클래스와 인터페이스의 차이점 (0) | 2023.05.20 |
---|---|
[JAVA] 인터페이스 (+ 다운 캐스팅) (0) | 2023.05.16 |
[JAVA] 부모 생성자 호출 super() / 부모 메소드 호출 super (0) | 2023.05.15 |
MVC란 무엇일까? 또, DAO와 DTO, VO는 무엇일까? (0) | 2023.05.14 |
[JAVA] 게터(getter)와 세터(setter) (0) | 2023.05.14 |