티스토리 뷰
다형성이란
여러 가지 형태를 가질 수 있는 능력
한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현
즉, 조상클래스의 타입 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다.
부모 클래스의 레퍼런스 변수는 자식 클래스의 타입 변수를 가져올 수 있다.
그러나, 부모 클래스의 레퍼런스 변수는 부모 클래스 타입으로 되어있기 때문에 실제 인스턴스가 자식꺼라고 하여도 자식의 맴버 변수나 메서드에 접근할 수 없다.
다만, 부모 클래스의 맴버는 사용가능하다.
즉, 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 맴버의 개수가 달라진다.
그런데, 반대는 불가능하다. 자식 클래스 레퍼런스에 부모 클래스의 인스턴스를 넣을 수 없다.
- 이 경우에는 컴파일 에러가 된다.
참조변수의 형변환
참조변수도 형변환이 가능하다.
업 캐스팅 : 자손 타입의 참조변수를 조상 타입의 참조변수로 변환하는 것 ( 형변환 생략가능 -> 누군지 알기 때문)
다운 캐스팅 : 조상 타입의 참조변수를 자손 타입으로 변환하는 것 (형변환 생략불가 , 부모입장에서 자식을 모르기 때문)
참조 변수간의 형변환은 캐스트 연산자로 () 안에 타입을 써주면 된다.
Car car = null; FireEngine fe = new FireEngine(); car = fe; // car = (Car) fe; 로 형변환이 생략된 것이다. fe = (FireEngine)car; // 생략불가
주의 : 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
- 단지 참조변수의 형변환을 통해 참조하고 있는 인스턴스에서 사용할 수 있는 맴버의 범위(개수)를 조절할 뿐이다. 이는 다음의 문제와 헷갈려서는 안된다. 조상 인스턴스를 가진 조상 레퍼런스 변수를 자식 레퍼런스 변수로 변환하더라도 사용할 수 없다. 에러가 발생할 수 밖에 없다. 이유는 레퍼런스 변수의 타입만 바꾼것이지 인스턴스를 바꾼 것은 아니기 때문이다. 이 때에는 컴파일은 성공하지만 실행 시 ClassCastException이 발생한다. 자식 -> 부모의 형변환으로 메서드를 불러오는 것은 가능하지만 자식 레퍼런스 변수로 부모를 가져올 수 없다는 것은 인지하자
그래서 부모타입을 자식타입으로 캐스팅하는 것이 불가능하므로 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 좋다.
instanceof 로 인스턴스의 참조형 변수를 검사한 타입으로 변환할 수 있는 지 확인할 수 있다. 결과값은 true or false 이다. (인스턴스 타입과 레퍼런스 변수 타입은 다를 수 있다.)
void doWork(Car c){ if(c instanceof FireEngine){ FireEngine fe = (FireEngine) c; fe.water(); } }
인스턴스만 자식 클래스이기만 하면 소용없다. 그러면 부모의 레퍼런스 변수로 접근할 수 있는 영역이 한계가 있기 때문이다.
그래서 형변환을 하는 것이다.
만약 직접 인스턴스의 타입을 확인하고 싶다면 .getClass().getName() 을 하면 참조변수가 가리키고 있는 인스턴스 클래스 이름을 문자열로 반환한다.
참조변수와 인스턴스의 연결
- 만약 조상 클래스와 자손 클래스에 이름이 같은 맴버 변수가 있다면 누구를 호출할까??
- 레퍼런스 변수가 조상 타입이면 조상 클래스에 선언된 맴버 변수가 사용되고, 자손 타입의 참조 변수를 사용할 때는 자손 클래스에 선언된 맴버 변수가 사용된다.
- 만약 자손 클래스에서 부모 클래스의 데이터를 쓰고 싶다면 super.x 를 사용하고, 자손에서 자신의 데이터를 불러오고 싶다면 this.x를 사용하자
다형성을 이용하여 하나의 타입(부모의 타입)으로 자식들의 인스턴스를 담을 수 있어, 부모타입 배열이나 매개변수를 선언하여 값을 받아낼 수 있다.
인터페이스의 장점
- 개발시간을 단축
- 표준화가 가능
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
- 상속 관계가 아니어도 composite한 관계를 맺으면 되기 때문이다.
- 독립적인 프로그래밍이 가능하다.
- 선언과 구현을 분리하여 독립적으로 프로그래밍 구현가능, 이는 구현부의 변경이 다른 클래스에 영향을 주지 않는 다는 것이다.
인터페이스의 이해
- 클래스를 사용하는 쪽(User)와 클래스를 제공하는 쪽(Provider)이 있다.
- 메서드를 사용하는 쪽에서는 사용하려는 메서드의 선언부만 알면 된다. (내용은 몰라도 된다. information hiding)
디폴트 메서드와 static 메서드( jdk 1.8 이후)
static 메서드는 인스턴스 관계가 없는 독립적인 메서드이다.
public으로 적어야 하며, 생략가능하고 구현을 적어야 한다.
만약 인터페이스가 변한다면 어떻게 될까?? 구현을 하는 모든 클래스들은 새로 추가된 메서드에 대한 구현을 해야할 것이다.
이에 따라, 자바 개발자들은 구조에 영향을 안주고 abstract 추상 메서드가 아닌 메서드를 추가할 수 있도록 하였다.
이것이 디폴트 메서드이다.디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하며, 추상 메서드가 아니기 때문에 디폴트 메서드가 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
앞에 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통이 있어야 한다. 접근제어자는 public이다.
```java
interface MyInstance {
void method();
default void newMethod(){}
}
```
- 다만, 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생한다. 이를 해결하는 규칙은 다음과 같다.
1. 여러 인터페이스의 디폴트 메서드 간의 충돌
- 디폴트 메서드를 오버라이딩 해야한다.
2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
\-> 그냥 필요한 기능으로 오버라이딩해놓으면 된다.
- 참고 코드
```java
class DefaultMethodTest{
public static void main(String[] args){
Child c = new Child();
c.method1();
c.method2();
MyInterface.staticMethod();
MyInterface2.staticMethod();
}
}
class Child extends Parent implements MyInterface, MyInterface2{
public void method1(){
System.out.println("method1() in Child"); // 오버라이딩
}
}
class Parent{
public void method2(){
System.out.println("method2 in parent");
}
}
interface MyInterface{
default void method1(){
System.out.println("method1 () in MyInterface");
}
default void method2(){
System.out.println("method2() in MyInterface");
}
static void staticMethod(){
System.out.println("staticMethod() in MyInstance");
}
}
interface MyInterface2{
default void method1(){
System.out.println("method() in MyInterface2");
}
static void staticMethod(){
System.out.println("staticMethod() in MyInterface2");
}
}
```
'노답 스터디 > JAVA' 카테고리의 다른 글
Java 문법 정리 10일차 - inner class (0) | 2021.07.11 |
---|---|
Java 문법 정리 9일차 - interface (0) | 2021.07.11 |
Java 문법 정리 7일차 - 추상 메서드 (0) | 2021.07.11 |
Java 문법 정리 6일차 - 제어자 (0) | 2021.07.11 |
Java 문법 정리 5일차 - Import문 (0) | 2021.07.10 |