상속

서현서현·2022년 3월 7일
0

JAVA

목록 보기
10/27
post-thumbnail

🌐 1. 상속

class 자식클래스 extends 부모클래스{}
  • 자식클래스가 부모클래스다. 라고 할 수있으면 상속관계이다 (ex 치즈케이크는 케이크다. 학생은 사람이다.)
  • 여러개의 부모클래스를 상속 할 수 없다
  • 부모클래스에서 private 접근제한자를 가지면 상속대상에서 제외된다

(cf) ‘has a’ 포함관계

bank와 customer > 은행은 고객을 갖고있다 (필드를 가지고 있는것.)

                    > 고객은 은행이다 (상속) X

🌐 부모생성자 호출

  • 자식 객체를 생성하면 부모객체가 먼저 생성되고 자식객체가 생성된다.
  • 모든 객체는 생성자를 호출해야만 생성된다. 부모생성자는 어디에 있는걸까?

    바로 자식 생성자에 숨어있다!

  • 부모 = CellPhone, 자식 = DmbCellPhone 이라면, super()가 부모의 기본 생성자를 호출
// DmbCellPhone의 생성자가 명시되어있지 않다면,
public DmbCellPhone() {
	super();
}
// 이 코드를 컴파일러가 자동으로 생성한다.

// 만약 직접 자식생성자를 선언하고 부모생성자를 호출하고 싶다면
자식클래스(매개변수 선언,..){
	super(매개값, ...); //매개값의 타입과 일치하는 부모 생성자를 호출!
	...
}
🚨 부모가 먼저 생성되고 자식객체가 생성된다 >> timing 정리해보자면 main 함수에서 새로운 자식 객체가 생겨 **자식 생성자가 실행되고, 그 즉시 부모 생성자가 실행**된다. 이때 만약 자식 생성자에 부모생성자를 호출하는 super가 써있지 않다면 default 생성자를 호출하는것!

🌐 메소드 재정의 (Overriding)

  • 상속받은 부모 메소드가 자식메소드에서 사용하기 적합치 않을때, 수정해서 사용하는것

예를들어 고양이와 강아지 클래스가 있을때,

소리를 낸다/ 숨을쉰다/ 밥을먹는다 (애니멀클래스의 메소드) = 강아지와 고양이 둘 다 갖는다.

근데 소리를 낸다는 부모클래스 동작이 강아지클래스에선 멍멍, 고양이클래스에선 야옹으로 재정의 되어야한다.

// 예를들어 고양이와 강아지 클래스가 있을때,
// 소리를 낸다/ 숨을쉰다/ 밥을먹는다 (애니멀클래스의 메소드) = 강아지와 고양이 둘 다 갖는다.
// 근데 소리를 낸다는 부모클래스 동작이 강아지클래스에선 멍멍, 
// 고양이클래스에선 야옹으로 재정의 되어야한다.

public class Animal {
	String sound(){
		return "소리를 낸다";
	}
	
}
public class Dog extends Animal{
	**// 메소드 재정의(Method Overriding)**
	String sound(){
	return "멍멍";
	}
}
public class AnimalExample {
	public static void main(String[] args) {
		Dog dog = new Dog();
		System.out.println(dog.sound());
	}

}

오버라이딩은 덮어쓰기이다. 소리를 낸다를 멍멍으로 덮어썼기 때문에 위와같은 결과가 나온다

메소드 재정의 방법

  • 부모의 메소드와 동일한 시그니처(리턴타입, 메소드이름, 매개변수목록)를 가져야함
  • 접근제한을 더 강하게 재정의 할 수 없다(더 약하게는 가능한데 보통은똑같이 쓴다)
  • 새로운 예외(Exeption)을 throws할 수 없다

@Override

  • 오버라이딩 조건에 부합하는지 체크해준다
🚨 **동일한 시그니처가 아니면 override가 아님!** → 즉 public String sound(int a){}는 새로운 함수가 추가된것이다. 이때 @Override 해주면 오버라이딩 조건에 맞지 않다고 알려준다
public class Dog extends Animal{
	// 메소드 재정의(Method Overriding)
	@Override 
	public String sound(int a){
	System.out.println(super.sound());
		return "멍멍";
	}

부모메소드 호출

  • 자식메소드에서 부모메소드를 재정의시 부모메소드는 숨겨지나, 일례로 숨겨졌던 부모메소드를 호출하고 싶다면 super.부모메소드();를 이용해서 부모메소드를 호출 할 수 있다.
public class Airplane {
	public void land() {
		System.out.println("착륙합니다");
	}
	public void fly() {
			System.out.println("일반비행합니다");
	}
	public void tackOff() {
	System.out.println("이륙합니다");
	}

}
public class SupersonicAirplane extends Airplane{
	public static final int NORMAL = 1;
	public static final int SUPERSONIC = 2;
	
	public int flyMode = NORMAL;
	
	@Override
	public void fly() {
		if(flyMode==SUPERSONIC) {
			System.out.println("초음속 비행기입니다");
		} else {
			super.fly();
		}
	}

}
public class SupersonicAirplaneExample {
	public static void main(String[] args) {
		SupersonicAirplane sa = new SupersonicAirplane();
		sa.tackOff(); //이륙합니다
		sa.fly(); // 일반비행합니다
		sa.flyMode=SupersonicAirplane.SUPERSONIC; //상수 바뀜! 상수쓸땐 클래스명.상수명 일케 쓰면 좋음 sa.상수명 ㄱㄴ하지만 경고뜸
		sa.fly(); // 초음속비행기입니다
		sa.flyMode=SupersonicAirplane.NORMAL; //상수바뀜
		sa.fly(); //일반비행
		sa.land(); //착륙합니다
		
	}

}

🌐 final 클래스와 final 메소드

상속 할 수 없는 final클래스

  • 클래스, 필드, 메소드 호출시 사용
  • finla클래스로 선언되면 최종적인 클래스로 더이상 호출 불가능. >> 자식클래스 만들수 X

재정의 할 수 없는 final메소드

  • final 붙으면 최종 메소드로 재정의 불가능.
  • 즉, 부모의 final메소드는 자식클래스에서 재정의 할 수 없다

🌐 this(” ”)

  • 같은 클래스의 다른 생성자를 호출하는 함수

🌐 2. 타입변환과 다형성

Driver driver = new Driver();
Bus bus = new Bus();
보통 이런식으로 객체를 생성할텐데
맨 앞 타입 BusVehicle로 바꾸는게 좋음
Vehicle bus = new Bus();
이런식으로!

**왜냐면** 버스를 운전하려고 버스 객체를 넣어서 쓰곤 했는데
이 버스를 택시로  바꾸려면
Taxi v = new Taxi();
driver.drive(v);
이런식이니까 모든 버스 코드를 택시로 바꾸어야 함
-> 유지보수가 어렵다

🌐 자동타입변환

: 부모와 동일하게 취급받는다!

// Animal은 부모, Cat은 자식객체
Cat cat = new Cat();

Animal animal = cat; // (형식) 부모타입 변수 = 자식타입;

합치면
Animal animal = new Cat();
// cat과 animal은 타입만 다를뿐 동일한 Cat 객체를 참조한다. (Cat주소값)
  • 바로 위의 부모가 아니더라도 상속계층에서 상위타입이라면 자동변환 가능.
  • 부모타입으로 자동 타입변환되면, 부모클래스에 선언된 필드와 메소드에만 접근 가능 비록 변수는 자식객체를 참조하지만, 변수로 접근 가능한건 부모클래스 멤버
  • (예외) 메소드가 자식클래스에 재정의 되어있다면 자식클래스 메소드가 대신 호출 = 다형성관련

오 그렇다면 결론적으로 자동타입변환된 변수는 주소는 자식주소지만 내부에 접근 가능한 필드와 메소드는 (오버라이딩된 경우 제외하고) 부모이구나! 주소는 자식이지만 부모의 멤버를 쓴다!! new 부모를 하지 않아도 부모 멤버를 쓸수있다!

<< 이거 근데 부모 필드가 private이여도 접근 되나? ⇒ 쌉불가능

다시말해 자동타입변환이란 new로 부모를 생성하지 않아도 부모의 필드와 메소드에 접근 가능하며, 오버라이딩을 통해 메소드를 바꿀수도 있는 기능이구나!!!!

책의 예시를 해석하면, 부모인 자동차 클래스에 다양한 자식객체인 타이어클래스들이 있을때 언제든 더 좋은 타이어 클래스로 바꿀 수 있다! 차를 바꾸지 않아도!!! 차의 메소드를 오버라이딩하는 자식객체인 타이어를 더 좋은 타이어 객체로!!!!!!!!!!!!! 그럼 이건 상속/재정의/타입변환 다쓰는거네!!!!!

글고 상속은 부모+알파지만 타입변환은 오버라이딩 제외오로지 부모임ㄷㄷ

타입변환은 안써도 되는거 아닌가? ㄴㄴ car를 런 할거아님 그럼 자식이 오버라이딩 하든말든 원래 메소드가 실행됨. 근데 타입변환 쓰면 부모타입변수를 run했는데도 메소드 수정이 가능함ㄷㄷ

g헐 시발 맞음

🌐필드의 다형성 : 자동타입변환의 필요성

자동타입변환은 다형성을 구현하기 위해 필요하다!

다형성의 기술적 조건

  1. 부모가 가지고 있는 필드와 메소드를 갖고있다 (상속)
  2. 자식은 부모 메소드를 재정의함으로서 더 우수한 실행결과를 도출할 수 있다 (오버라이딩)
  3. 자식타입을 부모타입으로 변환할 수 있다 (자동타입변환)
Car클래스에서 모든 타이어가 new Tire()를 이용해 필드로 선언되어있고, 
특정 타이어만 Tire()로 자동타입변환하는 HankookTire()로 바꾸었다.(상속관계)

Car myCar = new Car();
myCar.backLeftTire = new HankookTire();

Car클래스의 메소드,Run()을 실행하면 내부의 backLeftTire.roll()이 실행되어야 한다.
그러나 타이어를 교체했으므로 오버라이딩 되어 HankookTireroll()메소드가 실행된다.
=> Carrun()을 수정하지 않아도 다양한 roll()메소드 사용이 가능하다!

🌐 매개변수의 다형성

Driver 클래스에 정의된 drive() 메소드를 실행할때, 매개변수가 Vehicle 타입이다
class Driver{
	void drive(Vehicle vehicle){
		vehicle.run();}}

drive() 메소드를 호출은 다음과 같다
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(vehicle);

이때, 만약 Vehicle의 자식객체인 Bus객체를 매개값으로 넘겨준다면?
Bus bus = new Bus();
driver.drive(bus);

>> 자동타입변환 발생 <<
Vehicle vehicle = bus;
  • 매개변수의 타입이 클래스일경우 해당 클래스의 객체 뿐 아니라 자식객체도 매개변수로 이용 할 수 있다!
  • 자식객체가 부모의 메소드를 재정의 했다면 실행결과는 더욱 다양해지는것

🌐강제타입변환

  • 부모를 자식타입으로 변환하는것
  • 자식타입이 부모타입으로 자동타입변환 후 다시 자식타입으로 변환할때만 사용 할 수 있다
Parent parent = new Child(); // 자동타입변환
Child child = (child) parent; // 강제타입변환

자동타입변환으로 부모의 멤버만 사용 가능한 상황에서, 자식에 선언된 멤버를 꼭 사용해야 할 때 쓴다!

// Parent와 Child객체가 있을때
Parent p = new Child();
Parent는 a와 run();, Child는 c와 runA();
둘이 상속관계

주어졌을때, p.runA();나 p.c 가능할까? 둘다 불가능, Parent로 선언했기때문에 Parent만 알아
Parent p = new Child(); => Child p = new Child();로 고치면 가능
=> 결국 접근하려면, Child c = (Child)p. // c.runA();에 접근이 가능하다

🌐 instance of

  • 타입이 달라서 캐스팅이 안되는 경우도 존재한다.
  • 어떻게 해결? instance of를 이용한다!
boolean result = 좌항(객체) instance of 우행(타입)
// 좌항의 객체가 우항의 인스턴스라면, 즉 우행의 타입으로 객체가 생성되었다면 true를 리턴

if(parent instanceof Child) <- Child타입으로 변환 가능한지 확인

🌐 3. 추상클래스

  • 객체를 직접 생성하는 클래스는 실체클래스라고 하며, 이 클래스들의 공통적인 특성을 추출한 클래스를 추상클래스 라고 한다. 이 둘은 상속의 관계를 가지고 있으며 실체클래스는 추상클래스의 모든 특성(필드, 메소드)를 물려받는다.

🌐 추상클래스의 용도

공통된 필드와 메소드의 이름을 통일할 목적

실체클래스를 여러사람이 설계할 경우 실체클래스마다 필드와 메소드가 다를 수 있다.

데이터와 기능이 동일함에도 불구하고 이름이 다르니 객체가 호출하는 이름이 달라진다. 따라서 추상클래스를 이용하여 공통기능이나 데이터를 선언해두고 상속을 통해 이름을 통일한다.

PHONE의 OWNER와 turnOn()을 cellPhone과 smartPhone에 상속하면 따로 선언할 필요가 없고,
추가적 특성인 search()와 같은 메소드만 선언하면 된다

추상클래스의 메소드는 상속받은 객체가 구현해도됨! 즉 추상클래스는 비어도 된다

🌐 추상클래스 선언

  • 추상클래스를 선언할때는 abstract를 붙여야 한다. abstract를 붙이면 new를 이용해 객체를 만들지 못하고, 상속을 통해 자식클래스만 만들 수 있다.
  • 추상클래스와 마찬가지로 필드,생성자,메소드 선언을 할 수 있다. new연산자로 직접 생성자를 호출 할 순 없으나 자식객체 생성시 super()를 호출해서 추상클래스객체를 생성하므로 추상클래스도 생성자가 필요하다.
public abstract class Phone {
	//
	public String owner;
	//
	public Phone(String owner) {
		this.owner = owner;
	}
	//
	public void turnOn() {
		System.out.println("폰 전원을 켭니다.");
	}
	
	public void turnOff() {
		System.out.println("폰 전원을 끕니다.");
	}

}
public class SmartPhone extends Phone {
	// 생성자
	public SmartPhone(String owner) {
		super(owner); //안써있으면 기본생성자가 호출된다.
	}
	
	//
	public void internetSearch() {
		System.out.println("인터넷 검색을 합니다");
	}

}
public class PhoneExample {
	public static void main(String[] args) {
		// Phone phone = new Phone(null); new사용불가능!
		
		SmartPhone smartPhone = new SmartPhone("홍길동");
		
		//
		smartPhone.turnOn();
		smartPhone.internetSearch();
		smartPhone.turnOff();
		
	}

}

🌐 추상메소드와 재정의

  • 추상클래스의 목적은 실체클래스의 멤버(필드,메소드)를 통일하는데 목적이 있다고 언급하였다. 따라서 모든 실체들이 가진 메소드의 실행내용이 동일하다면 추상클래스에 메소드를 작성하는것이 좋음!
  • 하지만 메소드의 선언만 통일하고, 실행내용은 실체 클래스마다 달라야 할 수도 있다. 예를들어 Animal 추상클래스에서 모든 동물은 소리를 내므로 sound() 메소드를 정의했다. 동물마다 다른 소리를 내므로 실체클래스에선 그 내용을 수정해야한다. 만약 sound()메소드를 추상클래스에 선언하지 않고 실체클래스에 직접 작성한다면 까먹고 작성하지 않을경우 모든 동물은 소리를 내야함에 위배된다.
  • 이런경우를 위해 추상메소드를 쓴다!
  • 추상클래스의 메소드의 경우 자식들이 재정의 하면의미가 없으므로 중괄호가 있는 body를 없애는게 좋다. 그러나 메소드 선언시 바디가 있어야하는데 없어져버리면 문법적인 문제가 생긴다. 그래서 이 메소드는 실제 구현되는 내용은없고 관념적으로 이름만 선언했다는 추상메소드를 적어줘야한다 ..
  • 추상클래스 설계시 하위클래스가 반드시 실행내용을 채우도록 강제하고 싶은 메소드가 있을경우 해당 메소드를 추상메소드로 선언
public abstract class Animal {
	public String kind;
	
	public void breathe() {
		System.out.println("숨을 쉽니다");
	}
	
	public abstract void sound(); // {System.out.println();} 
	// 추상메소드는 body 적으면 안됨

}
public class Dog extends Animal {
	//생성자
	public Dog() {
		this.kind = "포유류";
	}
	//추상메소드 재정의
	@Override
	public void sound() {
		System.out.println("멍멍");
	}

}
public class DogExample {
	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.sound();
		System.out.println("---------");
		
		//변수의 자동타입변환
		Animal animal = null;
		animal = new Dog(); //부모타입 멤버에 접근가능하다
		animal.sound(); //근데 오버라이딩 돼서 Dog클래스 메소드 호출
		System.out.println("---------");
		
		//메소드의 다형성
		animalSound(new Dog()); //new Dog()가 Animal animal로 들어감(자동타입변환)
	}

	public static void animalSound(Animal animal) {
		animal.sound(); //자동타입변환된 animal객체의 sound()이므로 오버라이딩된 메소드 호출
		
	}

}
💡 추상메서드가 있으면 반드시 추상클래스가 되어야 한다 BUT, **추상클래스라고 해서 반드시 추상메서드가 있어야 하는것은 아님!**

0개의 댓글