*상속
다른 클래스가 가지고 있는 필드, 메소드들을 새로 작성할 클래스에서 직접 만들지 않고 이미 만들어진 클래스에서 "상속" 받음으로써 자신의 필드, 자신의 메소드처럼 사용 가능한 개념
=> 즉, 코드를 물려받겠다는 뜻
- 보다 적은 양의 코드로 새로운 클래스 작성 가능
- 중복된 코드를 공통적으로 관리하기 때문에 새로운 코드 추가, 수정 등에 용이하며 생산성과 유지보수에 큰 기여를 함
매 클래스마다 중복된 코드들을 일일이 기술하면 수정과 같은 유지보수 시 매번 일일이 찾아서 수정해야 한다는 번거로움이 생김
=> "상속"이라는 개념을 적용시켜 매 클래스마다 중복된 필드, 중복된 메소드들을 단 한 번만 또 다른 클래스에 정의해 두고 해당 클래스를 가져다 쓰는 방식으로 진행 가능함
=> 생성자는 물려받을 수 없음을 주의할 것
코드를 물려받는 측 ────▷ 코드를 물려주는 측
(자식이 부모를 찌르는 실선 화살표 모양)
자식 부모
후손 조상
하위 상위
서브 슈퍼
<상속 시 주의할 점>
1. 상속을 받기 위해서는 자식 클래스의 클래스 선언부에 public class 자식클래스명 extends 부모클래스명을 기재해 주어야 함
2. 상속은 부모 클래스의 필드와 메소드를 가져다 쓸 수 있지만 생성자는 가져다 쓸 수 없으므로 이를 따로 정의해 줘야 함
3. this.으로 내 안에 있던 생성자를 호출한 것처럼 super(매개변수명)으로 상속 관계에 있는 부모 클래스의 생성자를 호출할 수 있음
단, 이때 super(매개변수명)은 반드시 생성자의 가장 윗줄에 호출 구문을 작성해야 함
4. 마찬가지로 메소드부에서도 super.메소드명으로 부모 클래스의 메소드를 호출할 수 있음
5. 자식 클래스에서는 부모 클래스의 내용을 가져다 쓸 수 있지만 부모 클래스에서는 자식 클래스의 내용을 가져다 쓸 수가 없음
6. 클래스간 다중 상속은 불가능함 (단일 상속만 가능)
7. 오버라이딩을 활용하여 부모 클래스의 메소드를 자식 클래스마다 다르게 활용하여 사용 가능함
단, 오버라이딩 시 해당 메소드를 호출했을 때 오버라이딩된 자식 메소드가 우선적으로 호출됨(동적 바인딩)
오버라이딩 시 @Override(== 어노테이션)을 활용하여 오버라이딩 된 메소드임을 알려 주는 것이 좋음
*오버라이딩
상속받고 있는 부모 클래스의 메소드를 자식 클래스에서 재정의하는 것
- 부모 메소드명과 메소드명이 동일해야 함
- 매개변수의 자료형, 개수, 순서, 반환형이 동일해야 함
- 부모 메소드의 접근 제한자와 같거나 공유 범위가 더 커야 함
<toString() 메소드>
toString 메소드는 모든 객체의 부모 클래스인 Object클래스에서 "해당 객체가 가지고 있는 정보나 값(== 주소값)을 16진수의 형태로 만들어 리턴하는 메소드"이며, 객체명만 제시했을 경우 내부적으로 toString() 메소드가 자동으로 호출됨
=> 이 메소드를 재정의하여(== 오버라이딩 하여) 부모/자식 클래스에서 원하는 값을 나타내도록 함
ex) (-)String brand, (-)String name 필드가 Product라는 부모 클래스에 존재하고,
(-)int price 필드를 Desktop이라는 자식 클래스에서 추가한 경우
public class Desktop extends Product { // Product가 부모 클래스이며 상속받았음을 표현
// 필드부
private int price; // brand, name은 부모 클래스에서 이미 정의했으므로 별도 정의할 필요 없음
// 생성자부
public Desktop() {
} // 기본 생성자
public Desktop(String brand, String name, int price) { // 매개변수는 부모 클래스, 자식 클래스 모두 기재해 줘야 함
super(brand, name); // super(부모클래스매개변수명)
this.price = price; // this.자식클래스매개변수
}
// 메소드부
public void setPrice(int price) { // 값을 입력받아 매개변수에 넣고 담기 위한 자식 클래스의 메소드 작성
this.price = price;
} // 부모 클래스의 setter 메소드는 이미 부모 클래스에 작성되어 있을 것이므로 별도 작성할 필요 없음
public int getPrice(){ // Run클래스에서 호출한 int형의 price를 담아 보내겠다
return price;
} // 부모 클래스의 getter 메소드는 이미 부모 클래스에 작성되어 있을 것이므로 별도 작성할 필요 없음
@Override // 어노테이션을 활용하여 오버라이딩 된 메소드임을 기재해 놓는 것이 좋음! 부모 클래스, 자식 클래스 동일
public String toString(){
return super.toString() + ", price: " + price; // super.(부모클래스의메소드명)() + 재정의하고자하는자식클래스문자열
}
}
=> 이런 식으로 진행됨!
*다형성
같은 자료형에 여러 가지 객체를 대입하여 다양한 결과를 얻어내는 성질
- 상속 관계에서만 이루어질 수 있는 형 변환의 개념
- 대입연산자(=) 기준으로 왼쪽과 오른쪽의 자료형이 같아야 함
<다형성을 사용하는 이유>
- 자식 객체에 대한 배열 선언 없이 부모 타입의 참조변수로 다양한 자식 객체를 받을 수 있음
- 메소드에 매개변수나 반환형에 다형성을 적용하게 되면 메소드 개수가 줄어듦
<다형성 사용하는 법(UpCasting 버전)>
부모클래스명 대변할이름 = new 자식클래스명();
=> 부모클래스명의 대변할 이름으로 생긴 Stack 영역 상자에 자식 클래스의 실물 타입이 heap영역에 들어감
=> 자식이 부모 상자에 들어가는 것은 자동 형변환이며 형변환연산자 생략 가능(업캐스팅, UpCasting)
위와 같이 UpCasting으로 부모 상자에 자식의 값이 들어갔을 때 다시 자식 메소드를 사용하고 싶다면?
=> (자식클래스명)arr[0].자식메소드명();
=> (자식클래스명)을 앞에 붙여 강제 형변환함으로써 원상 복구 후에 사용 가능! (다운캐스팅, DownCasting)
==> DownCasting은 자식클래스명 대변할이름 = new (자식클래스명) 부모클래스명(); 따위로 진행되는 것이 아님을 꼭 확인할 것!
==> new로 생성 시에는 안 되고, 이미 생성된 부모 객체를 자식으로 다시 돌릴 때는 가능...?
Phone ph = (Phone)ois.readObject(); // 다형성에 의해 DownCasting
이거는 왜 됏지...
<instanceof 연산자>
현재 참조변수가 실제로 어떤 자식 클래스를 참고하고 있는지 확인할 때 사용하는 연산자
참조변수명 instanceof 검사할클래스명
case1) 부모객체명 instanceof 부모클래스명 == true
case2) 자식객체명 instanceof 부모클래스명 == true (상속을 받았으니 부모님 거도 내 거)
case3) 부모객체명 instanceof 자식클래스명 == false (부모객체는 자식객체의 내용물에 접근 불가)
case4) 자식객체명 instanceof 자식클래스명 == true
*추상 클래스(abstract class)
몸통부가 존재하지 않는 미완성된 메소드로써 개념적으로는 미완성 클래스임을 나타내기 위해 사용하고, 기술적으로는 객체 생성이 불가능한 점을 활용하기 위해 사용됨
- 객체 생성은 불가하나 참조자료형 변수로는 활용 가능
ex) 추상클래스명 대변할이름 = new 추상클래스명(); // 불가함!!
ex) 추상클래스명 대변할이름; // 가능함!!
=> 즉, 추상클래스명 대변할이름 = new 자식클래스(); // 활용 가능함!
- 오버라이딩을 통해 자식 클래스가 부모 클래스의 메소드를 '어차피' 바꾸게 될 테니 부모 클래스에서는 해당 메소드를 몸통부가 존재하지 않는 클래스로 만들고 싶을 때 사용함
- 해당 클래스 내부에 추상 메소드가 하나라도 존재하는 순간 해당 클래스는 반드시 추상 클래스로 정의해 줘야 함
=> 단, 추상 메소드가 없어도 굳이 추상 클래스로 정의할 경우 일반 필드 + 일반 메소드 + 추상 메소드(생략) 하여 사용 가능
=> 추상 메소드의 경우 자식 클래스에서 반드시! 오버라이딩 하여 완성해 줘야 함
<추상 클래스 사용하는 법>
클래스 선언부에 abstract라는 예약어를 추가해 줘야 함
ex) public abstract class 클래스명 {
<객체 배열로써의 사용법>
추상클래스[] 배열명 = new 추상클래스[배열개수];
배열명[0] = new 자식클래스1();
배열명[1] = new 자식클래스2();
...
for(int i = 0; i < 배열명.length; i++) {
배열명[i].추상메소드명();
}
*인터페이스
모든 필드가 상수 필드이고, 모든 메소드가 추상 메소드인 일종의 추상 클래스
- 무조건 구현해야 될 것들이 있을 때 인터페이스를 만들어서 상속하게 됨
<추상 클래스와의 비교>
추상 클래스 => 내부에 추상 메소드, 일반 메소드, 상수/변수 필드 모두 생성 가능
인터페이스 => 내부에 오직 상수 필드와 추상 메소드만 생성 가능
추상 클래스=> 적어도 추상 클래스가 가진 추상 메소드는 반드시 자식 클래스에서 오버라이딩 해야 함
인터페이스 => 내부의 모든 메소드가 추상 메소드이므로, 인터페이스를 가져다 쓰는 클래스는 인터페이스의 모든 메소드를 오버라이딩 해야 함
=> 추상 클래스보다 더욱 강한 규칙성과 강제성을 제공함
<인터페이스 사용하는 법>
1. 해당 클래스를 인터페이스로 사용하고 싶을 때
public interface 인터페이스명 {
2. 클래스에 인터페이스를 가져다 쓰고 싶을 때
public class 클래스명 implements 인터페이스명, 인터페이스명2, ... (다중구현 가능)
=> 클래스 간의 상속은 다중 상속이 불가능하나 클래스-인터페이스 간의 다중 구현, 인터페이스-인터페이스 간의 다중 상속은 가능함!
ex)
public interface 인터페이스명 {
// 필드부
final 자료형 필드명 = 값; // 어차피 상수 필드만 선언 가능하기 때문에 public static은 묵시적으로 쓰지 않음
// 메소드부
void 메소드명(); // 어차피 추상 메소드만 선언 가능하기 때문에 public static은 묵시적으로 쓰지 않음
}