1. 상속(Inheritance)
클래스들 간의 2가지 관계가 있다.
1) has-a 관계 :
자동차(물건, 객체) 클래스
클래스는 수십만개의 부품(객체)으로 이루어져있다. -> Car 클래스는 Engine class, Handle class 등
Car - Engine 클래스들 간의 관계 : Car has Engine (Car가 Engine 클래스를 가진다)
ex) Car.engine.moreFuel(20);
코드) System.out.println();
// has-a 관계
// System.out.println("홍길동");
class System {
// 필드
static PrintStream out;
}
class PrintStream {
void println() {}
}
2) is-a 관계(상속)
상속이란? 기존 클래스를 재사용하여 새로운 클래스를 작성(선언)하는 것
상속의 장점? 코드의 재사용, 코드의 중복제거 -> 생산성이 높아지고 코드의 유지,보수가 용이
상속 선언 형식 : extends 키워드 사용한다.
class 새로운 클래스명 extends 기존클래스명 { }
기존클래스 : 부모(parent) 클래스, ***[Super 클래스]***, 기초(base) 클래스, 상위 클래스
새로운 클래스 : 자식(child) 클래스, Sub 클래스, 파생 클래스, 하위 클래스
* 생성자와 초기화 블럭은 상속되지 않는다.
* 생성자가 호출되는 순서 이해(암기)
1) 부모 객체가 먼저 생성되기에 부모의 생성자가 먼저 호출되고 > ex) Employee 생성자 4 호출됨.
2) 자식 객체가 생성된다. > ex) Regular 생성자 5 호출됨.
1. has-a 관계
Car 클래스)
package days17;
public class Car {
// 필드
String name;
String gearType;
int door;
Door[] d = new Door[4]; // 객체 배열
// 클래스타입 객체;
// Engine engine;
// 결합력이 높은 코딩 -> 좋은 코딩이 아니다.
// Engine engine = new Engine(); // 명시적 초기화 -> 좋은 코딩 X 왜? 엔진이 고장나면 자동차(Car 클래스) 전체를 버린다.
private Engine engine = null;
// getter
public Engine getEngine() {
return engine;
}
// setter
// main() 메서드(외부)에서 engine 인스턴스 생성해서 주입을 받음
public void setEngine(Engine engine) {
this.engine = engine;
}
// 디폴트 생성자 - 생성자 초기화
Car() {
this.engine = new Engine(); // 엔진 인스턴스 생성
}
// main() 메서드(외부)에서 engine 인스턴스 생성한 다음에 주입을 받음
// 생성자를 통해서 엔진 객체 주입(DI : Dependence Injection) -> 의존성 주입(자체적으로 생성안하고 외부에서 가지고 오는 것)
// 자체적은 Car 클래스 안에서이고 외부는 main()에서
Car(Engine engine) {
this.engine = engine;
}
// 메서드
void speedUp(int fuel) { // 엑셀을 밟아서(엔진에 연료를 넣어서) 속도 증가
this.engine.moreFuel(fuel); // 에러 라인 : NullPointerException -> 클래스타입 객체를 인스턴스 생성하지 않아서 발생
}
void speedDown(int fuel) { // 브레이크를 밟아서 속도 감소
this.engine.lessFuel(fuel);
}
void stop() { // 속도 멈춤
this.engine.stop();
}
} // Car
Engine 클래스)
main() 메서드)
☞ 참고
- DI : Dependence Injection -> 의존성 주입
의존성 주입을 하는 이유?
Engine engine = new Engine(); // 명시적 초기화 -> 좋은 코딩 X
why? 엔진이 고장나면 자동차(Car 클래스) 전체를 버려야하기 때문에 외부에서 객체를 주입한다.
2. is-a 관계(상속) : extends 키워드 사용
student 클래스) 부모 클래스
childStudent 클래스) 자식클래스
코드 예시)
public class Ex06 {
public static void main(String[] args) {
// 객체(클래스) 배열 초기화
Point[] p = {
new Point(1,1),
new Point(10, 100),
new Point(120, 15)
};
// 생성자 DI(생성자를 통해서 객체 주입)
Triangle t = new Triangle(p);
} // main
} // class
// 삼각형, 사각형, 원, 마름모 등등
class Shape {
// 필드
String color = "black";
// 메서드
void draw() {
System.out.printf("[Color = %s]\n", this.color);
}
} // Shape class
// 좌표 클래스
class Point {
// 필드
int x;
int y;
// 디폴트 생성자
Point() {
this(0, 0); // this의 두번째 용도 : 생성자에서 또 다른 생성자를 호출할 때의 this
}
// 생성자
Point(int x, int y){
this.x = x; // this의 첫번째 용도 : 멤버(필드, 메서드)를 가리킬 때의 this
this.y = y;
}
// 메서드
String getXY() {
return String.format("(%d, %d)", x, y);
}
} // Point class
// 원 클래스
// > 자바는 다중상속을 할 수 없다(부모 클래스는 딱 한 개여야 된다)
// class Circle extends Shape extends Point{ // 문법 에러
// class Circle extends Shape, Point{ // 문법 에러
class Circle extends Shape{
// 원점 (어쩔 수 없이 has-a 관계를 가질 수 밖에 없음)
Point center; // Point center = new Point(); // 좋은 코딩은 아님
int r; // 반지름
// 디폴트 생성자 DI
Circle() {
this(new Point(0,0) , 100);
}
// 생성자 DI
Circle(Point center, int r) {
this.center = center;
this.r = r;
}
} // Circle class
// 삼각형 클래스
class Triangle extends Shape{
// 점3
Point[] p = new Point[3]; // 객체 배열 선언 코딩 -> main() 메서드에서 인스턴스 생성
public Triangle(Point[] p) {
this.p = p;
}
} // Triangle class
// 사각형 클래스
class Rectangle extends Shape{
// 점4
Point[] p = new Point[4];
public Rectangle(Point[] p) {
this.p = p;
}
} // Rectangle class
상속 이해 예제) MyApplication
> 리스너, 어댑터, 인터페이스(implements 키워드)에 대해서는 다시 설명해 주실 예정이므로 코드 한 번 훑고 암기하기
main() 메서드)
MyApplication 클래스)
package days17;
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
// MyApplication 클래스가 윈도우 리스너 역할까지 하도록 코딩
// this의 3번째용도 - 인자 설명
// top-level window(윈도우, 창)
public class MyApplication extends Frame implements WindowListener{
// 필드 선언 x
// 메서드 선언 x
// 디폴트 생성자
public MyApplication() {
System.out.println("> MyApplication 디폴트 생성자 호출");
this.setTitle("새로운 창");
this.setSize(400, 400);
// this.addWindowListener(new MyAppWindowListener()); // 객체를 생성해야하니까 안에 new 연산자로 객체를 생성..
this.addWindowListener(this); // MyApplication 자기 자신이 리스너 역할을 한다. / this의 3번째 용도
this.setVisible(true); // 보이도록 설정
}
@Override
public void windowActivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowClosed(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowClosing(WindowEvent e) {
System.out.println("프로그램 종료됩니다");
System.exit(-1);
}
@Override
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
} // MyApplication
/*
// [인터페이스 개념] implements 키워드 설명 -> 나중에 상세히 설명 예정 이거 다 암기 내용
// 리스너(청취자) 선언 : 윈도우 관련 이벤트(X 닫기 버튼 클릭)를 처리하는 리스너
class MyAppWindowListener implements WindowListener{
@Override
public void windowActivated(WindowEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void windowClosed(WindowEvent arg0) {
// 닫히고 난 후에 작업
}
// 이벤트 핸들러
// 메서드는 메서드인데 이벤트가 발생하면 호출되는 메서드
@Override
public void windowClosing(WindowEvent e) {
// 닫히기 전에 물어보는 작업 등
System.out.println("프로그램 종료됩니다");
System.exit(-1);
}
@Override
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
} // MyAppWindowListener
*/
has-a(포함관계)와 is-a(상속) 코드 예시)
// 원 클래스 is-a 관계(상속)
class Circle extends Point{
// [필드]
// 원점
// int x;
// int y;
// 반지름
int r;
} // Circle
// 원 클래스 has-a 관계(포함관계)
// Circle 클래스는 Point 클래스를 가지고 있다.
class Circle{
// [필드]
// 원점
// int x;
// int y;
Point 원점 = null;
// 반지름
int r;
Circle() {
원점 = new Point();
}
Circle(Point p){
this.원점 = p;
}
//
} // Circle
// 좌표점 클래스
class Point{
// 필드
int x;
int y;
// 디폴트 생성자
Point() {
this(0, 0); // this의 두번째 용도 : 생성자에서 또 다른 생성자를 호출할 때의 this
}
// 생성자
Point(int x, int y){
this.x = x; // this의 첫번째 용도 : 멤버(필드, 메서드)를 가리킬 때의 this
this.y = y;
}
// 메서드
String getXY() {
return String.format("(%d, %d)", x, y);
}
} // Point
> 원점이 has-a 관계를 가질수 밖에 없는 이유 : 다중상속을 할 수 없기 때문에
> 자바는 다중상속을 지원하지않는다.
2. Object 클래스
- 모든 클래스의 최상위 부모(super) 클래스는 Object 클래스이다.
> 모든 클래스는 java.lang.Object 클래스를 상속받기 때문에 컴파일할 때 자동으로 extends.java.lang.Object로 처리
3. super 키워드
- super() == 조상 클래스의 생성자
- super 키워드 정의와 3가지용도? =this의 3가지 용도와 비슷함
- 정의 : 클래스 자기 자신의 부모 주소값을 갖는 참조변수 == super 키워드(예약어)
- 다른 코딩보다 항상 첫번째 라인에 있어야 한다.
두번째 용도) 생성자에서 또 다른 부모 생성자를 호출할 때의 super();
첫번째 용도) 부모 멤버를 가르킬 때의 super
4. 업캐스팅(upcasting) / 다운캐스팅(downcasting)
- 업캐스팅(upcasting) : 자식 객체를 생성해서 부모 객체에 참조시키는 것(자동형변환 가능)
- 다운캐스팅(downcasting) : 부모 객체를 다시 자식 객체에 참조시키는 것(자동형변환X, 강제형변환)
- 형변환 조건 : 상속관계
- 부모객체 생성 -> 자식객체로 다운캐스팅(형변환)할 때 에러 발생하는 이유
> 업캐스팅을 한 객체를 다운캐스팅 가능(부모가 자식이 될 수 없음)
ex)
코드)
5. 오버로딩과 [오버라이딩(overriding)]
- 오버로딩 : 중복된 함수명을 사용하고 매개변수의 타입,갯수가 다른 것
- 오버라이딩 : 부모의 메서드를 재정의하는 것
> 자바는 모든 부모 클래스의 인스턴스 메서드를 자식 클래스에서 재정의(오버라이딩) 할 수 있다.
(단, final 키워드가 붙으면 오버라이딩 할 수 없음)
6. final이 붙는 경우 3가지
1) final 클래스 선언 => 자식클래스(subclass)를 가질 수 없는 최종 클래스 ex.SalesMan 클래스
2) final 변수 선언 => 상수 : final은 상수 -> 한번 초기화하면 값을 바꿀 수 없음
3) final 메서드 선언 => 자식클래스(subclass)에서 오버라이딩 할 수 없다.
* final 변수는 꼭 초기화 값을 할당해 주어야한다.
> 에러메시지 : The type XXX cannot subclass the final class SalesMan
> 해석 : SalesMan은 최종 클래스이기 때문에 자식 클래스를 가질 수 없다.
public final class SalesMan extends Regular { }
class XXX extends SalesMan{ }
3,4,5,6 관련 코드 예제)
1. Employee 클래스 - 사원이라면 공통적으로 가지고 있어야할 멤버(속성==필드, 기능==메서드)
2. Employee 클래스를 상속받은 Regular 정규직 사원 클래스 - 정규직이라면 가지고 있어야할 멤버
3. Regular 클래스를 상속받은 SelesMan 영업직 사원 클래스 - 영업직이라면 가지고 있어야할 멤버
4. Employee 클래스를 상속받은 Temp 임시직 사원 클래스 - 임시직(일용직, 계약직)
Employee 클래스)
package days17;
// 사원 클래스 - 사원이라면 공통적으로 가지고 있어야할 멤버(필드, 메서드)를 구현한 클래스
public class Employee { // extends Object 라는 코딩이 되어있지만 안보이는 상태
// [필드]
private String name; // 사원명
private String addr; // 사원주소
private String tel; // 연락처
private String hiredate; // 입사일자
// [getter, setter]
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getHiredate() {
return hiredate;
}
public void setHiredate(String hiredate) {
this.hiredate = hiredate;
}
// [생성자]
// 디폴트 생성자
public Employee() {
System.out.println("> Employee 디폴트 생성자 호출됨.");
}
// 생성자 4
public Employee(String name, String addr, String tel, String hiredate) {
// super();
this.name = name;
this.addr = addr;
this.tel = tel;
this.hiredate = hiredate;
System.out.println("> Employee 생성자 4 호출됨.");
}
// [메서드]
// 1. 사원 정보를 출력하는 메서드(인스턴스 메서드)
public void dispEmpInfo() {
System.out.printf("사원명 : %s, 사원주소 : %s, 연락처 : %s, 입사일자 : %s\n", this.name, this.addr, this.tel, this.hiredate);
}
} // Employee class
Regular 클래스) Employee 상속
package days17;
// 정규직 사원 클래스
public class Regular extends Employee {
// [필드] - Employee - name, addr, tel, hiredate 상속됨.
private int pay; // 기본급
// [생성자 오버로딩(overloading) == 중복함수]
// 생성자, 초기화 블럭은 상속되지 않는다.
public Regular() {
System.out.println("> Regular 디폴트 생성자 호출됨.");
}
public Regular(String name, String addr, String tel, String hiredate, int pay) {
// 각각의 필드 초기화
// 에러메시지 : The field Employee.name [is not visible] -> 멤버 변수 접근지정자 문제(private 선언했기때문에) -> getter,setter 생성
// this.name = name;
// ㄱ. 해결 : getter, setter 생성
/*
this.setName(name); // Ex08에서 이름이 나옴
this.setAddr(addr);
this.setTel(tel);
this.setHiredate(hiredate);
*/
// ㄴ. 해결 : p332 super() - 조상 클래스의 생성자
// super 키워드 정의와 3가지용도?
// 정의 : 클래스 자기 자신의 부모 주소값을 갖는 참조변수 == super 키워드(예약어)
// 2번째 용도 : 생성자에서 또 다른 부모 생성자를 호출할 때의 super();
// 다른 코딩보다 항상 첫번째 라인에 있어야 한다.
super(name, addr, tel, hiredate); // this() 코딩처럼 자식의 생성자에서 부모의 4개짜리 생성자를 호출하는 코딩(Regular에서 Employee)
this.pay = pay;
System.out.println("> Regular 생성자 5 호출됨.");
}
// [Ex08_03 final 설명]
// public final void dispEmpInfo() {
// Employee 클래스에서 메서드에 final을 붙이면 아래와 같은 에러메시지 발생
// 에러메시지 : Cannot override the final method from Employee
// 해석 : Employee에 있는 final 메서드는 재정의 할 수 없다.
// [메서드] - Employee - dispEmpInfo() 상속됨.
// 오버라이딩(overriding) == 부모의 메서드 재정의
@Override // 어노테이션(Annotation) : 부모의 것을 물려받아 재정의 했다라는 뜻
public void dispEmpInfo() {
// 사용 못함 : this.name, this.addr, this.tel, this.hiredate);
// 이유 : private 멤버(필드)이기 때문에 접근(Access) 못한다.
super.dispEmpInfo(); // name, arrd, tel, hire 출력 -> super 첫번째 용도 : 부모의 멤버를 가리킬 때의 super
System.out.printf("기본급 : %d\n", this.pay);
}
/*
// 오버로딩 문제점 - 매개변수 갯수, 타입이 달라야 된다.
// 쓸데없이 매개변수를 줄수는 없이 오버로딩은 하지않겠다.
public void dispEmpInfo() {
System.out.printf("사원명 : %s, 사원주소 : %s, 연락처 : %s, 입사일자 : %s\n", this.name, this.addr, this.tel, this.hiredate);
}
*/
// 급여 계산하는 메서드
public int getPay() {
return this.pay; // 정규직 사원은 기본급(pay)
}
} // Regular class
SalesMan 클래스) final 클래스 + Regular 클래스 상속
package days17;
// 영업직 사원 클래스
// 자식클래스를 가질 수 없는 최종(마지막) 클래스 입니다라고 선언할 때 final 키워드를 클래스 선언 부분 앞에 붙인다.
public final class SalesMan extends Regular {
// [필드] - E(n,a,t,h) / R(p) 상속되어짐
private int sales; // 판매량
private int comm; // 커미션
// [생성자]
// 디폴트 생성자
public SalesMan() {
System.out.println("> SalesMan 디폴트 생성자 호출됨.");
}
// 생성자 7
public SalesMan(String name, String addr, String tel, String hiredate, int pay, int sales, int comm) {
super(name, addr, tel, hiredate, pay);
this.sales = sales;
this.comm = comm;
System.out.println("> SalesMan 생성자 7 호출됨.");
}
// [메서드] - E(dispEI()) / R(dispEI(재정의), getPay()) / getter,setter 상속되어짐
// Employee의 dispEmpInfo() -> Regular의 dispEmpInfo() + 기본급 재정의 -> SalesMan의 dispEmpInfo() + 판매량, 커미션 재정의
@Override
public void dispEmpInfo() {
// TODO Auto-generated method stub
super.dispEmpInfo(); // Regular의 dispEmpInfo()의 함수(기본급까지 출력하는)
System.out.printf("판매량 : %d, 커미션 : %d\n", this.sales, this.comm);
}
@Override // 영업직의 급여 계산
public int getPay() {
return super.getPay() + this.comm * this.sales; // getPay()는 기본급
}
} // SalesMan class
Temp 클래스)
package days17;
// 임시직 사원 클래스
public class Temp extends Employee{
// [필드] - E(n,a,t,h)상속되어짐
private int days; // 근무일수
private int payOfDay; // 하루 일당
// [생성자]
// 디폴트 생성자
public Temp() {
System.out.println("> Temp 디폴트 생성자 호출됨.");
}
// 생성자 6
public Temp(String name, String addr, String tel, String hiredate, int days, int payOfDay) {
super(name, addr, tel, hiredate);
this.days = days;
this.payOfDay = payOfDay;
System.out.println("> Temp 생성자 6 호출됨.");
}
// [메서드] - E(dispEI()) 상속되어짐
@Override
public void dispEmpInfo() {
super.dispEmpInfo();
System.out.printf("근무일수 : %d, 하루일당 : %d\n", this.days, this.payOfDay);
}
public int getPay() {
return this.days * this.payOfDay; // 임시직 급여 = 근무일수 * 하루일당
}
} // Temp class
main() 메서드)
'TIL > Java' 카테고리의 다른 글
[SIST] Java_days19 (0) | 2022.03.16 |
---|---|
[SIST] Java_days18_OOP관련 (0) | 2022.03.15 |
[SIST] Java_days16 (0) | 2022.03.11 |
[SIST] Java_days15 (0) | 2022.03.10 |
[SIST] Java_days14 (0) | 2022.03.08 |