자바에서는 abstract
키워드를 클래스명과 메서드 명 앞에 붙임으로서 컴파일러에게 추상 클래스와 추상 메서드임을 알려주게 된다.
추상 메서드는 작동 로직은 없고 이름만 껍데기만 있는 메서드라고 보면 된다. 즉, 메서드의 선언부만 작성하고 구현부는 미완성인 채로 남겨둔 메소드 인 것이다. (메소드의 구현부인 중괄호가 없는 형태)
보통 문법적인 측면으로 하나 이상의 추상 메소드를 포함하는 클래스를 가리켜 추상 클래스라고 정의 하기도 한다.
추상 클래스 안에 메서드를 미완성으로 남겨놓는 이유는 추상 클래스를 상속받는 자식 클래스의 주제에 따라서 상속 받는 메서드의 내용이 달라질 수 있기 때문이다.
부모(추상) 클래스에서 메서드를 선언부만을 작성하고, 실제 내용은 상속받는 클래스에서 구현하도록 하기 위해 일부러 비워두는 개념이라고 보면 된다.
따라서 추상 클래스를 상속받는 자식 클래스는 부모의 추상 메서드를 상황에 맞게 적절히 재정의 하여 구현해주어야 비로소 사용이 가능해진다.
즉, 클래스의 선언부에 abstract
키워드가 있다는 말은 안에 추상메서드(abstract method)가 있으니 상속을 통해서 구현해주라는 지침이기도 하다.
// 추상 클래스
abstract class Pet {
abstract public void walk(); // 추상 메소드
abstract public void eat(); // 추상 메소드
public int health; // 인스턴스 필드
public void run() { // 인스턴스 메소드
System.out.println("run run");
}
}
class Dog extends Pet {
// 상속 받은 부모(추상) 메소드를 직접 구현
public void walk() {
System.out.println("Dog walk");
}
public void eat() {
System.out.println("Dog eat");
}
}
public class main {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(); // 부모(추상) 클래스로 부터 상속받은 추상 메소드를 직접 구현한 메소드를 실행
d.walk();
d.run(); // 부모(추상) 클래스의 인스턴스 메소드 실행
}
}
추상 클래스는 클래스의 일종이라고 하지만 new
생성자를 통해 인스턴스 객체로 직접 만들 수 없다. 왜냐하면 추상클래스는 상속 구조에서 부모 클래스를 나타내는 역할로만 이용되기 때문이다.
//틀린 코드
abstract class Animal{
}
Animal a = new Animal();
//에러!! -> 추상 클래스는 인스턴스를 직접 바로 생성할 수 없음
//옳은 코드
abstract class Animal {
}
class Cat extends Animal { // 추상 클래스 상속
}
class Dog extends Animal {
}
public class Main {
public static void main(String[] args) {
// 추상 클래스를 상속한 자식 클래스를 객체로 초기화
Cat c = new Cat();
Dog d = new Dog();
}
}
그렇다고 추상 클래스의 생성자를 전혀 이용 못하는 것은 아니다. 직접적인 인스턴스화가 불가능 하다 뿐이지, super()
메소드를 이용해 추상 클래스 생성자 호출이 가능하다.
// 추상 클래스
abstract class Shape {
public String type;
// 추상 클래스 생성자
public Shape(String type) {
this.type = type;
}
// 추상 메서드
public abstract void draw();
}
class Figure extends Shape {
public String name;
public Figure(String type1, String type2) {
super(type1); // 부모 추상 클래스 생성자 호출
name = type2;
}
@Override
public void draw() { ... } // 추상 메서드 구현
}
public class main {
public static void main(String[] args) {
Figure f = new Figure("polygon", "square");
f.name; // "square"
f.type; // "polygon" - 부모(추상) 클래스의 멤버를 추상 클래스 생성자를 호출하는 super()을 통해 초기화
}
}
추상 클래스를 상속한 자식 클래스를 new
생성자로 객체를 초기화 할 때, 자식 클래스 생성자 메소드 내에서 가장 먼저 부모 클래스인 추상 클래스의 생성자가 실행되게 된다.
그래서 만일 위와 같이 부모 추상 클래스 생성자 실행에 있어 인자를 주어 제어를 하고 싶다면, 자식 클래스 생성자 메서드 내에서 super()
부모 생성자 호출 메서드를 통해 가능하다.
public static final
과 public abstract
제어자는 생략이 가능하다.interface 인터페이스이름{
public static final 타입 상수이름 = 값;
public abstract 타입 메서드이름(매개변수목록);
}
====
interface TV{
int maxVolume = 10; //public static final 생략 가능
int minVolume = 10;
void turnOn(); // public abstract 생략 가능
void turnOff();
void changeVolume(int volume);
void changeChannel(int channel);
}
인터페이스도 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상 클래스가 상속을 통해 완성되는 것처럼 인터페이스도 구현부를 만들어주는 클래스에 구현(상속) 되어야 한다.
해당 클래스에 인터페이스를 구현하고 싶다면, implements
키워드를 쓴 후에 인터페이스를 나열하면 된다.
인터페이스를 상속 받았으면, 자식 클래스에서 인터페이스가 포함하고 있는 추상 메소드를 구체적으로 구현해준다.
인터페이스의 가장 큰 특징은 여러개를 다중 구현(다중 상속)이 가능하다는 것이다.
자식 클래스에 클래스 상속(extends)와 인터페이스 구현(implements)는 동시에 가능하다.
interface Animal {
public abstract void cry();
}
interface Pet {
public abstract void play();
}
class Tail {
// ...
}
class Cat extends Tail implements Animal, Pet { // 클래스와 인터페이스를 동시에 상속
public void cry() {
System.out.println("냐옹냐옹!");
}
public void play() {
System.out.println("쥐 잡기 놀이하자~!");
}
}
인터페이스도 따지고 보면 상속이지만
extends
키워드 대신implements
라는 '구현'이라는 키워드를 사용하는 이유는, 상속은 클래스간의 부모 - 자식 관계를 연관 시키는데 의미가 중점이 된다면, 구현은 클래스를 확장시켜 다양히 이용하는데 중점이 되기 때문이다.
인터페이스를 구현받고 추상 메서드를 구체적으로 구현할 때 접근제어자 설정에 주의해야 한다.
기본적으로 메서드를 오버라이딩(overriding) 할 때는 부모의 메서드보다 넓은 범위의 접근제어자를 지정해야 한다는 규칙이 존재한다. 따라서 인터페이스의 추상 메소드는 기본적으로public abstract
가 생략된 상태이기 때문에 반드시 자식 클래스의 메서드 구현부에서는 제어자를public
으로 설정해 주어야 한다.
extends
를 통해 인터페이스를 상속하면 된다.static
이기 때문에 구현체를 따라가지 않게 된다.(독립상수)자바는 클래스 상속에 대해서 다중상속을 허용하지 않지만, 인터페이스에 한해서는 다중상속을 허용한다.
interface Changeable{
//채널을 바꾸는 기능의 메서드
void change();
}
interface Powerable{
//전원을 껐다 켰다 하는 메서드
void power(boolean b);
}
//채널 기능과 전원 기능을 가진 인터페이스들을 하나의 인터페이스로 통합 상속
interface Controable extends Changeable, Powerable {
//인터페이스끼리 다중 상속하면 그대로 추상 멤버들을 물려받음
}
//클래스에 통합된 인터페이스를 그대로 상속
class MyObject implements Controlable{
public void change(){
System.out.println("채널의 기능을 바꾸는 메서드");
}
public void power(boolean b){
System.out.println("전원을 껐다켰다 하는 메서드");
}
}
public class Main {
public static void main(String[] args) {
// 인터페이스 다형성 (인터페이스를 타입으로 취급해서 업캐스팅 가능)
Controlable[] o = { new MyObject(), new MyObject() };
o[0].change();
o[0].power(true);
// 각각 단일 인터페이스로도 타입으로 사용이 가능하다. (그러나 지니고 있는 추상 메서드만 사용이 가능하다)
Changeable inter1 = new Changeable();
inter1.change();
Powerable inter2 = new Powerable();
inter2.power(true);
}
}
클래스 상속일 경우 클래스 필드 멤버끼리 상속되어 덮어 씌워지지만, 인터페이스 필드들은 모두 public static final
이기에, 서로 상속을 해도 독립적으로 운용된다.
public abstract
로 정의 (default 메소드 제외)public static final
상수static
, default
, private
제어자를 붙여 클래스 같이 구체적인 메서드를 가질 수 있음다형성이란 하나의 객체가 여러가지 타입을 가질 수 있는 것을 의미한다.
자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로서 자식 클래스 타입의 인스턴스를 참조할수 있도록 하여 구현하고 있다.\
자바에서는 다형성을 위해 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하고있다.
이때 참조변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 같거나 적어야 참조가 가능하다.
//예제
class Parent { ... }
class Child extends Parent { ... }
...
Parent pa = new Parent(); // 허용
Child ch = new Child(); // 허용
Parent pc = new Child(); // 허용
Child cp = new Parent(); // 오류 발생.
특정타입의 참조변수로는 같은 타입의 인스턴스 참조 가능
-> 참조변수가 사용할 수 있는 멤버의 개수 = 실제 인스턴스 멤버 개수
부모 클래스 타입의 참조 변수로도 자식 클래스 타입의 인스턴스 참조 가능
-> 참조변수 사용할 수 있는 멤버의 개수 < 실제 인스턴스 멤버 개수
자식 클래스 타입의 참조 변수로는 부모 클래스 타입의 인스턴스를 참조 X
-> 참조변수가 사용할 수 있는 멤버의 개수 > 실제 인스턴스 멤버 개수
클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없으므로, 항상
자식 클래스 멤버개수 >= 부모 클래스 멤버 개수 이다.
참조변수는 다음과 같은 조건에 따라 타입변환이 가능하다.
(변환할 타입의 클래스 이름) 변환할참조변수
//예제
class Parent { ... }
class Child extends Parent { ... }
class Brother extends Parent { ... }
...
Parent pa01 = null;
Child ch = new Child();
Parent pa02 = new Parent();
Brother br = null;
pa01 = ch; // pa01 = (Parent)ch; 와 같으며, 타입 변환을 생략할 수 있음.
br = (Brother)pa02; // 타입 변환을 생략할 수 없음.
br = (Brother)ch; // 직접적인 상속 관계가 아니므로, 오류 발생.
이러한 다형성으로 인해 런타임에 참조 변수가 실제로 참조하고 있는 인스턴스의 타입을 확인할 필요성이 생긴다. 자바에서는 instanceof
라는 연산자를 제공하여, 참조변수가 참조하고 있는 인스턴스의 실제 타입을 확인할 수 있도록 해준다.
참조변수 instanceof 클래스이름
왼쪽에 전달된 참조 변수가 실제로 참조하고 있는 인스턴스의 타입이 오른쪽에 전달된 클래스 타입이면 true
를 반환하고, 아니면 false
를 반환한다.
만약에 참조 변수가 null
을 가리키고 있으면 false
를 반환한다.
// 참조변수가 실제로 가지키고 있는 인스턴스 타입을
// instanceof 연산자로 확인하는 예제
class Parent { }
class Child extends Parent { }
class Brother extends Parent { }
public class Polymorphism01 {
public static void main(String[] args) {
Parent p = new Parent();
System.out.println(p instanceof Object); // true
System.out.println(p instanceof Parent); // true
System.out.println(p instanceof Child); // false
System.out.println();
Parent c = new Child();
System.out.println(c instanceof Object); // true
System.out.println(c instanceof Parent); // true
System.out.println(c instanceof Child); // true
}
}
//실행결과
true
true
false
true
true
true
출처
https://www.tcpschool.com/java/java_polymorphism_concept
https://www.tcpschool.com/java/java_inheritance_overriding
https://inpa.tistory.com/객체지향 클래스문법
https://inpa.tistory.com/추상클래스
https://inpa.tistory.com/인터페이스vs 추상클래스