Chapter 7 객체지향 프로그래밍2
상속
새로운 클래스를 정의할 때 이미 구현된 클래스를 상속받아서 속성이나 기능이 확장되는 클래스를 구현하는 것 (다중상속은 불가)
-상속에서의 메모리 상태: 상위 클래스의 인스턴스가 먼저 생성이 되고, 하위 클래스의 인스턴스가 생성됨
-
상속은 언제 사용할까?
1) IS-A 관계(inheritance): 일반적인 개념과 구체적인 개념 / 상위와 하위 클래스
2) HAS-A 관계(composition): 한 클래스가 다른 클래스를 소유한 관계 -
하위 클래스가 생성되는 과정
1) 하위 클래스가 생성될 때 상위 클래스가 먼저 생성됨
2) 상위 클래스의 생성자가 먼저 호출되고 하위 클래스의 생성자가 호출됨
-> 하위 클래스의 생성자에서는 무조건 상위 클래스의 생성자가 호출되어야함
3) 하위 클래스에서 명시적으로 상위 클래스의 생성자를 호출하는 코드가 없는 경우, 컴파일러는 자동으로 상위 클래스 기본 생성자를 호출하기 위한 super()를 추가함 (super()로 호출되는 생성자는 상위 클래스의 기본 생성자)
4) 만약 상위 클래스의 기본 생성자가 없는 경우(매개변수가 있는 생성자만 존재하는 경우), 하위 클래스는 명시적으로 상위 클래스의 생성자를 호출해야함 -
super 키워드
-상위 클래스의 메모리(참조값)을 가리킴
-super()는 상위 클래스의 디폴트 생성자(기본 생성자)가 호출됨 -
상위 클래스로의 묵시적 형 변환 (업캐스팅)
-상위 클래스 형으로 변수를 선언하고 하위 클래스 인스턴스를 생성할 수 있음
-하위 클래스는 상위 클래스의 타입을 내포하고 있으므로 상위 클래스로 묵시적 형 변환이 가능함
-상속관계에서 모든 하위 클래스는 상위 클래스로 묵시적 형 변환이됨 (그 역은 성립하지 않음)
-> Customer vipCustomer = new VIPCustomer();
(선언된 클래스형, 상위 클래스) (생성된 인스턴스의 클래스형, 하위 클래스)
-형 변환에서의 메모리
-> Customer vipCustomer = new VIPCustomer(); 에서 vipCustomer가 가리키는 것은? Customer 클래스의 멤버 변수와 메서드 (타입이 Customer 이므로)
-> vipCustomer.calcPrice(10000); 을 호출하는 경우 calcPrice()는 어느 메서드에서 호출될까?
: 항상 인스턴스 (VIPCustomer)의 메서드가 호출됨
: 재정의한 메서드의 경우는 재정의한 메서드의 주소를 참조함 -
가상 메서드
-메서드의 이름과 메서드 주소를 가진 가상 메서드 테이블에서 호출될 메서드의 주소를 참조함
다형성
-하나의 코드가 여러 자료형으로 구현되어 실행하는 것 (같은 코드에서 여러 실행 결과가 나옴)
-다양한 여러 클래스를 상위 클래스의 자료형으로 선언하거나 형변환하여 각 클래스가 동일한 메서드를 오버라이딩 한 경우, 하나의 코드가 다양한 구현을 실행할 수 있음
-유사한 클래스가 추가되는 경우 유지보수에 용이하고 각 자료형 마다 다른 메서드를 호출하지 않으므로 코드에서 많은 if문이 사라짐
업/다운캐스팅과 instanceof
- 업캐스팅: 하위 클래스의 객체가 상위 클래스의 타입으로 형변환 되는 것
- 다운캐스팅: 하위 클래스로 형변환, 묵시적으로 상위 클래스 형변환된 인스턴스가 원래 자료형(하위 클래스)으로 변환되어야 할 때, 명시적으로 되어야함 (업캐스팅이 선행되어야함)
- instanceof: 객체 타입을 확인하는 연산자, 형변환 가능여부를 확인하며 true / false 로 결과를 반환
*참고 자료
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=dlaxodud2388&logNo=221642221204
추상 클래스
-추상 메서드(구현 코드가 없이 선언부만 있는 메서드)를 포함한 클래스
-abstract 예약어 사용, new 인스턴스화 할 수 없음
-추상 메서드를 선언한 경우, 추상 클래스를 상속받는 메서드는 추상 메서드를 오버라이딩 할 수도 안할 수도 있다. 만약 선언한 추상 클래스를 모두 오버라이딩 하지 않을 경우는?
-> 예를들어 추상 메서드가 2개 선언된 경우, 2개 중에 1개를 오버라이딩 하거나 2개 모두 오버라이딩 하지 않으려면 상속받는 메서드는 abstract 클래스로 만들어야한다.
-모든 메서드가 구현되었다 해도 클래스에 abstract 키워드를 사용하면 추상 클래스이다.
-> new(인스턴스화) 할 수 없기 때문에 상속을 위한 용도로 사용할 때 (다 구현이 되어 있지만 여러 클래스들의 기반이 되는 클래스, 실제로 쓰일 일이 없고 공통 메서드들을 많이 가지고 있는 경우)
인터페이스
-인터페이스란 클라이언트 프로그램에 어떤 메서드를 제공하는지 알려주는 명세 또는 약속으로 한 객체가 어떤 인터페이스의 타입이라 함은 그 인터페이스의 메서드를 구현했다는 의미이다.
-만약 선언한 추상 메서드들을 모두 오버라이딩 하지 않을 경우는?
-> 예를들어 추상 메서드가 2개 선언된 경우, 2개 중에 1개를 오버라이딩 하거나 2개 모두 오버라이딩 하지 않으려면 상속받는 메서드는 abstract 클래스로 만들어야한다.
-인터페이스의 요소
1) 상수: 선언된 모든 변수는 상수로 처리됨
2) 메서드: 모든 메서드는 추상 메서드
3) 디폴트 메서드: 기본 구현을 가지는 메서드, 구현하는 클래스에서 재정의할 수 있음 (자바8)
-> 다중 상속할 경우 각 인터페이스에서 선언한 메서드 이름이 겹칠 경우 "인터페이스명.super.메서드명" 으로 명시하거나 별도로 재정의해서 사용한다.
4) 정적 메서드: 인스턴스 생성과 상관없이 인터페이스 타입으로 호출하는 메서드 (자바8)
-> 자바8 이전에는 인터페이스 관련 유틸리티 또는 팩토리 메서드를 그룹화하려면 별도 유틸리티 클래스를 작성해야했다. (Common 인터페이스, CommonUtils 클래스) 추가 클래스를 만들지 않고 한 곳에 유지할 수 있게 되었다.
5) private 메서드: 인터페이스 내에서 사용하기 위해 구현한 메서드, 구현하는 클래스에서 재정의 할 수 없음 (자바9)
추상클래스와 인터페이스의 차이점
-
접근자
인터페이스에서 모든 변수는 public static final, 모든 메서드는 public abstract이다.
하지만, 추상 클래스에서는 static이나 final이 아닌 필드를 가질 수 있고 public, protected, private 모두 가질 수 있다. -
다중 상속 여부
인터페이스를 구현하는 클래스는 다른 여러 개 인터페이스를 함께 구현할 수 있다.
하지만, 다중 상속을 지원하지 않기 때문에 여러 추상 클래스를 상속할 수 없다. -
사용 의도
- 추상 클래스는 이를 상속할 각 객체들의 공통점을 찾아 추상화시켜 놓은 것으로, 상속 관계를 타고 올라갔을 때 같은 상위 클래스를 상속하는데 기능까지 완변히 똑같은 기능이 필요한 경우
-> 추상 클래스와 추상 클래스를 상속 받는 클래스와 관련성 높아서 클래스 간에 코드를 공유하고 싶거나 완벽히 기능까지 똑같이 필요한 경우, non-static, non-final 필드 선언이 필요한 경우, 공통으로 가지는 메서드와 필드가 많거나 public이외의 접근자(protected, private) 선언이 필요한 경우 - 인터페이스는 상속 관계를 타고 올라갔을 때 다른 상위 클래스를 상속하더라도, 같은 기능이 필요할 경우 사용한다. 상위 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용한다.
-상속 관계가 없는 클래스간 서로 공통되는 로직을 구현하여 쓸 수 있도록 한다.
-> 서로 관련성이 없는 클래스들이 인터페이스를 구현하게 되는 경우 (ex) Comparable, Cloneable, Serializable 등..), 다중상속을 허용하고 싶은 경우
*자바 8 이전에는 인터페이스의 추상 메서드가 있고, 인터페이스를 상속받은 추상 클래스에서 비어있는 구현체를 선언하고, 추상 클래스를 상속받은 concrete 클래스에서 필요한 메서드만 가져다가 재정의해서 사용해 편의성을 제공했다.
하지만, 자바 8 이후부터는 인터페이스에서 이 편의성을 제공할 수 있게 되었다. 인터페이스에서 디폴트 메서드를 사용해서 정의해놓고 실제로 구현해야 하는 concrete 클래스에서는 중간에 추상 클래스를 상속 받아서 구현하는 것이 아니라 인터페이스를 상속 받아서 구현하는 것이 가능하다.
즉, 구현한 클래스는 인터페이스를 구현한 것이기 때문에 내가 만든 프레임워크 때문에 상속이 강제되지 않아 상속이 자유롭다.
내부 클래스
클래스 내부에 구현한 클래스 (중첩된 클래스)로 클래스 내부에서 사용하기 위해 선언하고 구현하는 클래스이다. 주로 외부 클래스 생성자에서 내부 클래스를 생성한다.
내부 클래스에서 외부 클래스의 멤버를 쉽게 접근하기 위해 사용한다. 또한, 서로 관련 있는 클래스들을 한 곳에 묶음으로써 코드의 캡슐화를 증가시키고 책임을 클래스 하나로 묶을 수 있다.
<내부 클래스 유형>
*참고자료 - 패스트 캠퍼스 온라인 강의
- 인스턴스 내부 클래스는 외부 클래스를 먼저 만든 후 내부 클래스를 생성하기 때문에 static 클래스로 선언하지 않고는 static 변수/메서드를 사용할 수 없다.
- 메서드 안에서 내부 클래스가 사용될 때는 지역변수 값을 변경할 수 없다. 내부적으로 final 처리한다.
class Outer {
private int outNum = 100;
private static int sNum = 200;
public Runnable getRunnable(int i) { // = final int i
int num = 100; // == final int num = 100;
class MyRunnable implements Runnable {
@Override
public void run() {
// num += 10;
// i = 200;
System.out.println(num);
System.out.println(i);
System.out.println(outNum);
System.out.println(Outer.sNum);
}
}
return new MyRunnable();
}
}
- 익명 클래스는 class MyRunnable (클래스 이름)이 한 번 뿐이 없고 해당 블록에서만 쓰이므로 클래스를 지우고 new 클래스 이름으로 생성한다. 바로 인터페이스나 추상 클래스에 대한 생성을 할 수 있다. 단 하나의 인터페이스나 추상 클래스인 경우 이름 없이 바로 new로 인터페이스나 추상클래스 타입으로 생성할 수 있다. 익명 클래스는 별도의 생성자를 작성할 수 없다.
public Runnable getRunnable(int i) {
int num = 100;
return new Runnable() {
@Override
public void run() {
System.out.println(num);
System.out.println(i);
System.out.println(outNum);
System.out.println(AnonymousOuter.sNum);
}
};
}
Runnable runner = new Runnable() {
@Override
public void run() {
System.out.println("Test");
}
};
*참고 자료
https://codevang.tistory.com/110
Author And Source
이 문제에 관하여(Chapter 7 객체지향 프로그래밍2), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@gkskaks1004/chapter-7-객체지향프로그래밍2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)