[자바 기초] 상속과 다형성 Inheritance & Polymorphism
2021, Jun 29
상속
상속의 정의
- 상속(inheritance)은 기존에 존재하는 클래스로부터 데이터와 코드를 이어받고, 필요한 기능을 추가할 수 있는 기법
- 검증된 소프트웨어를 재사용할 수 있어 신뢰성 있는 소프트웨어를 손쉽게 개발, 유지보수 가능
상속의 형식
class Child extends Parent { ... }
- 자바에서는 extends 키워드를 이용하여 상속을 나타냄
- 상속하는 클래스를 부모 클래스(슈퍼 클래스) 라고 함
- 상속 받는 클래스를 자식 클래스(서브 클래스) 라고 함
- 자식 클래스는 부모 클래스의 public 멤버, protected 멤버, 디폴트 멤버(부모 클래스가 같은 패키지 내에 존재하는 경우)를 상속 받음
- private 멤버는 상속되지 않음
상속의 필요성
- 직접 작성할 필요 없이 이미 존재하는 클래스의 필드와 메소드를 재사용 가능
- 상속을 사용하여 공통되는 부분을 하나로 정의하고 중복되는 코드를 줄일 수 있음
상속과 생성자
- 생성자의 호출 순서는 (부모 클래스의 생성자) → (자식 클래스의 생성자) 순
- 자식 클래스 객체는 부모 클래스에서 상속된 부분을 초기화하기 위해 부모 클래스의 생성자를 호출
- 부모 클래스의 생성자 호출이 끝나면 자식 클래스가 추가한 부분을 초기화 하기 위해 자식 클래스의 생성자를 실행
- 명시적 호출
- 자식 클래스의 생성자에 명시적으로 부모 클래스의 생성자를 호출
- super 키워드를 사용
class Shape {
public Shape() {
System.out.println("Shape 생성자() ");
}
}
class Rectangle extends Shape {
public Rectangle() {
super();
System.out.println("Rectangle 생성자() ";
}
}
- 묵시적 호출
- 자바에서는 명시적으로 슈퍼 클래스의 생성자를 호출하지 않아도 서브 클래스의 객체가 생성될 때 자동으로 수퍼 클래스의 기본 생성자를 호출
- 묵시적으로 부모 클래스의 생성자를 호출하는 경우 부모 클래스의 기본 생성자가 반드시 정의되어 있어야 함
메소드 재정의
- 메소드 재정의(Overriding)의 정의
- 부모 클래스로부터 상속 받은 메소드를 재정의 할 때 사용
- 메소드의 이름과 매개변수, 반환형은 동일
- 완벽하게 일치하지 않는 경우 메소드 중복 정의(Overloading)로 처리
- 재정의 된 메소드 이름 앞에는 @Override 어노테이션 사용
- 메소드 오버라이딩을 하는 경우 보통 super 키워드를 통해 부모 클래스의 메소드를 호출한 후 자식 클래스의 내용에 맞게 내용을 추가함
class Parent {
public void print() {
System.out.println("부모 클래스의 print() 메소드");
}
}
class Child extends Parent {
@Override
public void print() {
super.print();
System.out.println("자식 클래스의 print() 메소드");
}
}
다형성
상속과 다형성
- 자식 클래스 안에는 부모 클래스 부분이 있기 때문에 부모 클래스의 객체로 취급할 수 있음
- 부모 클래스 참조 변수로 자식 클래스 객체를 참조 가능
- 이것을 상향 형변환(up-casting)이라고 함
- 참조 변수가 가리키는 실제 객체에 따라 메소드가 자동적으로 선택
- 부모 클래스의 참조 변수를 통하여 메소드를 호출하면 자식 클래스에 정의된 내용에 따라 적절한 메소드를 자동으로 호출
- 이것을 동적 바인딩(dynamic binding) 또는 가상 메소드 호출(virtual method invocation) 이라고 함
class Shape {
protected int x,y;
public void draw() { System.out.println("Shape Draw"); }
}
class Rectangle extends Shape {
private int width, height;
@Override
public void draw() { System.out.println("Rectangle Draw"); }
}
class Triangle extends Shape {
private int base, height
@Override
public void draw() { System.out.println("Triangle Draw"); }
}
class Circle extends Shape {
private int radius;
@Override
public void draw() { System.out.println("Circle Draw"); }
}
public class ShapeTest {
public static void main(String[] args){
Shape[] arrOfShapes = new Shape[3];
arrOfShapes[0] = new Rectangle();
arrOfShapes[1] = new Triangle();
arrOfShapes[2] = new Circle();
//부모 클래스 참조 변수로 자식 클래스 객체를 참조 (upcasting)
for(int i=0; i<arrOfShapes.length; i++)
arrOfShapes[i].draw()
//실제 객체에 따라 서로 다른 draw()가 호출 (동적 바인딩)
}
}
instanceof
- 참조 변수가 가리키는 객체의 실제 타입을 알고 싶은 경우 instanceof 연산자 사용
- instanceof 연산자를 통한 연산은 참(true) 또는 거짓(false)을 반환
추상 클래스
- 추상 클래스(abstract class)는 완전하게 구현되지 않은 메소드를 포함하는 클래스
- 메소드가 미완성 되어 있어 추상 클래스는 객체 생성이 불가능
- 추상 클래스는 하나 이상의 추상 메소드를 갖고 있어야 함
-
추상 클래스를 상속받는 자식 클래스는 반드시 추상 메소드를 재정의
public abstract class Animal { public abstract void move(); } public class Lion extends Animal { @Override public void move() { System.out.println("사자의 move() 메소드입니다. "); } }
인터페이스
- 인터페이스 정의
- 인터페이스(interface)는 클래스 간의 상호작용을 기술한 일종의 규격
-
인터페이스를 정의할 때는 interface 키워드를 사용
interface 인터페이스_이름 { 반환형 추상메소드1(...); 반환명 추상메소드2(...); ... }
- 인터페이스에서 정의되는 메소드들은 public과 abstract를 사용하지 않아도 자동으로 public 및 abstract로 취급
- 인터페이스 구현
- 인터페이스만으로는 객체를 생성할 수 없음
- 인터페이스는 다른 클래스에 의하여 구현(implement) 가능
- 클래스가 인터페이스를 구현하기 위해서는 implements 키워드 사용
interface Drawable{ void draw(); } class Circle implements Drawable { int radius; public void draw() { System.out.println("Circle Draw"); } }
- 다른 클래스를 상속하는 동시에 인터페이스를 구현할 수 있음
- 자바에서는 클래스의 다중 상속은 금지되어 있으나 인터페이스를 이용한 다중 상속 효과를 낼 수 있음
- Java 8 에서는 인터페이스 안에 정적 메소드와 디폴트 메소드를 제공할 수 있음