08_상속(Inheritance)
매 클래스마다 중복된 코드들을 일일이 기술하면
수정과 같은 유지보수 시 매번 일일이 찾아서 수정해야 한다는 번거로움이 생김
=> "상속"이라는 개념을 적용시켜서 매 클래스마다 중복된 필드, 중복된 메소드들을 단 한 번 또 하나의 클래스로 정의해 둔 후
해당 클래스를 가져다 쓰는 방식으로 진행
*상속
다른 클래스가 가지고 있는 필드, 메소드들을 새로 작성할 클래스에서 직접 만들지 않고
이미 만들어진 클래스에서 "상속" 받음으로서 자신의 필드, 자신의 메소드처럼 사용 가능한 개념
=> 즉, 코드를 물려받겠다
* 코드를 물려받는 측 -------> 코드를 물려주는 측
자식 부모
후손 조상
하위 상위
// 부모 클래스: 세 클래스 모두 공통적으로 기술했던 요소들만 추출해서 단 한 번 정의해 둔 클래스
package com.kh.chap01.beforeVSafter.after.model.vo;
// 부모 클래스: 세 클래스 모두 공통적으로 기술했던 요소들만 추출해서 단 한 번 정의해 둔 클래스
public class Product {
// 필드부
private String brand;
private String pCode;
private String pName;
private int price;
// 생성자부
public Product() {
}
public Product(String brand, String pCode, String pName, int price) {
this.brand = brand;
this.pCode = pCode;
this.pName = pName;
this.price = price;
}
// 메소드부
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getpCode() {
return pCode;
}
public void setpCode(String pCode) {
this.pCode = pCode;
}
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
public int getprice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String information() {
return "brand: " + brand + ", pCode: " + pCode + ", pName: " + pName + ", price: " + price;
}
}
// 자식 클래스: 부모 클래스로부터 중복된 코(필드, 메소드)를 물려받아 사용하는 클래스 - 생성자는 물려받지 않음
// => 자식 클래스에서 우선적으로 해야 할 것: 누구로부터 코드를 물려받을 것인지 언급하기!!
package com.kh.chap01.beforeVSafter.after.model.vo;
// 자식 클래스: 부모 클래스로부터 중복된 코(필드, 메소드)를 물려받아 사용하는 클래스 - 생성자는 물려받지 않음
// => 자식 클래스에서 우선적으로 해야 할 것: 누구로부터 코드를 물려받을 것인지 언급하기!!
// 자식 -------> 부모
// 후손 조상
// 하위 상위
// 서브 슈퍼
public class Desktop extends Product {
// 필드부
private boolean allInOne; // Product에서 4개의 필드를 상속받았으므로 Desktop의 필드는 5개가 됨
// 생성자부
public Desktop() {
}
// 메소드부
public boolean isallInOne() {
return allInOne;
}
public void setallInOne(boolean allInOne) {
this.allInOne = allInOne;
}
}
이렇게 상속받고 나면 자식 클래스를 메인 메소드에 등록했을 때
부모 클래스의 메소드까지 함께 불러올 수 있음!
하지만 매개변수를 사용하고 싶다면?
=> 상속에서는 필드, 메소드를 가지고 올 뿐 생성자는 가지고 오지 못함!
자식 클래스에 생성자를 추가해 보려고 했지만 is not visible 오류가 뜸
=> this. 은 나의 주소값, super. 은 부모의 주소값을 가지고 오므로 this. -> super.으로 바꾸어 주기
하지만! 기존 필드가 private이므로 여전히 is not visible 오류가 뜨면서 생성자를 만들 수 없음
*해결 방법1: private -> protected로 접근 제한자를 변경
=> 단, 이 방법 사용 시 정보은닉(캡슐화)를 어긴 꼴이 됨!
*해결 방법2: 부모 클래스에 있는 public 접근 제한자인 setter 메소드를 호출해서 필드값을 채워 넣는 방법
// 상속 관계에서 부모 클래스에 있는 메소드에 직접 접근 하고자 할 경우 super.메소드명()으로 호출
=> setter 값을 매번 입력해 줘야 하는 번거로움 있음
// 해결 방법3: 부모 클래스에 있는 매개변수 생성자를 호출하기(== super 생성자 호출)
// 상속관계에서 부모 클래스에 있는 생성자를 호출하고자 할 경우 super 생성자를 호출
// => 내 안에 있던 생성자를 호출할 경우 this 생성자를 호출했었음! (반드시 생성자의 가장 윗줄에 호출 구문 작성)
// super(); // 부모의 기본 생성자가 호출이 됨
이 방법들을 이용하면 이제 Run 클래스에서 호출도 잘됨
하지만 현재 부모 클래스에서 만들어 둔 information() 메소드를 호출했기 때문에 자식 클래스의 값들이 출력되지 않음
// 출력
System.out.println(d.information());
System.out.println(s.information());
System.out.println(t.information());
// 현재 출력물
brand: 삼성, pCode: d-01, pName: 짱짱데스크탑, price: 2000000
brand: 애플, pCode: s-01, pName: 아이폰, price: 1300000
brand: 엘지, pCode: t-01, pName: 고오급벽걸이티비, price: 4000000
실행하고자 하는 메소드가 부모 클래스에만 있고 자식 클래스에는 없음
자식 클래스에서는 부모 클래스의 내용을 가져다 쓸 수 있지만
부모 클래스에서는 자식 클래스의 내용을 가져다 쓸 수가 없다!! => 상속 구조의 가장 핵심적인 특징
자식 클래스 입장에서 물려받은 부모 클래스의 메소드를 내 입맛대로 재정의해서 쓸 수 있음 (오버라이딩)
=> 오버라이딩 시 해당 메소드를 호출했을 때 오버라이딩된 자식 메소드가 우선적으로 호출됨(동적 바인딩)
*주의사항
오버로딩이랑 헷갈리면 안 됨!
오버로딩: 상속과는 관련 없이 메소드명을 동일하게 여러 개 만들 수 있는 규칙
메소드명 동일, 매개변수의 자료형의 종류, 순서, 개수가 다르면 중복된 이름이 가능
오버라이딩: 상속과 관련 있는 개념으로
"상속 관계에서" 부모 메소드의 내용물을 자식이 내 입맛대로 재정의해서 사용 가능
메소드 틀(접근제한자, 반환형, 메소드명, 매개변수) 동일해야 함
//// --------------- Desktop 클래스 -------------
// 오버라이딩 작업(information 메소드)
public String information() {
// return "brand: " + super.getBrand() + ", pCode: " + super.getpCode() + ", pName: " + super.getpName()
// + ", price: " + super.getPrice() + ",allInOne: " + allInOne;
return super.information() + ", allInOne: " + allInOne;
}
//// --------------- SmartPhone 클래스 -------------
public String information() {
return super.information() + ", mobileAgency: " + mobileAgency;
}
//// --------------- Tv 클래스 -------------
public String information() {
return super.information() + ", inch: " + inch;
}
자식 클래스에 부모 클래스의 information 메소드를 불러 온 뒤 + 자식 클래스의 값을 따로 information 메소드 만드는 것과 같ㅇ느 방법으로 추가해 줌
super.information() + "출력하고 싶은 문자" + 자식클래스필드명
만들어 주고 난 후 다시 출력하면
**상속의 과정
1) public class 클래스명 extends 부모클래스 {
=> 부모클래스로부터 상속을 받겠다라는 것을 정의
2) 자식 클래스에 추가할 값을 필드부, 메소드부, 생성자부에 기재
3) information 메소드 작성 시 오버라이딩 과정 진행
public String information() {
return super.information() + "출력을원하는문자" + 자식클래스필드명;
4) 기본 생성자 작성 후 모든 필드에 대해 매개변수 있는 생성자 만들 때
public 자식클래스명(①자료형 필드명) {
super.(부모필드명1, 부모필드명2, ...);
this.자식필드명 = 자식필드명;
*①: 자료형 필드명을 기재할 때는 부모 클래스, 자식 클래스 모두 다 적음!
만약에 각 자식 클래스에 information 메소드를 재정의하지 않았다면 (재정의 == 오버라이딩)
각각 부모 클래스인 Vehicle에 있는 information 메소드로 호출됐을 것!!
=> 재정의 하는 순간 자식 메소드로 우선권이 넘어가서 자식 클래스의 information 메소드가 호출됨
오버라이딩을 진행할 경우 @Override로 표시해 주는 것이 좋음!
여기서 @ <- 주석과 같은 역할을 함
*상속의 장점
- 보다 적은 양의 코드로 새로운 클래스들을 작성 가능함
- 중복된 코드를 공통적으로 관리하기 때문에 새로운 코드를 추가하거나 수정할 때 용이
- 프로그램의 생산성과 유지보수에 큰 기여
*상속의 특징
- 자식 클래스는 부모 클래스의 필드, 메소드를 모두 가져다 쓸 수 있지만
부모 클래스는 자식 클래스의 코드를 가져다 쓸 수 없음
- 클래스 간의 상속은 다중 상속이 불가능함(단일 상속만 가능)
만약 다중 상속 시에 서로 같은 필드명, 메소드명이 있을 것에 대비해 처음부터 막아 둠
- 명시되어 있지는 않지만 모든 클래스(자바에서 미리 만들어서 제공되는 클래스, 내가 만든 클래스)
Object(자바에서 미리 만들어서 제공되는 클래스) 클래스의 후손임
=> Object 클래스에서 이미 만들어진 메소드를 가져다 쓸 수 있음
=> Object 클래스에서 이미 만들어진 메소드가 마음에 들지 않는다면
오버라이딩을 통해 재정의 가능
다중 상속 불가능
Object는 모든 클래스의 조상
*setter, getter 메소드 한 큐에 만들기 (자동 완성)
*오버라이딩
모든 클래스는 Object 클래스의 후손임
즉, 최상위 클래스는 항상 Object
=> Object에 있는 메소드들은 다 가져다 쓸 수 있음
=> Object에 있는 메소드들을 내 입맛대로 오버라이딩 할 수 있음
- 상속받고 있는 부모 클래스의 메소드를 자식 클래스에서 재정의(재작성) 하는 것
- 부모가 제공하고 있는 메소드를 자식이 일부 고쳐서 사용하겠다라는 의미
- 오버라이딩 시 해당 메소드를 호출하면 자식 메소드가 우선권을 가짐
package com.kh.chap03.override.run;
import com.kh.chap03.override.model.vo.Book;
public class OverrideRun {
public static void main(String[] args) {
// 모든 클래스는 Object 클래스의 후손임
// 즉, 최상위 클래스는 항상 Object
// => Object에 있는 메소드들은 다 가져다 쓸 수 있음
// => Object에 있는 메소드들을 내 입맛대로 오버라이딩 할 수 있음
Book bk = new Book("수학의정석", "나수학", 10000);
System.out.println(bk /* .toString() */); // 주소값
System.out.println(bk.hashCode()); // 주소값 십진수 형태
// Object에서 제공하는 유용한 메소드를 상속받아서 사용 가능
// 그중에서도 아주! 유용하게 쓸 수 있는 메소드
// toString() 메소드
System.out.println(bk.toString()); // 주소값 내용이 bk만 제시했을 때랑 동일하게 출력
}
}
* toString() 메소드
해당 참조 타입의 풀클래스명 + @ + 해당 객체의 주소값의 16진수의 형태로 돌려주는 메소드
알게 모르게 객체명만 제시했을 경우 내부적으로 toString() 메소드가 호출된 꼴 (JVM에 의해)
출력문 안에서 참조형 변수를 제시해서 해당 내용을 출력하고자 할 때
toString() 메소드가 내부적으로 알아서 호출된다는 점을 이용해서
toString() 메소드를 오버라이딩 해서 각 필드의 값을 한 개의 문자열로 리턴해 주게끔 재정의해서 씀!!
=> 기존에 information 메소드의 역할을 toString이 하게끔 오버라이딩 하겠음
*오버라이딩 성립 조건
- 부모 메소드명과 메소드명이 동일해야 함
- 매개변수의 자료형, 개수, 순서가 동일해야 함(단, 매개변수명과는 무관하나 헷갈리지 않게 맞춰 주는 게 좋긴 함!)
- 반환형 동일해야 함
- 부모 메소드의 접근 제한자보다 같거나 공유 범위가 더 커야 함
=> 규약의 개념이 들어가 있음(재정의 하려면 이 정도의 규칙은 지켜야 함)
*오버로딩
: 메소드명을 중복해서 정의할 수 있는 규칙 / 상속이랑 관련 없는 개념이므로 주의할 것!
*오버로딩 성립 조건
1. 메소드명은 일치
2. 매개변수의 자료형, 순서, 개수가 달라야 함
3. 반환형, 매개변수명, 접근제한자는 영향을 주지 않음
@Override // 어노테이션(생략 가능)
public String toString() {
return "title: " + title + ", author: " + author + ", price: " + price;
}
// 기존 information 메소드의 역할을 toString 메소드가 대체하는 것임!
* @Override 어노테이션
- 생략 가능(명시를 안 해도 부모 메소드와 형태가 같다면 오버라이딩이 잘된 것)
- 어노테이션을 붙이는 이유? (생략 가능하지만 붙이는 걸 권장)
> 잘못 기술했을 경우 오류를 알려 주기 때문에 다시 검토할 수 있게 유도함
> 혹시라도 부모 메소드가 후에 수정되었을 때 오류로 알려 주기 때문에 검토할 수 있게 유도함
> 이 메소드가 오버라이딩 된 메소드라는 것을 알리기 위한 목적으로 사용 (주석의 역할)
*클래스 구조
public class 클래스명 {
// 필드부
// 생성자부: 기본 생성자, 모든필드에대한매개변수생성자
// 메소드부: getter / setter, toString() 오버라이딩
}
*toString 자동 완성
필드부만 선택 후 Generate