1. 다형성
(1) 다형성(Polymophism)
- 여러 형태를 가지는 성질이란 뜻으로, 한 가지 타입이 여러가지 형태의 인스턴스를 가질 수 있다는 의미
- 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질
- 부모 타입 변수에는 모든 자식 인스턴스들이 대입될 수 있음
// 1) 지금까지의 객체 생성 방식
A obj = new A();
/*
2) 부모 클래스 타입의 참조변수로 자식 클래스 타입의 객체를 참조할 때
참조변수 obj 하나로 A타입의 인스턴스를 참조할 수 도, B타입의 인스턴스를 참조할 수도 있음.
*/
A obj = new B(); // 클래스 B가 A를 상속할 때
class A{
void methodA(){
System.out.println("methodA");
}
}
class B extends A{
void methodB(){
System.out.println("methodB");
}
void methodA(){
System.out.println("methodA-2");
}
}
public class Test{
public static void main(String[] args){
A obj = new B();
obj.methodA();
B obj2 = new B();
obj2.methodA();
}
}
(2) 참조변수와 인스턴스 간의 관계
- 부모 클래스 타입의 참조변수로 자식 인스턴스 참조는 가능, 그 반대는 에러
/*
<선제 조건>
Parent 클래스의 메소드는 A,B,C
Child 클래스는 Parent 클래스를 상속 받고 메소드 D를 추가
*/
// Child 클래스가 부모의 멤버 A,B,C를 상속
// D 라는 참조 변수를 호출할 수 없음!
Child i = new Parent(); // 부모의 객체 생성
// Child 클래스 내부에 D라는 멤버를 선언
Parents p = new Child(); // 자식의 객체 생성
(3) 부모, 자식간의 타입 변환
- 자동 타입 변환(Promotion): 프로그램 실행 자동으로 타입 변환이 일어나는 것
- 부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근 가능
- 예외로, 메소드가 자식 클래스에서 오버라이딩 되었다면 자식 클래스의 메소드가 대신 호출됨
class Animal{
}
class Cat extends Animal{
}
Cat cat = new Cat();
Aniaml animal = cat;
// cat == animal -> true
- 강제 타입 변환(Casting) : 프로그래머가 강제로 타입 변환을 시키는 것
- 업캐스팅(Upcasting) : 더 높은 객체로 변환시키는 것(자식에서 부모로)
- 다운 캐스팅(Dwoncasting) : 더 낮은 객체로 변환시키는 거(부모에서 자식으로)
- 다운 캐스팅이 가능할 때는, 객체가 자식에서 부모로 업 캐스팅 되어있을 경우만 가능(되돌림만 가능)
- 따라서, 해당 객체가 다운 캐스팅이 가능한 지 instanceof로 확인 후 캐스팅 시도
- instanceof : 객체의 타입을 확인하는 명령어
자식 클래스 변수 = (자식 클래스) 부모 클래스 타입; // 다운 캐스팅
부모 클래스 타입 = 자식 클래스 타입; // 업 캐스팅
객체 instanceof 타입 // 객체가 해당 타입인지 boolean으로 반환
Parent parent = new Parent();
Child child = (Child) parent; // 불가능!!!!
boolean result = child instanceof Parent; // true
2. 추상 메소드와 추상 클래스
(1) 추상 클래스
- 추상은 실체 간에 공통되는 특성을 추출한 것을 의미
- 클래스들의 공통적인 특성을 추출해서 선언한 클래스
- 추상 메소드를 멤버로 가지는 클래스
- 추상 메소드를 하나라도 포함하는 클래스를 의미
- 추상 클래스를 상속받은 자식 클래스는 반드시 추상 메소드를 구현해야 함
- 추상 메소드와 같이 abstract를 붙여 선언
abstract class 클래스명{
abstract void 추상 메소드명();
.....
}
(2) 추상 메소드
- 선언부만 정의하고 구체적인 내용은 비워 놓은 메소드
- 추상 메소드를 하위 클래스에서 상속받아 구현
- '추상적인'의 뜻인 'abstract'를 메소드 앞에 붙여 사용
abstract 반환값 메소드명();
(3) 추상 클래스의 사용
- 실체 클래스들의 공통된 필드와 메소드의 이름을 통일하기 쉬움
- 실체 클래스를 작성할 시 시간을 절약할 수 있음
- 추상 클래스를 상속받은 클래스에게 문법적인 제한을 붐으로서 추상 메소드를 자신의 클래스에 맞게 구현하라는 강제성을 줌
abstract class Cellphone{
abstract void methodA();
}
class MyPhone extends Cellphone{
void methodA() {
}
}
// 추상 클래스 정의
abstract class Pokemon{
String name;
abstract void attack();
abstract void sound();
public String getName() {
return this.name;
}
}
// 추상 클래스를 상속받는 Pikachu클래스
class Pikachu extends Pokemon{
Pikachu(){
this.name = "피카츄";
}
@Override
void attack(){
System.out.println("100만 볼트");
}
@Override
void sound(){
System.out.println(피카 피카!");
}
}
3. 인터페이스
(1) 인터페이스(Interface)
- inter(사이의) + face(마주하다)의 합성어로, 물체들 사이에서 상호작용을 하기 위한 매개 역할을 하는 것을 의미
- 인터페이스를 바탕으로 클래스를 작성(클래스 : 붕어빵 기계, 인터페이스 : 붕어빵을 만드는 재료나 제적법을 적어놓은 종이)
- 인터페이스를 사용하면 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드를 구현하기만 하면 된다는 장점이 있음
- 변수에는 public static final이 메소드에는 public abstact를 작성하지 않아도 컴파일러가 자동으로 추가
- '상속'이 아닌 '구현'이라도 표현
- 자식 클래스는 implements를 사용하여 인터페이스를 구현함
(2) 인터페이스의 구현과 상속
- 인터페이스를 구현하는 자식 클래스는 상속과 구현이 동시에 가능함
- 인터페이스 간에도 상속이 가능 => 다수의 인터페이스 구현 가능 => 다중 상속이 가능
interface 인터페이스명{
public static final 자료형 = 값;
public abstract 반환타입 메소드명(매개변수);
default 타입 메소드명(매개변수 ...);
static 타입 메소드명(매개변수 ...)
}
interface A {
int a = 4;
void methodA();
void methodB();
}
// A를 구현하는 클래스 B
class B implements A{
public void method A(){
// methodA 구현
}
public void methodB(){
// methodB 구현
}
}
// C를 상속 받는 동시에 A를 구현하는 클래스 B
class B extend C implements A{
/*
interface A의 멤버가 존재
부모 클래스 C의 멤버가 존재
*/
}
interface A{
void methodA();
}
interfaceB{
void methodB();
}
interface C extends A, B{
// A와 B를 합친 종합 적인 기능을 다루는 인터페이스
}
(3) 인터페이스와 다형성
- 특정 인터페이스를 구현한 인스턴스는 해당 인터페이스 타입의 참조변수로 참조가 가능
- 하나의 객체를 여러가지 타입으로 참조 가능
인터페이스명 참조변수명 =new 클래스명();
interface Camera{
void photo();
}
interface Call{
void calling();
}
interface Clock{
void clock();
}
class MyCellPhone implements Camera, Call, Memo, Clock{
@Override
public void clock() {}
@Override
public void write() {}
@Override
public void calling() {}
@Override
public void photo() {}
}
class PhoneUser{
void call(Call c) {
System.out.println("전화를 걸었습니다.");
}
}
public class Test{
public static void main(String[] args){
// MyCellPhone 클래스가 Camera, Call, Memo, Clock 4개의 인터페이스를 구현
MyCellPhone phone1 = new MyCellPhone();
// MyCellPhone의 인스턴스는 자신 이외에 추가로 4개의 인터페이스 타입을 참조
Camera phone2 = new MyCellPhone();
Call phone3 = new MyCellPhone();
Memo phone4 = new MyCellPhone();
Clock phone5 = new MyCellPhone();
PhoneUser user1 = new PhoneUser();
user1.call(phone3);
user1.call(phone1);
}
}
4. 정리
(1) 추상 클래스를 사용하는 경우
- 관련된 클래스 사이에서 코드를 공유하고 싶을 때
- 공통적인 필드나 메소드가 많은 경우, 또는 public 이외의 접근 지정자를 사용해야 하는 경우
- 정적이 아닌 필드나 상수가 아닌 필드를 선언하기를 원할 때
- 일반적인 필드도 선언할 수 있으며, 일반적인 메소드도 정의 가능
(2) 인터페이스를 사용하는 경우
- 관련 없는 클래스들이 인터페이스를 구현하기를 원할 때
- 특정한 자료형의 동작을 지정하고 싶지만 누가 구현하든지 신경 쓸 필요가 없을 때
- 다중 상속이 필요할 때
- 모든 메소드가 public, abstract가 되며, 여러 개의 인터페이스를 상속 받아 동시에 구현이 가능