클래스를 재사용하는 방법 中 하나!
자식(클래스)이 상속받고싶은 부모(클래스)를 선택해서 물려받는다.
상속의 사용조건
무작정 상속을 사용할 경우,
클래스 간 결합도가 너무 높아져서, 유지 보수 효율 ↓
IS-A
- 포함 관계
- 한 클래스 A가 다른 클래스 B의 자식 클래스 다.
- 예시 : 햄스터는 동물이다. (소의 부모 클래스는 동물)
HAS-A
상속이 아닌구성 (Composition) 관계- 한 객체가 다른 객체에 속한다.
- 예시 : 컴퓨터 안에는 CPU 가 있다. (컴퓨터 객체가 CPU 객체를 구성함)
부모 클래스는 여러 개의 자식 클래스에게 상속 O
자식 클래스는 여러 부모로부터 다중 상속 받는 것은 X
부모 클래스는 자식 클래스에서 정의한 메소드나 필드를 사용하지 못한다. (자식 클래스는 부모 클래스에 영향 X)
접근 제한자
1) 부모 클래스에서 private 접근 제한을 갖는 필드, 메소드는 X
2) 부모 - 자식 클래스가 다른 패키지에 있는 경우, default 접근 제한을 갖는 필드, 메소드
참고: 캡슐화, 접근 제한자
문법
public class 부모클래스 {
...
}
public class 자식클래스 extends 부모클래스 {
...
}
extends
부모 클래스 CellPhone
public class CellPhone {
// 필드
String model;
String color;
}
자식 클래스 DmbCellPhone
public class DmbCellPhone extends CellPhone {
// 필드
int channel;
// 생성자
DmbCellPhone (String model, String color, int channel) {
this.model = model; // CellPhone 클래스로부터 상속받은 필드
this.color = color; // CellPhone 클래스로부터 상속받은 필드
this.channel = channel;
}
}
// 조부모 클래스
class A {
String aField = "클래스 A 필드";
public void aMethod() {
System.out.println(aField);
// System.out.println("A : "+ bField); // 컴파일 에러(자식 필드 사용 불가)
}
}
// 부모 클래스
class B extends A {
String bField = "클래스 B 필드";
public void bMethod() {
System.out.println(aField); // 부모 클래스 필드 사용
System.out.println(bField); // 본인 클래스 필드 사용
// System.out.println("B : "+cField); // 컴파일 에러(자식 필드 사용 불가)
}
}
// 자식 클래스
class C extends B {
String cField = "클래스 C 필드";
public void cMethod() {
System.out.println(aField); // 조부모 클래스 필드 사용
System.out.println(bField); // 부모 클래스 필드 사용
System.out.println(cField); // 본인 클래스 필드 사용
}
}
object 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상 클래스
따로 extends object를 추가해주지 않아도, 컴파일러가 자동 추가한다
메서드 : totring(), equals(Object o) 등...
부모 클래스
class Parent {
int x = 10; // super.x
}
자식 클래스
class Child extends Parent {
int x = 20; // this.x -> 부모 클래스에서의 int x 와 이름이 동일하다
...
}
부모 클래스
class Point {
int x;
int y;
Point (int x, int y) {
this.x = x;
this.y = y;
}
}
자식 클래스
class Point3D extends Point {
int z;
Point3D (int x, int y, int z) {
this.x = x;
this.y = y;
tihs.z = z;
}
}
// super() 를 사용해서 고쳐보면
class Point3D extends Point {
int z;
Point3D (int x, int y, int z) {
super(x, y); // 부모 클래스의 생성자인 Point (int x, int y) 를 호출해서 초기화
tihs.z = z; // 자신의 멤버를 초기화
}
}
super(매개값, ...) 으로 부모 생성자 호출이 가능
단, 생성자는 상속되지 않는다!
객체: Bird, Insect, Fish
특성: 필드, 메소드
추상 클래스 : 이 클래스들의 공통적 특성을 추출하여 선언한 클래스
실체 클래스 : 객체를 직접 생성할 수 있는 클래스
즉,
Bird.class, Insect.class, Fish.class 의 공통적인 필드와 메소드를 따로 선언한 Animal.class 만드는 것
= 추상 클래스
abstract 키워드 붙이기
new 연산자 사용 X
→ 상속을 위한 클래스이므로, 따로 객체 생성이 불가능
→ 상속을 통해, 자식 클래스 생성은 가능
클래스 = "설계도"
추상 클래스 = "미완성의 설계도"
추상화 ↔ 상속
하나의 메서드라도 추상 메서드가 되면, 해당 클래스는 추상 클래스가 되어야 함
public abstract class Bird { // 추상 클래스
public abstract void sing(); // 추상 메서드
}
그러나, 추상 메서드라고 해도, 일반 메서드도 사용 가능하다
public abstract class Bird { // 추상 클래스
public abstract void sing(); // 추상 메서드
public void fly(); // 일반 메서드
}
abstract class EX { // 추상 클래스
abstract void move(); // 추상 메서드
}
"abstract" = "미완성의"
추상 클래스 : 클래스 내에 추상 메서드가 선언됐음을 의미 -> 미완성 설계도 (인스턴스 생성 불가)
추상 메서드 : 선언부만 작성 O. 구현부는 아직 작성 x
공통된 필드, 메소드의 이름 통일
(단, 데이터와 기능일 동일함에도, 이름이 다른 경우도 있음)
실체 클래스 작성 시, 시간 절약
→ 공통된 필드, 메소드 : 추상 클래스에 모두 선언
→ 다른 부분만 : 실체 클래스에 선언
부모 클래스
abstract class Player { // 추상 클래스
abstract void play(int pos); // 추상 메서드
abstract void stop(); // 추상 메서드
}
자식 클래스
class AudioPlayer extends Player {
void play(int pos) { // 추상 메서드 구현
...
}
void stop() { // 추상 메서드 구현
...
}
}
자식 클래스
abstract class AbstractPlayer extends Player {
void play(int pos) { // 추상 메서드 구현
...
}
}
public class Marine {
int x, y; // 현재 위치
void move(int x, int y) {
}
void stop() {
}
void simPack() {
}
}
Tank 클래스
public class Tank {
int x, y; // 현재 위치
void move(int x, int y) {
}
void stop() {
}
void changeMode() {
}
}
Dropship 클래스
public class Dropship {
int x, y; // 현재 위치
void move(int x, int y) {
}
void stop() {
}
void load() {
}
void unload() {
}
}
public class Unit {
int x, y;
abstract void move(int x, int y);
void stop() {
}
}
Marine 자식 클래스
public class Marine extends Unit {
void move(int x, int y) {
}
void simPack() {
}
}
Tank 자식 클래스
public class Tank extends Unit {
void move(int x, int y) {
}
void changeMode() {
}
}
Dropship 자식 클래스
public class Dropship extends Unit {
void move(int x, int y) {
}
void load() {
}
void unload() {
}
}
Animal 추상 클래스 - sound() 메소드
동물들은 소리를 낸다. → 공통된 특성
동물들은 다양한 소리를 낸다. → 실체 클래스마다 차이점
하위 클래스에 강제하고 싶은 메소드가 있을 경우, 해당 메소드를 추상 메소드로 선언
→ 자식 클래스는 추상 메소드를 재정의 해서 사용해야함 (그렇지 않을 시, 컴파일 에러)
예시
부모 클래스
public abstract class Animal {
//필드
public String kind;
//메소드
public void breathe() {
...
}
public abstract void sound(); //추상 메소드
}
자식 클래스 1
public class Dog extends Animal {
public Dog() {
this.kind = "포유류";
}
//추상 메소드 재정의
//이 부분이 없다면, 컴파일 에러 발생
@Override
public void sound() {
...
}
}
자식 클래스 2
public class Cat extends Animal {
public Cat() {
this.kind = "포유류";
}
//추상 메소드 재정의
//이 부분이 없다면, 컴파일 에러 발생
@Override
public void sound() {
...
}
}
AnimalExample
public class AnimalExample {
public static void main(String[] args) {
// Dog, Cat 변수 호출
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound();
cat.sound();
System.out.println("-----");
// 변수의 자동 타입 변환 (Dog, Cat 자식 -> Animal 부모)
Animal animal = null;
animal = new Dog(); //변수 animal 로 타입 변환
animal.sound(); //자식 클래스 Dog 의 sound() 메소드 호출
animal = new Cat(); //변수 animal 로 타입 변환
animal.sound(); //자식 클래스 Cat 의 sound() 메소드 호출
System.out.println("------");
// 메소드의 다형성
animalSound(new Dog()); // animalSound() 라는 새로운 메소드를 선언 (animalSound()는 잠깐 사용하는 새로운 그릇일 뿐)
animalSound(new Cat()); // animalSound() 라는 새로운 메소드를 선언
}
// 자동 타입 변환
// animalSound() 메소드 안에 있던 자식 객체 Dog, Cat 을 --> animalSound() 메소드 안의 부모 타입의 매개변수 animal 에 대입
public static void animalSound(Animal animal) {
// 부모 타입으로 자동 타입 변환되어 재정의된 sound() 메소드 호출
animal.sound();
}
}
참고: 인터페이스 (interface) - 4. 인터페이스 VS 추상 클래스
부모 클래스
Class Animal {
...
}
자식 클래스
Class Cat extends Animal {
...
}
AnimalExample
public class AnimalExample {
...
/* Animal animal = new Cat(); 도 가능 → 상속을 통해 생성했기 때문 */
Cat cat = new Cat(); // 1. Cat 객체 생성
Animal animal = cat // 2. cat 객체를 Animal 변수에 대입 → 자동으로 타입 변환
}
cat 변수 와 animal 변수 모두, 동일한 객체 Animal 을 참조
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b;
A a2 = c
B b1 = d;
C c1 = e;
B b3 = e; //불가능: e를 b에 대입하지 못한다
C c2 = d; //불가능: d를 c에 대입하지 못한다
D 객체 --> B 타입, A 타입으로 자동 타입 변환 O
E 객체 --> C 타입, A 타입으로 자동 타입 변환 O
D 객체 --> C 타입 자동 타입 변환 X (상속 관계가 아니므로)
E 객체 --> B 타입 자동 타입 변환 X
자동 타입 변환 이후에 → 강제 타입 변환 가능
단, 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환 X
(부모 클래스(Parent), 자식 클래스(Child) 모두 있었다고 가정한다.)
ChildExample
public class ChildExample {
public static void main(String[] args) {
Parent parent = new Child(); // 자동 타입 변환(부모 -> 자식)
parent.field1 = "data1";
parent.method1();
parent.method2();
// 불가능한 경우
parent.field2 = "data2";
parent.method3();
// 가능한 경우
Child child = (Child) parent; // 강제 타입 변환(자식 -> 부모)
child.field2 = "yy";
child,method3();
}
}
객체의 타입 확인 : instanceof 활용
// 문법 boolean result = 객체 instanceof 타입 // 예시 public void method (Parent parent) { if (parent instanceof Child) { // 객체: parent, 타입: Child Child child = (Child) parent; } }
매개값 parent 가 Parent 객체인지, Child 객체인지 연산자로 확인 필요
만약,
우측(Child)로 객체 생성했다면 true
좍측(parent)로 객체 생성했다면 false주의!
타입 확인하지 않고 강제 타입 변환 시, ClassCastException 발생
참고: OOP (Object-Oriented Programming, 객체 지향 프로그래밍) - 4) 다형성 (Polymorphism)
사용 방법은 동일하나, 다양한 객체를 이용해서 다양한 실행결과가 나오도록하는 성질
객체 생성 : 부모 클래스
Tire
public class Tire {
//필드
public int maxRotation; //최대 회전수
public int accumulatedRotation; //누적 회전수
public String location; //타이어 위치
//생성자
public Tire (String location, int maxRotation) {
this.maxRotation = maxRotation; //매개 변수 maxRotation 를 받아서, 필드 this.maxRotation 에 저장
this.location = location; //매개 변수 location 를 받아서, 필드 this.location 에 저장
}
//메소드
public boolean roll() {
++accumulatedRotation; //roll() 메소드를 1번 실행시킬때마다, accumulatedRotation 를 1씩 증가시킴
//roll() 메소드의 실행결과 1
if (accumulatedRotation < maxRotation) { //그러다가, accumulatedRotation < maxRotation 인 경우라면
System.out.println(location + " Tire 수명: " + (maxRotation - accumulatedRotation) + "회");
return true;
//roll() 메소드의 실행결과 2
} else { //그러다가, accumulatedRotation < maxRotation 가 아닌 경우(accumulatedRotation = maxRotation)라면
System.out.println("*** " + location + " Tire 펑크 ***");
return false;
}
}
}
Car
public class Car {
//필드
//4개의 타이어를 가짐
Tire frontLeftTire = new Tire ("앞왼쪽", 6);
Tire frontRightTire = new Tire ("앞오른쪽", 6);
Tire backLeftTire = new Tire ("뒤왼쪽", 6);
Tire backRightTire = new Tire ("뒤오른쪽", 6);
//생성자
//메소드 1
//각 Tire 객체의 roll() 메소드를 호출해서, roll() 메소드가 false 를 리턴한다면, stop() 메소드를 호출하고, 1을 리턴
int run() {
System.out.println("[자동차가 달립니다.]");
if (frontLeftTire.roll() == false) { //Tire 객체의 roll() 메소드를 호출해서, roll() 메소드가 false 를 리턴한다면
stop(); //stop() 메소드를 호출하고
return 1; //1을 리턴
}
if (frontRightTire.roll() == false) { //Tire 객체의 roll() 메소드를 호출해서, roll() 메소드가 false 를 리턴한다면
stop(); //stop() 메소드를 호출하고
return 2; //2를 리턴
}
if (backLeftTire.roll() == false) {
stop();
return 3;
}
if (backRightTire.roll() == false) {
stop();
return 4;
}
return 0;
}
//메소드 2: 펑크 났을 때 실행
void stop() {
System.out.println("[자동차가 멈춥니다.]");
}
}
자식 클래스
Tire 클래스의 자식 클래스 1
public class HanKookTire extends Tire {
//필드
//생성자
public HanKookTire (String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
//roll() 메소드의 재정의로 인해, Tire 클래스에서의 roll() 메소드의 출력 결과와는 달라지게 됨
@Override
public boolean roll() {
++accumulatedRotation;
if (accumulatedRotation < maxRotation) {
System.out.println(location + " HankookTire 수명: " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " HanKookTire 펑크 ***");
return false;
}
}
}
Tire 클래스의 자식 클래스 2
public class KumhoTire extends Tire {
//필드
//생성자
public KumhoTire(String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
//roll() 메소드의 재정의로 인해, Tire 클래스에서의 roll() 메소드의 출력 결과와는 달라지게 됨
@Override
public boolean roll() {
++accumulatedRotation;
if (accumulatedRotation < maxRotation) {
System.out.println(location + " HankookTire 수명: " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " HanKookTire 펑크 ***");
return false;
}
}
}
CarExample
public class CarExample {
public static void main(String[] args) {
//Car 객체 생성
Car car = new Car();
//for 문을 통해, Car 객체의 run() 메소드 5번 실행
for (int i = 1; i <= 5; i++) {
int problemLocation = car.run();
switch (problemLocation) {
case 1:
System.out.println("앞왼쪽 HanKookTire 로 교체"); //앞외쪽 Tire 가 펑크 났을 때,
car.frontLeftTire = new HanKookTire ("앞왼쪽", 15); //HanKookTire 로 교체
break;
case 2:
System.out.println("앞오른쪽 KumhoTire 로 교체"); //앞오른쪽 Tire 가 펑크 났을 때,
car.frontRightTire = new KumhoTire ("앞오른쪽", 15); //KumhoTire 로 교체
break;
case 3:
System.out.println("뒤왼쪽 HanKookTire 로 교체");
car.backLeftTire = new HanKookTire ("뒤왼쪽", 15); //Car 객체의 backLeftTire 필드에 HanKookTire 객체를 대입 (= 자동 타입 변환)
break; //--> HanKookTire 클래스의 재정의된 메소드 roll() 를 실행
case 4:
System.out.println("뒤오른쪽 KumhoTire 로 교체");
car.backRightTire = new KumhoTire ("뒤오른쪽", 15); //Car 객체의 backRightTire 필드에 KumhoTire 객체를 대입 (= 자동 타입 변환)
break; //--> KumhoTire 클래스의 재정의된 메소드 roll() 를 실행
}
System.out.println("------"); //for 문을 1회 돌린 후, 출력될 내용
}
}
}
모든 자동차는 타이어를 사용하는데, 한국 타이어(객체)와 금호 타이어(객체)의 성능은 다르게 나온다.
객체 생성 : 부모 클래스
Vehicle
public class Vehicle {
public void run() {
System.out.println("차량이 달립니다.");
}
}
Driver
public class Driver {
public void drive(Vehicle vehicle) {
//Vehicle 타입의 매개변수 vehicle 를 받아서, run() 메소드 실행
//
vehicle.run();
}
}
자식 클래스
Vehicle 클래스의 자식 클래스 1
public class Bus extends Vehicle {
@Override
public void run() { //Vehicle 클래스의 run() 메소드 재정의
System.out.println("버스가 달립니다.");
}
}
Vehicle 클래스의 자식 클래스 1
public class Taxi extends Vehicle {
@Override
public void run() { //Vehicle 클래스의 run() 메소드 재정의
System.out.println("택시가 달립니다.");
}
}
DriverExample
public class DriverExample {
public static void main(String[] args) {
//객체 생성
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
//Driver 객체의 driver() 메소드 호출 + Bus 객체 제공
driver.drive(bus);
//Driver 객체의 driver() 메소드 호출 + taxi 객체 제공 --> Taxi 객체의 run() 메소드 실행
driver.drive(taxi);
}
}
실행결과
버스가 달립니다.
택시가 달립니다.
참고: 오버로딩, 오버라이딩 - 오버라이딩 (overriding)
코드 중복 ↓
→ 이미 잘 개발된 클래스를 재사용해서, 새로운 클래스를 만들기 때문
유지 보수 시간 최소화
→ 부모 클래스 수정은 곧 모든 자식 클래스들의 수정이기도 하기 때문
다형성 구현
통일성 있는 코드