기존 클래스(부모)의 필드와 메서드를 자식 클래스가 물려받는 것이다.
extends 키워드로 선언하며, 자바는 단일 상속만 지원한다.
Employee (부모)
↑ extends
Engineer, Manager (자식)
상속이 의미 있으려면 IS-A 관계가 성립해야 한다.
Engineer IS-A Employee Manager IS-A Employee자바의 모든 클래스는 Object를 암묵적으로 상속한다.
extends를 명시하지 않아도 컴파일러가 자동으로 extends Object를 삽입한다.
public class Employee { }
// 컴파일러가 실제로 보는 것 ↓
public class Employee extends Object { }
따라서 실제 상속 구조는 다음과 같다.
Object
↑ (암묵적 extends)
Employee
↑ extends
Engineer, Manager
Object가 루트이기 때문에 모든 클래스는 Object의 메서드를 기본으로 갖는다.
| Object 주요 메서드 | 설명 |
|---|---|
toString() | 객체를 문자열로 표현 |
equals() | 객체 동등 비교 |
hashCode() | 해시값 반환 |
getClass() | 런타임 클래스 정보 반환 |
업로드 코드에서 Employee, Engineer, Manager 모두 toString()을 오버라이딩하는데,
이게 바로 Object의 toString()을 재정의하는 것이다.
부모 클래스에 정의된 메서드를 자식 클래스에서 같은 시그니처로 재정의하는 것이다.
조건 3가지
1. 메서드 이름이 동일
2. 매개변수 타입/개수가 동일
3. 반환 타입이 동일 (또는 하위 타입)
// Object의 toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// Employee에서 오버라이딩
@Override
public String toString() {
return "empno=" + empno + ", name=" + name + ", salary=" + salary;
}
@Override는 문법적으로 필수는 아니지만, 컴파일러가 오버라이딩 조건을 검증해주므로 붙이자. 시그니처를 잘못 작성하면 컴파일 오류로 잡아준다.
Object.toString() → (의미없는 주소)
↑ 오버라이딩
Employee.toString() → empno=D002, name=그레이몬, salary=3500000
↑ 오버라이딩 + super.toString() 재활용
Engineer.toString() → empno=D002, name=그레이몬, salary=3500000, skill=불꽃폭발
super.toString()을 호출하면 부모가 이미 만들어 둔 문자열을 그대로 재활용하고
자식은 자신의 필드만 추가하면 된다. 중복 코드 없이 계층적으로 확장되는 구조다.
| 항목 | 내용 |
|---|---|
extends | 단일 상속만 가능 |
super() | 부모 생성자 호출, 반드시 첫 줄 |
super.메서드() | 부모의 메서드 호출 |
| Object | 모든 클래스의 루트, 명시 없어도 자동 상속 |
| 오버라이딩 조건 | 이름/매개변수/반환타입 동일 |
@Override | 오버라이딩 조건을 컴파일러가 검증 |
toString() | Object 메서드, 재정의하지 않으면 참조값 출력 |
package chapter06;
public class Employee {
private String empno;
private String name;
private int salary;
public Employee() {}
public Employee(String empno, String name, int salary) {
super(); // Object() 호출
this.empno = empno;
this.name = name;
this.salary = salary;
}
public String getEmpno() { return empno; }
public String getName() { return name; }
public int getSalary() { return salary; }
public void setEmpno(String empno) { this.empno = empno; }
public void setName(String name) { this.name = name; }
public void setSalary(int salary) {
if (salary < 2000000)
this.salary = 2000000;
else
this.salary = salary;
}
// Object의 toString()을 오버라이딩
@Override
public String toString() {
return "empno=" + empno + ", name=" + name + ", salary=" + salary;
}
}
package chapter06;
public class Engineer extends Employee {
private String skill;
public Engineer() {}
public Engineer(String empno, String name, int salary, String skill) {
super(empno, name, salary); // Employee 생성자 호출
this.skill = skill;
}
public String getSkill() { return skill; }
public void setSkill(String skill) { this.skill = skill; }
// Employee의 toString()을 오버라이딩
// super.toString()으로 부모 결과를 재활용하고 자식 필드를 추가
@Override
public String toString() {
return super.toString() + ", skill=" + skill;
}
}
package chapter06;
public class Manager extends Employee {
private String position;
Manager() {}
public Manager(String empno, String name, int salary, String position) {
super(empno, name, salary); // Employee 생성자 호출
this.position = position;
}
public String getPosition() { return position; }
public void setPosition(String position) { this.position = position; }
// Employee의 toString()을 오버라이딩
@Override
public String toString() {
return super.toString() + ", position=" + position;
}
}
package chapter06;
import java.util.Arrays;
public class EmployeeManager {
private Employee[] emps;
private int empIndex;
public EmployeeManager() {
emps = new Employee[10];
}
public int findIndex(String empno) {
if (empno != null) {
for (int i = 0; i < empIndex; i++) {
if (empno.equals(emps[i].getEmpno())) {
return i;
}
}
}
return -1;
}
public Employee findEmployee(String empno) {
int index = findIndex(empno);
if (index > -1) return emps[index];
return null;
}
public void add(Employee emp) {
if (emp != null) {
int index = findIndex(emp.getEmpno());
if (index > -1) {
System.err.printf("%s번은 이미 등록된 사원번호입니다.", emp.getEmpno());
} else {
if (empIndex >= emps.length) {
emps = Arrays.copyOf(emps, empIndex + 5);
}
emps[empIndex++] = emp;
}
}
}
public void displayEmp() {
System.out.println("총 등록 수: " + empIndex);
for (int i = 0; i < empIndex; i++) {
System.out.println(emps[i].toString());
}
}
}
package chapter06;
public class MainEmp {
public static void main(String[] args) {
// 기본 Employee 객체
Employee e1 = new Employee("D001", "걍사원아구몬", 2000000);
System.out.println("=== Employee (기본 Employee 객체)===");
System.out.println(e1);
/* 출력결과
=== Employee (기본 Employee 객체)===
empno=D001, name=걍사원아구몬, salary=2000000
*/
// Engineer 객체
Employee e2 = new Engineer("D002", "엔지니어그레이몬", 3500000, "불꽃폭발");
System.out.println("\n=== Engineer (Engineer 객체)===");
System.out.println(e2);
/* 출력결과
=== Engineer (Engineer 객체)===
empno=D002, name=엔지니어그레이몬, salary=3500000 skill=불꽃폭발
*/
// Manager 객체
Employee e3 = new Manager("D003", "매니저워그레이몬", 5000000, "팀장");
System.out.println("\n=== Manager (Manager 객체)===");
System.out.println(e3);
/* 출력결과
=== Manager (Manager 객체)===
empno=D003, name=매니저워그레이몬, salary=5000000 position=팀장
*/
// salary 최솟값 보정 확인
Employee e4 = new Engineer("D004", "엔지니어아구몬주니어", 1000000, "아기불꽃");
System.out.println("\n=== salary 보정 확인 (salary 최솟값 보정 확인) ===");
System.out.println(e4);
/* 출력결과
=== salary 보정 확인 (salary 최솟값 보정 확인) ===
empno=D004, name=엔지니어아구몬주니어, salary=2000000 skill=아기불꽃
*/
// EmployeeManager에 등록
EmployeeManager empM = new EmployeeManager();
empM.add(e1);
empM.add(e2);
empM.add(e3);
empM.add(e4);
System.out.println("\n=== 전체 출력 ===");
empM.displayEmp();
/* 출력결과
=== 전체 출력 ===
총 등록 수: 4
empno=D001, name=걍사원아구몬, salary=2000000
empno=D002, name=엔지니어그레이몬, salary=3500000 skill=불꽃폭발
empno=D003, name=매니저워그레이몬, salary=5000000 position=팀장
empno=D004, name=엔지니어아구몬주니어, salary=2000000 skill=아기불꽃
*/
// 중복 등록 시도
System.out.println("\n=== 중복 등록 시도 ===");
empM.add(new Engineer("D002", "엔지니어피카츄", 4000000, "번개"));
/* 출력결과
=== 중복 등록 시도 ===
D002번은 이미 등록된 사원번호입니다.
*/
// Object가 루트임을 확인 - 모든 클래스는 Object를 상속하므로 Object 변수에 담을 수 있다
System.out.println("\n=== Object 루트 확인 ===");
Object o1 = new Employee("D005", "걍사원피카츄", 2200000);
Object o2 = new Engineer("D006", "엔지니어피카츄", 3800000, "번개");
Object o3 = new Manager("D007", "매니저피카츄", 5500000, "부팀장");
// o1.toString() → Object 변수지만 실제 타입의 toString()이 호출됨
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
/* 출력결과
=== Object 루트 확인 ===
empno=D005, name=걍사원피카츄, salary=2200000
empno=D006, name=엔지니어피카츄, salary=3800000 skill=번개
empno=D007, name=매니저피카츄, salary=5500000 position=부팀장
*/
// getClass()는 Object의 메서드 → 모든 클래스에서 사용 가능
System.out.println("\n=== getClass() 확인 ===");
System.out.println(e2.getClass().getName());
System.out.println(e3.getClass().getName());
/* 출력결과
=== getClass() 확인 ===
chapter06.Engineer
chapter06.Manager
*/
}
}
=== Employee (기본 Employee 객체)===
empno=D001, name=걍사원아구몬, salary=2000000
=== Engineer (Engineer 객체)===
empno=D002, name=엔지니어그레이몬, salary=3500000 skill=불꽃폭발
=== Manager (Manager 객체)===
empno=D003, name=매니저워그레이몬, salary=5000000 position=팀장
=== salary 보정 확인 (salary 최솟값 보정 확인) ===
empno=D004, name=엔지니어아구몬주니어, salary=2000000 skill=아기불꽃
=== 전체 출력 ===
총 등록 수: 4
empno=D001, name=걍사원아구몬, salary=2000000
empno=D002, name=엔지니어그레이몬, salary=3500000 skill=불꽃폭발
empno=D003, name=매니저워그레이몬, salary=5000000 position=팀장
empno=D004, name=엔지니어아구몬주니어, salary=2000000 skill=아기불꽃
=== 중복 등록 시도 ===
D002번은 이미 등록된 사원번호입니다.
=== Object 루트 확인 ===
empno=D005, name=걍사원피카츄, salary=2200000
empno=D006, name=엔지니어피카츄, salary=3800000 skill=번개
empno=D007, name=매니저피카츄, salary=5500000 position=부팀장
=== getClass() 확인 ===
chapter06.Engineer
chapter06.Manager