한 타입의 참조 변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것이다.
반대의 경우는 왜 안돼냐?
실제 참조하고 있는 인스턴스의 멤버를 기준으로 참조 변수의 타입의 멤버가 실제 인스턴스의 멤버 수보다 작은 것은 실제 사용할 수 있는 기능을 줄이는 것이라 허용되지만, 그 반대의 경우는 참조하고 잇는 인스턴스에 실제로 구현된 기능이 없어 사용이 불가능하다.
자바의 다형성을 이해하려면 참조 변수의 타입 변환에 대한 내용을 꼭 알고 있어야한다.
참조 변수의 타입 변환
사용할 수 있는 멤버의 개수를 조절하는 것을 의미한다.
하위 클래스 타입에서 상위 클래스 타입으로의 타입 변환을 업캐스팅,
상위 클래스에서 하위 클래스 타입으로 변환을 다운캐스팅 이라한다.
타입 변환을 위해선 아래 조건을 충족해야한다.
만약 업캐스팅이 (또는 다운캐스팅이) 가능한지 모르겠다면 instance of 연산자를 사용해서 캐스팅이 가능한지 여부를 boolean 타입으로 확인할 수 있다.
리턴 값이 true면 타입변환 가능, false면 타입 변환 불가능하다.
//예시
System.out.println(animal instanceof Object); //true
System.out.println(animal instanceof Animal); //true
System.out.println(animal instanceof Bat); //false
추상화란 객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 말한다.
기존 클래스들의 공통적인 요소들을 추려서 상위 클래스를 만들어내는 것이라고 생각하면된다.
설계를 할때 공통적인 속성과 기능을 정의하고 하위 클래스를 생성하는 하향식 설계, 반대로 하위 클래스들의 공통성을 모아 상위 클래스를 정의하는 상향식 설계 모두 크게 상관 없다.
자바에서 추상화를 구현할 때 주로 추상 클래스 와 인터페이스 라는 문법 요소를 사용한다.
1) 추상 클래스
추상 클래스란 메서드 시그니처만 있고 바디가 선언되어있지 않는 추상 메서드를 포함하는 것을 말한다.
미완성 설계도라고 생각하면 된다.
추상 메서드는 메서드의 시그니처만 있고 바디가 없는 메서드를 의미하는데, 이때 abstract 키워드를 메서드 이름 앞에 써서 이 메서드가 추상 메서드임을 표시한다.
abstract class AbstractExample { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
abstract void start(); // 메서드 바디가 없는 추상메서드
}
abstract 제어자는 주로 클래스와 메서드를 형용하는 키워드로 사용된다.
메서드 앞에 붙으면 '추상 메서드', 클래스 앞에 붙으면 '추상 클래스'라고 부른다.
어떤 클래스에 추상 메서드가 포함되어있다면 해당 클래스는 자동으로 추상 클래스가 된다.
그렇기 때문에 메서드 바디가 완성되기 전까지 이를 기반으로한 객체 생성이 불가능하다.
이런 추상 클래스(미완성 설계도)를 왜 만드냐?
내 식대로 정리하면, 상위 클래스를 추상 클래스로 써서 선언부만 작성하고, 구체적인건 상속받는 하위 클래스에서 구현하게하면 나중에 내용이 변해도 유연하게 대응이 가능하다.
상속받는 하위 클래스에서 오버라이딩을 해서 그때그때 필요한 메서드 구현을 가능하게 한다는 것이다.
상층부는 정말 공통적인 속성이나 기능들만, 하위에서 그것들을 하나하나 구체화 시킨다. 이게 추상화라고 이해했다.
2) 인터페이스
인터페이스는 추상 클래스보다 더 높은 추상성을 가지는 밑그림이라고 생각하면 된다.
인터페이스는 기본적으로 추상 메서드와 상수만을 멤버로 가질 수 있기 때문이다.
인터페이스 -> 추상 메서드의 집합.
인터페이스의 기본 구조
클래스를 작성하는 것과 비슷하지만 class 가 아닌 interface 키워드를 쓴다.
또한 내부의 모든 필드가 public static final로 정의되고, static과 default메서드 이외의 모든 메서드가 public abstract 로 정의된다.
//작성 예시
public interface InterfaceEx {
public static final int rock = 1; // 인터페이스 인스턴스 변수 정의
final int scissors = 2; // public static 생략
static int paper = 3; // public & final 생략
public abstract String getPlayingNum();
void call() //public abstract 생략
}
위 요소들은 일부분 또는 전부 생략해도 된다. -> 컴파일러가 자동으로 추가해주기 때문에.
위에서 정의한 애들을 구현하려면, extends키워드처럼 implements 키워드를 사용해서 작성하면 된다.
여기서 추상 클래스처럼 인터페이스도 그 자체로 인스턴스를 생성못하고 메서드 바디를 정의하는 클래스를 따로 만들어야하는것을 기억하자.
//예시
class 클래스명 implements 인터페이스명 {
... // 인터페이스에 정의된 모든 추상메서드 구현
}
특정 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 추상메서드를 구현해야한다.
어떤 클래스가 어떤 인터페이스를 구현한다는 것은 그 인터페이스가 가진 모든 추상 메서드들을 해당 클래스 내에서 오버라이딩하여 바디를 환성한다는 의미를 가진다.
추가로, 인터페이스는 다중적 구현이 가능하다.
-> 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있다. but 인터페이스는 인터페이스로부터만 상속 가능, object클래스같은 최고 조상이 없음.
//다중적 구현 예시
class ExampleClass implements Example1, Example2, Example3 {
...
}
인터페이스는 왜 다중 구현이 가능할까?
클래스는 부모 클래스에 동일한 이름의 필드나 메서드가 존재하면 충돌이 발생한다.
하지만 인터페이스는 애초에 미완성된 멤버를 가지고 있기 때문에 충돌이 발생하지 않는다. 그래서 안전하게 다중 구현이 가능하다.
인터페이스는 왜 사용할까?
인터페이스는 역할과 구현을 분리시킨다. 그래서 복작한 기능의 구현이나 코드 교체 또는 변경과 상관없이 해당 기능을 사용할 수 있다.
코드 변경의 번거로움을 최소화하고 손쉽게 해당 기능을 사용할 수 있게 한다는 것이다.
이렇게되면 선언과 구현을 분리시켜 개발시간을 단축시킬 수 있고, 독립적인 프로그래밍을 통해 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화 할 수 있기 때문에 사용한다.
머리속으로는 이해되었는데.. 이 구조를 내가 직접 짜라고하면 못할거같다.. 연습만이 살길이겠지....