상속

이동주·2025년 3월 6일

JAVA

목록 보기
17/30

상속

  • 부모 클래스의 필드와 메소드를 자식 클래스에게 물려줄 수 있음

  • 재사용성이 상속의 목적임

  • 이미 개발된 클래스를 재사용하므로 중복 코드를 줄일 수 있으며, 클래스 수정을 최소화함

  • 상속의 구조

클래스 상속 및 부모생성자 호출

  • 자식 클래스를 선언할 때 어떤 부모클래스에서 상속받을지 extends 뒤에 적어주면 됨
  • 다중 상속은 허용하지 않음
  • 자식 객체를 생성하기 위해서는 부모 객체를 먼저 생성해야 함
  • 콜 스택 : 호출되서 함수가 쌓여있는 스택
  • 자식클래스에서 부모클래스를 명시적으로 호출하기 위해서는 super();를 써줌
  • 구조
 // 부모 클래스
 class A{
 }
 
 // 자식 클래스
 class B extends A{
 	super(); // 부모클래스를 호출하는 기능
 }
 // B클래스는 A클래스를 상속받음
 
 class C extends A,B{ // X
 }
 //부모클래스는 반드시 하나의 부모클래스에서만 상속받음

메소드 호출 예제

  • 부모 클래스 (Phone)
package ch07.sec02;

public class Phone {
	public String model;
	public String color;
	// 필드는 private로 생성하는 것이 좋음
	
	public void bell() {
		System.out.println("벨이 울립니다");
	}
	// bell() 기본메소드 생성
	
	public void sendVoice(String message) {
		System.out.println("자기: " + message);
	}
	// sendVoice() 일반메소드 생성
	
	public void receiveVoice(String message) {
		System.out.println("상대방: " + message);
	}
	// receiveVoice() 일반메소드 생성
	
	public void hangUp() {
		System.out.println("전화를 끊습니다");
	}
	// hangUp() 기본메소드 생성
}
  • 자식 클래스 (SmartPhone)
package ch07.sec02;

public class SmartPhone extends Phone{
	//Phone 객체를 상속받겠다는 의미
	//첫 줄에 숨겨져있는 super()가 상속하는 역할을 함
	//Phone 객체를 상속받았기 때문에 Phone에 있는 모든 메소드를 그대로 사용할 수 있음
	
	// 필드 선언
	public boolean wifi;
	
	// 매개변수 2개 일반생성자 선언
	public SmartPhone(String m, String c) {
		this.model = m;
		this.color = c;
		// Phone으로부터 필드를 상속 받았기 때문에
		// 그대로 사용 가능
	}
	
	// 메소드 선언
	public void setWifi(boolean wifi) {
		this.wifi = wifi;
		System.out.println("와이파이 상태를 변경하였습니다");
	}
	
	public void internet() {
		System.out.println("인터넷에 연결합니다.");
	}
}
  • 실행 클래스
package ch07.sec02;

public class SmartPhoneExam {
	public static void main(String[] args) {
		// SmartPhone 객체 생성
		SmartPhone phone = new SmartPhone("갤럭시", "은색");
		
		// Phone으로 부터 상속받은 필드값을 읽음
		// 위에 생성한 SmartPhone 객체가 Phone 객체에 상속을 받았기 때문에
		// Phone 객체에 있는 필드를 SmartPhone 객체를 통해서도 사용이 가능함
		System.out.println("모델: " + phone.model); // 갤럭시
		System.out.println("색상: " + phone.color); // 은색
		
		// SmartPhone 객체의 필드 읽기
		System.out.println("와이파이 상태: " + phone.wifi); //false
		
		// Phone 객체에 생성된 메소드 호출
		phone.bell(); // 벨이 울립니다
		phone.sendVoice("We are Landers 화이팅");
		phone.receiveVoice("우리의 함성을 하나로 모아");
		phone.sendVoice("달려가자 승리를 향해 나가자");
		phone.receiveVoice("우리의 랜더스여 워어어");
		phone.sendVoice("We are Landers 화이팅");
		phone.receiveVoice("우리의 열정을 하나로 모아");
		phone.sendVoice("날아올라 정상을 향해 나가자");
		phone.receiveVoice("우리의 랜더스여");
		phone.hangUp(); // 전화를 끊습니다
		
		// SmartPhone 객체에 생성된 메소드 호출
		phone.setWifi(true);
		phone.internet();
	}
}

super()

  • 자식 생성자에서 명시적으로 부모 생성자를 호출하는데에 사용함
  • 자식 생성자 첫 줄에 숨겨져 있기에 부모 생성자가 기본 생성자만 있을 경우 생략이 가능함
  • 부모의 객체를 자식생성자가 참조하는 것이기에 자식 생성자에서 무 조 건 가장 먼저 호출되어야 함
  • 만약 부모 생성자 중에 매개변수가 있을 경우에는 super()안에 매개값을 해당 매개변수의 타입에 맞게 넣으면 됨
  • 하지만 this를 이용하여 생성자 함수에서 생성자 함수를 호출할 때에는 super를 사용하면 안됨

부모 생성자 호출 예제 (매개변수 없음)

  • 부모 클래스 (Phone)
package ch07.sec03.exam01;

public class Phone {
	public String model;
	public String color;
	// 필드는 private로 쓰는 것이 좋음
	
	// 인자가 없는 기본메소드 생성
	public Phone() {
		System.out.println("Phone 생성자 실행");
	}
}
  • 자식 클래스 (SmartPhone)
package ch07.sec03.exam01;

public class SmartPhone extends Phone{
	// Phone 객체로부터 상속받음
	
	// String 인자가 2개인 일반생성자 선언
	public SmartPhone(String model, String color) {
		super();
		// super() : 부모 클래스를 호출하는 것을 의미
		// 인자가 없는 생성자를 실행할 때에는 생략 가능
		
		this.model = model;
		this.color = color;
		// Phone 객체에서 생성된 필드를 그대로 사용할 수 있음
		
		System.out.println("SmartPhone 생성자 실행됨");
	}
}
  • 실행 클래스
package ch07.sec03.exam01;

public class SmartPhoneExam {
	public static void main(String[] args) {
		// SmartPhone 객체 생성
		SmartPhone phone = new SmartPhone("갤럭시", "은색");
		
		// Phone 객체에서 생성한 필드값을 출력함
		// SmartPhone 객체가 Phone 객체에 상속받았기 때문에 사용 가능
		System.out.println("모델: " + phone.model); // 갤럭시
		System.out.println("색상: " + phone.color); // 은색
	}
}

부모 클래스 호출 예제 (매개변수 있음)

  • 부모 클래스 (Phone)
package ch07.sec03.exam02;

public class Phone {
	public String model; 
	public String color;
	// 필드에는 private를 사용하는 것이 좋음
	
	// 인자가 2개인 Phone 일반생성자
	public Phone(String model, String color) {
		this.model = model;
		this.color = color;
		// 매개변수를 필드에 대입
		System.out.println("Phone 생성자 실행");
	}
}
  • 자식 클래스 (SmartPhone)
package ch07.sec03.exam02;

public class SmartPhone extends Phone{
	//Phone 객체를 상속받음
	
	// String 인자가 2개인 일반생성자
	public SmartPhone(String model, String color) {
		super(model, color);
		// 부모 생성자에 인자가 있을 경우 super 생략 절대 불가
		// super에 부모 생성자의 인자 수와 타입에 맞게 넣어줌
		System.out.println("SmartPhone 생성자 실행");
	}
}
  • 실행 클래스
package ch07.sec03.exam02;

public class SmartPhoneExam {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		SmartPhone phone = new SmartPhone("갤럭시", "은색");
		// SmartPhone 객체 생성
		// 문자열 타입 인자가 2개인 일반생성자이기 때문에 인자값을 알맞게 넣어줌
		// Phone 객체를 상속받았기 때문에 Phone에 생성자도 같이 실행됨 (먼저 호출)
		
		// Phone 객체를 상속받았기 때문에 Phone 객체의 필드를 사용 가능
		System.out.println("모델: " + phone.model); // 갤럭시
		System.out.println("색상: " + phone.color); // 은색
	}

}

메소드 재정의 (가장 중요)

메소드 오버라이딩

  • 상속된 메소드를 자식 클래스에서 재정의하는 것.
  • 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용됨
  • 부모 메소드의 선언부의 리턴 타입, 함수 명, 매개변수의 타입과 갯수가 모두 동일해야 함
  • 접근 제한을 더 강하게 오버라이딩 할 수 없음
    -> public에서 private로 변환 x
  • 예외를 추가할 수 없음
  • 메소드를 재정의라고도 하며 다시 정의하는 것을 뜻함. 다른 말로 "다형성" 이라고 함.
  • 상속을 기본으로함
  • 호출하는 방법은 동일하지만 실행 결과가 다양함
  • @Override : 부모 클래스에 동일한 메소드가 있는지 확인하는 용도 (생략 가능)

부모 메소드 호출

  • 자식 메소드 내에서 super.부모메소드명();의 형태로 호출해야 함

오버라이딩 예제 1

  • 부모 클래스 (Calculator)
package ch07.sec04.exam01;

public class Calculator {
	
	// 원주율 구하는 인자 1개인 일반메소드
	public double areaCircle(double r) {
		System.out.println("Calculator 객체의 메소드 실행");
		return 3.14159 * r * r;
		// 기본형 타입의 함수이므로 반드시 타입에 맞게 리턴해야함
	}
}
  • 자식 클래스 (Computer)
package ch07.sec04.exam01;

// Calculator 객체를 상속받음
public class Computer extends Calculator{
	
	// Calculator 객체에 동일한 메소드가 생성되었고, 매개변수의 타입과 갯수가 모두 동일
	// 이 경우 오버라이딩이라는 과정을 진행하는데, 부모메소드를 감추고 자식메소드의 메소드를 호출함
	// @Override라는 구문을 쓰는데 생략 가능함
	public double areaCircle(double r) {
		System.out.println("Computer 객체의 메소드 실행");
		return Math.PI * r * r;
		// Math.PI : Math 클래스에서 제공하는 함수
	}
}
  • 실행 클래스
package ch07.sec04.exam01;

public class ComputerExam {
	public static void main(String[] args) {
		int r = 20;
		
		Calculator cal = new Calculator();
		// Calculator 객체 생성 (기본생성자)
		System.out.println("원 면적: " + cal.areaCircle(r));
		// Calculator 객체의 메소드가 실행됨
		System.out.println();
		
		Computer com = new Computer();
		// Computer 객체 생성 (기본 생성자)
		System.out.println("원 면적: " + com.areaCircle(r));
		// 오버라이딩 과정을 거쳤기 때문에 Computer 객체의 메소드가 실행됨
		System.out.println();
		
		cal = com;
		System.out.println("원 면적: " + cal.areaCircle(r));
		// Computer 객체의 메소드를 Calculator 객체의 메소드에 대입하였는데
		// 두 메소드가 같기 때문에 Calculator 객체의 
        // 메소드가 실행된다고 볼 수 있지만
		// Computer 객체에서 메소드를 오버라이딩 했기 때문에
        // Computer 객체의 메소드가 실행됨
	}
}

final 클래스

  • 최종 클래스라는 의미로 상속할 수 없는 클래스를 의미하며 자식클래스를 만들 수 없음
  • class 앞에 final이라는 키워드를 넣어주면 됨

final 메소드

  • 메소드를 선언할 때 final 키워드를 붙이면 오버라이딩할 수 없음
  • 부모클래스에 상속 받아 자식클래스를 선언할 때 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의가 불가함

예제

Car

package ch07.sec05;

public class Car {
	public int speed;
	
	public void speedUp() {
		speed += 1;
	}
	
	public final void stop() {
		System.out.println("차를 멈춤");
		speed = 0;
	}
}

SportsCar (Car에서 상속받음)

package ch07.sec05;

public class SportsCar extends Car{
	// Car 객체에서 상속받음
	@Override
	public void speedUp() {
		speed += 10;
	}
	
	/*
	@Override
	public void stop() {
		// Car 객체의 stop() 메소드가 final로 지정되었기 때문에
		// 상속받은 현재 객체에서 선언이 불가하다
		System.out.println("스포츠카를 멈춤");
		speed = 0;
	}
	*/
}

protect 접근 제한자

  • 같은 패키지 안의 클래스와 다른 패키지의 경우는 자식 클래스만 접근을 허용하는 것으로 상속관계에서 사용됨
  • protected는 상속과 관련이 있고, public과 default의 중간 쯤에 해당하는 접근 제한
  • 현업에서는 많이 사용하지는 않음
  • 제한 대상 : 필드, 생성자, 메소드

예제

class A

package ch07.sec06.package1;

public class A {
	// protected : public과 default의 중간 개념으로
	// 같은 패키지 안에 있는 클래스이거나 자식클래스에서만 사용 가능
	
	protected String field;
	// 필드 생성
	
	protected A() {
		
	}
	// 생성자
	
	protected void method() {
		
	}
	// 메소드
}

class B (A와 같은 패키지)

package ch07.sec06.package1;

public class B {
	public void method() {
		A a = new A();
		a.field = "value";
		a.method();
		// A와 같은 패키지에 속해있기 때문에
		// 필드와 메소드 호출 및 객체 생성이 가능
	}
}

class C(A와 다른 패키지, 상속관계 x)

package ch07.sec06.package2;

import ch07.sec06.package1.A;

public class C {
	public void method() {
//		A a = new A();
//		a.field = "value";
//		a.method();
		// A 클래스와 소속되어있는 패키지가 다름
		// import를 하였더라도 protected 특성상 
        // 상속받지 않은 클래스에서는 사용이 불가능
	}
}

class D (A와 다른 패키지, 상속관계 o)

package ch07.sec06.package2;

import ch07.sec06.package1.A;
// A와 패키지가 다르므로 import 선언을 반드시 해줘야함

public class D extends A{
	public D(){
		super();
		// A로 부터 상속받기 때문에 A의 생성자를 호출함
	}
	
	public void method1() {
		this.field = "value";
		this.method();
		// 상속받는 필드와 메소드 : this를 이용해 호출 가능함
	}
	
	public void method2() {
//		A a = new A();
//		a.field = "value";
//		a.method();
		// 하지만 A와 소속된 패키지가 다르기 때문에
		// A 객체를 직접 생성해서 사용은 불가하다
        // super를 통해 상속받은 객체를 생성함
	}
}

타입 변환

  • 일반화 : 일반적인 기능 (대략, 대충)
    -> ex) '야구 팀'과 같은 포괄적인 의미
  • 특별화 : 특별한 기능 (디테일함)
    -> ex) SSG랜더스, LG트윈스, 키움히어로즈 등.. 구체적인 팀 이름 개념

자동 타입 변환 (매우 중요)

  • 자동으로 타입이 변환되는 것을 의미
  • 상속을 기본으로 함
  • 특별한 기능 (특별화) : 자식 클래스 : 각각의 기능이 있음
  • 일반적인 기능 (일반화) : 부모 클래스
  • 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급함

부모 클래스

package ch07.sec07.exam02;

public class Parent {
	
	// method1() 메소드 선언
	public void method1() {
		System.out.println("Parent-method1");
	}
	
	// method2() 메소드 선언
	public void method2() {
		System.out.println("Parent-method2");
	}
}

자식 클래스

package ch07.sec07.exam02;

public class Child extends Parent{
	
	@Override
	// @Override : 부모 클래스에 있는 동일 메소드를 받아
	// 자식 메소드에서 재정의하는 것
	// 따라서 부모 메소드는 감춰지게 됨
	public void method2() {
		System.out.println("Child-method2");
	}
	
	// method3() 메소드 선언
	public void method3() {
		System.out.println("Child-method3");
	}
}

실행 클래스

package ch07.sec07.exam02;

public class ChildExam {
	public static void main(String[] args) {
		Child ch = new Child();
		// Child(자식) 객체 생성
		
		Parent parent = ch;
		// Parent 객체를 사옹할 수 있는 이유 : Child 객체가 Parent에게 상속받았기 때문
		// parent 참조변수에 Child 참조변수를 대입하는 것
		// 부모객체에 자식객체를 대입하는 과정을 자동 타입 변환이라고 함
		// 앞서 배웠던 자동 형변환이랑 원리가 같음
		// 이렇게 하면 Parent 참조변수에서도 Child 객체의 메소드나 필드를 이용할 수 있음
		
		parent.method1();
		// Parent 객체에 있는 method1 호출
		
		parent.method2();
		// Child 객체에서 Parent2의 method2를 재정의했기 때문에
		// Child 객체에 있는 method2가 호출이 됨!
	}
}

강제 타입 변환

  • 부모 타입은 자식타입으로 자동 변환되지 않기 때문에 타입을 캐스팅 연산자로 변환하는 것
  • 자식 객체가 부모 타입으로 자동 변환하면 부모 타입에 선언된 필드와 메소드만 사용 가능
  • 하지만 부모 타입에서 자식 객체로 형변환한 후 다른 자식 객체로 형변환하면 논리상의 오류가 생김

부모 클래스

package ch07.sec07.exam03;

public class Parent {
	// 필드 선언
	public String field1;
	
	// 메소드1 선언
	public void method1() {
		System.out.println("Parent-method1");
	}
	
	// 메소드2 선언
	public void method2() {
		System.out.println("Parent-method2");
	}
}

자식 클래스

package ch07.sec07.exam03;

public class Child extends Parent{
	public String field2;
	// 필드 선언
	
	// method3 메소드 선언
	public void method3() {
		System.out.println("Child-method3");
	}
}

실행 클래스

package ch07.sec07.exam03;

public class ChildExam {
	public static void main(String[] args) {
		Parent parent = new Child();
		// Child라는 객체를 생성했지만 Parent(부모)타입 변수에 저장했기 때문에
		// Parent의 메소드나 필드만 사용 가능함
		// Child가 Parent의 자식 객체이기 때문에 자동적으로 타입 변환이 일어남
		
		parent.field1 = "data1";
		parent.method1();
		parent.method2();
		// Child 객체는 Parent 클래스를 상속받기 때문에
		// Parent에 있는 객체의 필드나 메소드를 호출 가능
		
//		parent.field2 = "data2";
//		parent.method3();
		// Parent(부모)객체 타입을 사용하였기 때문에 Parent(부모)안에 있는 메소드나 필드만 사용 가능
		// 따라서 Child 객체 안에 있는 메소드나 필드는 Parent가 모르므로 실행되지 않음
		
		Child child = (Child) parent;
		// Child 객체에 있는 필드나 메소드를 사용하기 위해서는 Child 타입으로 형변환을 해야함
		// Child 클래스는 Parent 클래스보다 하위 클래스이기 때문에
		// 타입 변환을 위해서는 강제 타입 변환을 해야함 : 강제 형변환과 동일한 원리
		
		child.method3();
		// Child 타입으로 변환했으므로 Child 객체 내부에 있는 메소드나 필드르 사용 가능
	}
}

다형성 (매우 중요)

  • 하나의 함수 호출로 부모 메소드나 자식 메소드에 따라 결과가 다르게 나올 수 있음
  • 자동 타입 변환과 메소드 오버라이딩을 모두 실행한 결과
  • 호출하는 참조변수는 반드시 부모의 참조변수
  • 자식의 메소드에서 반드시 재정의를 해줘야함 (상속이 없으면 안됨)
  • 자식객체의 참조변수를 부모객체의 참조변수에 대입하면 메소드 오버라이드를 통해 부모 메소드가 감춰졌기 때문에 자식 메소드가 실행됨
  • 다형성의 목적은 코드의 일반화임
  • 매개변수 타입은 부모 클래스여야 하지만, 전달되는 객체는 자식 클래스일 수 있다.

필드 다형성

  • 필드 타입은 동일하지만, 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것을 뜻함

예제

Tire

package ch07.sec08.exam01;

public class Tire {
	// roll() 함수 선언
	public void roll() {
		System.out.println("회전합니다");
	}
}

Hankook Tire

package ch07.sec08.exam01;

// Tire 클래스를 상속받음
public class HankookTire extends Tire{
	@Override
	// @Override : 부모 클래스에서 받은 동일한 메소드를 자식 메소드의 것으로 재정의함
	public void roll() {
		System.out.println("한국 타이어가 회전합니다");
	}
}

Kumho Tire

package ch07.sec08.exam01;

// Tire 클래스를 상속받음
public class KumhoTire extends Tire{
	@Override
	// @Override : 부모 클래스에서 받은 동일한 메소드를 자식 메소드의 것으로 재정의함
	public void roll() {
		System.out.println("금호 타이어가 회전합니다");
	}
}

Car

package ch07.sec08.exam01;

public class Car {
	// Tire 객체를 사용하기 위한 필드 선언
	public Tire tire;
	
	// 메소드 선언
	public void run() {
		// Tire의 roll() 메소드를 호출한 것이지만
		// KumhoTire와 HankookTire가 Tire의 객체를 상속받아
		// 같은 메소드를 가지고 있어 오버라이딩 되므로
		// HankookTire와 KumhoTire의 객체를 생성하면 
		// 해당 roll() 메소드를 호출함
		tire.roll();
	}
}

CarExample

package ch07.sec08.exam01;

public class CarExample {
	public static void main(String[] args) {
		// Car 객체를 생성함
		Car c = new Car();
		
		// Car 객체 내부에 있는 tire라는 필드에 Tire라는 객체 생성
		c.tire = new Tire();
		// Car 객체 안에 run() 함수 실행됨
		c.run(); // 회전합니다
		
		// Car의 tire 필드에 HankookTire라는 객체 생성
		c.tire = new HankookTire();
		// Car 객체 안에 run() 함수 실행됨
		c.run();
		// run 함수 내부에서 roll 함수가 HankookTire 내부의 roll 함수로 오버라이딩되어
		// 결과 값은 : 한국타이어가 굴러갑니다
		
		// Car의 tire 필드에 HankookTire라는 객체 생성
		c.tire = new KumhoTire();
		// Car 객체 안에 run() 함수 실행됨
		c.run();
		// run 함수 내부에서 roll 함수가 KumhoTire 내부의 roll 함수로 오버라이딩되어
		// 결과 값은 : 금호타이어가 굴러갑니다
	}
}

매개변수 다형성

  • 메소드가 클래스 타입의 매개변수를 가지고 있을 경우, 호출할 때 동일한 타입의 자식 객체를 제공할 수 있음
  • 어떤 자식 객체가 제공되느냐에 따라서 메소드의 실행 결과가 달라짐

Vehicle

package ch07.sec08.exam02;

public class Vehicle {
	// run() 메소드 선언
	public void run() {
		System.out.println("차량이 달립니다");
	}
}

Bus

package ch07.sec08.exam02;

public class Bus extends Vehicle{
	// Vehicle 클래스에서 상속받음
	
	@Override
	// @Override : 부모 클래스 Vehicle의 run() 메소드를
	// 현재 Bus 클래스에서 재정의함
	public void run() {
		System.out.println("버스가 달립니다");
	}
}

Taxi

package ch07.sec08.exam02;

public class Taxi extends Vehicle{
	// Vehicle 클래스에서 상속받음
	
	@Override
	// @Override : 부모 클래스 Vehicle의 run() 메소드를
	// 현재 Bus 클래스에서 재정의함
	public void run() {
		System.out.println("택시가 달립니다");
	}
}

Driver

package ch07.sec08.exam02;

public class Driver {
	// 동작을 위한 클래스
	
	// 클래스의 매개변수를 대입하여 해당 클래스의 run 함수가 실행되게끔 하기
	public void drive(Vehicle v) {
		v.run();
	}
}

DriverExam

package ch07.sec08.exam02;

public class DriverExam {
	public static void main(String[] args) {
		Driver driver = new Driver();
		// Driver 객체 생성 및 참조변수 설정
		
		Bus bus = new Bus();
		// Bus 객체 생성 및 참조변수 설정
		driver.drive(bus);
		// Driver 클래스에 drive()라는 함수를 선언하였고
		// 함수에 객체의 매개변수에 따라 run 함수를 실행함
		// 따라서 Bus 클래스의 run 함수가 실행되어
		// 결과값은 버스가 달립니다
		
		Taxi taxi = new Taxi();
		// Taxi 객체 생성 및 참조변수 설정
		driver.drive(taxi);
		// Driver 클래스에 drive()라는 함수를 선언하였고
		// 함수에 객체의 매개변수에 따라 run 함수를 실행함
		// 따라서 Taxi 클래스의 run 함수가 실행되어
		// 결과값은 버스가 달립니다
	}
}

instanceof

  • instanceof는 객체가 특정 클래스의 인스턴스인지 확인하는 연산자
    -> 자식 클래스의 형태로 변환할 수 있는지!
  • 형태 : a instanceof b
    -> a의 타입을 b의 타입으로 변환할 수 있는지 확인하는 구문
    -> a의 객체의 타입이 b의 타입으로 변환할 수 있으면 true
    -> a의 객체의 타입이 b의 타입으로 변환할 수 없으면 false
  • Java 12 부터는 instanceof 연산의 결과가 true일 경우 우측 타입 변수를 사용할 수 있기 때문에 강제 형변환이 필요 없음!
    -> 형태 : a instance B b : a의 타입의 참조변수를 B클래스 타입으로 변환할 수 있으면 true를 반환하고 필드 b에 값 저장, false면 b에 저장이 안됨,,

부모 클래스 (Person)

package ch07.sec09;

public class Person {
	public String name;
	// 필드 선언
	
	// 인자가 1개인 일반생성자 선언
	public Person(String name) {
		this.name = name;
		// name 매개변수가 위의 name 필드에 저장
	}
	
	// walk() 메소드 생성
	public void walk() {
		System.out.println("걷습니다.");
	}
}

자식 클래스 (Student)

package ch07.sec09;

public class Student extends Person{
	// Person 클래스를 상속받음
	public int studentNo;
	// 필드 선언
	
	// 인자가 2개인 일반생성자 선언
	public Student(String name, int studentNo) {
		super(name);
		// Person 생성자는 String 타입 매개변수를 1개 가지므로
		// super에 String 타입 매개변수를 1개 넣기
		// super는 부모 클래스의 생성자를 호출하기 위해 사용, 인자가 있을 경우에는 꼭 호출해주기
		
		this.studentNo = studentNo;
		// 매개변수로 받은 studentNo를 해당 클래스의 studentNo 필드에 적용
	}
	
	// study 메소드 선언
	public void study() {
		System.out.println("공부를 합니다.");
	}
}

Instanceof 실행 클래스

package ch07.sec09;

public class InstanceofExam {
	// 해당 클래스에 바로 사용할 것이기 때문에 static (정적)선언
	// Person 클래스 타입의 매개변수 한 개를 가짐
	// Person이 부모 클래스이기 때문
	public static void personInfo(Person person) {
		System.out.println("name: " + person.name);
		// 입력받은 매개변수를 Person 클래스의 name 필드에 저장
		person.walk();
		// Person 클래스의 walk() 메소드 실행함
		// 걷습니다 실행
		
//		if(person instanceof Student) {
		// a instanceof b : a의 객체를 b의 객체로 변환이 가능한지 (상속관계에 있는지)
		// 맞으면 true, 틀리면 false
		
//			Student student = (Student) person;
			// Person 타입의 변수를 Student 타입으로 강제 타입 변환
			// 강제 타입 변환 이유 : Person 객체가 Student 객체의 부모 클래스이기 때문에
			// 자식 클래스의 타입으로 사용하기 위해서는 강제로 형변환이 필요함
		
//			System.out.println("StudentNo : " + student.studentNo);
			// Student 클래스에 있는 studentNo 필드에 저장
		
//			student.study();
			// Student 클래스의 study() 함수 실행
//		}
		
		// Person 타입으로 매개변수를 받았기에
		// 자식 클래스인 Student 필드와 메소드를 이용하기 위해서는
		// 강제 타입 변환이 필요함
		if(person instanceof Student student) {
			// a instanceof B b
			// a 객체를 B클래스 타입으로 변환이 가능하면 변수 b에 해당 객체를 저장
			// Student 클래스에 studentNo 필드에 해당 값을 저장함
			System.out.println("StudentNo: " + student.studentNo);
			student.study();
			// Student 객체의 study() 함수 실행
		}
	}
	
	public static void main(String[] args) {
		Person p1 = new Person("이동주");
		// Person 생성자의 인자가 String 타입 1개였으므로, String 인자값을 넣기
		personInfo(p1);
		// personInfo(p1)에서 Person 객체를 전달하면, walk() 메소드가 Person 클래스에서 실행됩니다.
		
		System.out.println();
		
		Person p2 = new Student("홍길동", 10);
		// Person 타입의 변수이지만 Student 객체를 생성하여 자동 형변환이 수행됨
		// Student 생성자의 인자는 String 1개와 정수타입 1개
		personInfo(p2);
		// personInfo(p2)에서 Student 객체를 전달하면, instanceof가 true를 반환하여 Student 객체의 studentNo와 study() 메소드가 실행됩니다.
	}
}

추상 클래스

  • 객체를 생성할 수 있는 실체 클래스들의 공통적인 필드나 메소드를 추출해서 선언한 클래스

  • 실체 클래스들의 부모 역할 (참조 변수) 을 함. 공통적인 필드나 메소드를 물려받을 수 있음

  • 클래스를 선언할 때 abstrcat 키워드를 붙여 사용한다.

  • new 연산자를 이용하여 객체를 직접 만들지 못하고 상속을 통해서 자식 클래스만 만들 수 있다.

  • 추상 메소드의 경우 부모 클래스에서 함수 선언은 하되 구현은 자식 클래스에서 재정의하기 때문에 실행문은 선언하지 않는다. (함수만 선언)

  • 의존성 역전 원칙 : 실체를 보지 않고 부모를 볼 수 있도록 의존 관계를 반대로 한다는 것!

  • 명확하지 않고 특별한 기능이 없는 것!

  • 특별한 코드를 일반화 시킴

추상 클래스 선언

Phone(추상 클래스)

package ch07.sec10.exam01;

public abstract class Phone {
	// abstract : 추상이라는 의미로 추상 클래스나 메소드를 사용할 때
	// 클래스나 메소드명 앞에 명시함
	
	String owner;
	// 필드 선언
	
	// 인자가 1개인 생성자 만들기 (String 타입)
	Phone(String owner){
		this.owner = owner;
		// 매개변수를 위의 필드에 저장
	}
	
	// 메소드 선언
	void turnOn() {
		System.out.println("폰 전원을 켭니다");
	}
	void turnOff() {
		System.out.println("폰 전원을 끕니다");
	}
}

SmartPhone 클래스(자식 클래스)

package ch07.sec10.exam01;

public class SmartPhone extends Phone{
	// 추상 클래스인 Phone을 상속받음
	
	// 인자가 1개인 일반생성자 선언
	SmartPhone(String owner){
		super(owner);
		// Phone 생성자 호출 : String 타입의 인자를 1개 가지기 때문에
		// Phone 생성자에 String 타입 매개변수 하나를 넣어주기
		// 추상 클래스는 new 연산자를 통해 선언할 수 없으므로 반드시 super()로 호출해줘야함
	}
	
	// internetSearch() 메소드 선언
	void internetSearch() {
		System.out.println("인터넷 검색을 합니다");
	}

}

실행 클래스

package ch07.sec10.exam01;

public class PhoneExam {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SmartPhone smartPhone = new SmartPhone("이동주");
		// SmartPhone 객체 생성
		
		smartPhone.turnOn();
		smartPhone.internetSearch();
		// SmartPhone 객체에 있는 internetSearch() 함수 호출
		smartPhone.turnOff();
		// SmartPhone 클래스는 추상메소드 Phone을 상속받기 때문에
		// Phone에 있는 메소드들(turnOn, turnOff)을 실행 가능!
	}

}

추상 메소드 선언

추상 메소드가 있는 클래스

package ch07.sec10.exam02;

public abstract class Animal {
// 추상 메소드를 선언하면 반드시 클래스명에도 abstract를 붙여 추상클래스로 만들기	
	
	public void breathe() {
		System.out.println("숨을 쉰다");
	}
	// 메소드 선언
	
	public abstract void sound();
	// 추상 메소드 사용
	// 추상 메소드는 실행문이 없고, 자식 클래스에서 반드시 재정의(오버라이딩)해야 한다.
	// 대신 메소드를 호출하는 용도로 사용해야 하기 때문에 메소드 자체는 선언하고 세미콜론(;) 붙이기
}

Dog 클래스

package ch07.sec10.exam02;

public class Dog extends Animal{
	// 추상 클래스 Animal을 상속받음
	
	@Override
	public void sound() {
		System.out.println("멍멍");
	}
	// 추상 메소드 sound()를 현재 클래스에서 재정의함
    // 추상 클래스는 직접 객체를 생성할 수 없고, 반드시 자식 클래스를 통해서만 객체를 생성할 수 있다.
}

Cat 클래스

package ch07.sec10.exam02;

public class Cat extends Animal{
	// 추상 클래스 Animal을 상속받음
	
	@Override
	public void sound() {
		System.out.println("야옹");
	}
	// 추상 메소드 sound()를 현재 클래스에서 재정의함
    // 추상 클래스는 직접 객체를 생성할 수 없고, 반드시 자식 클래스를 통해서만 객체를 생성할 수 있다.
}

실행 클래스

package ch07.sec10.exam02;

public class AbstractMethodExam {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Dog dog = new Dog();
		// Dog 객체 생성
		dog.sound();
		// Dog에서 재정의된 sound 메소드 실행
		
		Cat cat = new Cat();
		// Cat 객체 생성
		cat.sound();
		// Cat에서 재정의된 sound 메소드 실행
		
		animalSound(new Dog());
		// Dog의 객체를 생성하여 이를 매개변수로 사용
		// Dog 클래스에 있는 sound() 메소드로 재정의되어 실행
		
		animalSound(new Cat());
		// Cat의 객체를 생성하여 이를 매개변수로 사용
		// Cat 클래스에 있는 sound() 메소드로 재정의되어 실행
		
	}

	public static void animalSound(Animal animal) {
		animal.sound();
	}
	// 현재 클래스에서 메소드를 사용하기 위해 static(정적) 형태로 메소드 생성
	// Animal 객체에 있는 sound() 메소드 실행
	// 매개변수로 Animal 클래스 타입 사용
}

봉인된 클래스

  • 무분별한 자식 클래스 생성을 방지하기 위해 봉인된 클래스가 도입됨
  • Java 15부터 사용함
  • sealed 키워드를 class 앞에 사용하고, 뒤에는 premit 키워드를 사용하여 상속이 가능한 클래스를 지정함 (두개 이상도 가능)
  • non-sealed 키워드를 사용하여 상속받은 클래스에서 봉인을 해제할 수 있음
  • final은 더 이상 상속할 수 없다는 뜻

sealed

package ch07.sec11;

public sealed class Person permits Employee, Manager {
	// sealed : 자식 클래스를 마음대로 생성하지 못하게함
	// permits : 상속할 수 있는 클래스들 나열하기
	// sealed 클래스에서 상속받는 클래스들은 똑같이 sealed를 사용해서 같은 방식으로 봉인하거나
	// non-sealed로 봉인을 풀기 또는 final로 상속을 종료하겠다고 선언하기
	
	public String name;
	
	public void work() {
		System.out.println("하는 일이 결정되지 않았습니다");
	}
}

final

package ch07.sec11;

public final class Employee extends Person{
	// final : 더 이상 상속을 할 수 없음을 뜻함 (최종적인 클래스)

	@Override
	public void work() {
		System.out.println("제품을 생산합니다");
	}
}

non-sealed

package ch07.sec11;

public non-sealed class Manager extends Person{
	// non-sealed : 봉인을 해제한다는 뜻으로 다른 클래스에 상속이 가능해짐
	
	@Override
	public void work() {
		System.out.println("생산 관리를 합니다");
	}
}

봉인 해제 후 상속

package ch07.sec11;

public class Director extends Manager{
	// Manager 클래스를 상속받음
	// Manager 클래스가 봉인을 해제했기 때문에 상속 받을 수가 있음
	
	@Override
	public void work() {
		System.out.println("제품을 기획합니다");
	}
}

실행 클래스

package ch07.sec11;

public class SealedExam {
	public static void main(String[] args) {
		Person p = new Person();
		Employee e = new Employee();
		Manager m = new Manager();
		Director d = new Director();
		
		p.work();
		e.work();
		m.work();
		d.work();
	}

}
profile
끄작끄작

0개의 댓글