클래스 상속 & 오버라이딩
상속의 기본 조건 IS-A (~는 ~이다)
하위 클래스는 상위 클래스의 모든 특징을 갖는다.
하위 클래스는 자신만의 추가적 특성을 더한다.
public class Main1 {
public static void main(String[] args) {
SmartPhone phone = new SmartPhone("010-1234-5678", "Nougat");
MobilePhone phone1= new SmartPhone("010-9999-999", "Nougat");
phone1.answer();
phone.answer();
phone.playApp();
}
}
class MobilePhone{
protected String number;
public MobilePhone(String num){
this.number = num;
}
public void answer(){
System.out.println("Hi from" + number);
}
}
class SmartPhone extends MobilePhone{
private String androidVer;
public SmartPhone(String num, String ver){
super(num);
androidVer = ver;
}
public void playApp(){
System.out.println(androidVer);
}
}
+) HAS-A는 상속으로 표현 할 수 있는 관계로 언급. 실무에서는 HAS-A를 상속으로 보기 어렵다.
메소드 오버라이딩
메소드 오버라이딩은 상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의
상위클래스의 참조변수가 참조할 수 있는 대상의 범위
SmartPhone phone = new SmartPhone("010-1234-5678", "Nougat");
MobilePhone phone1= new SmartPhone("010-9999-999", "Nougat");
MobilePhone이 참조변수가 SmartPhone 인스턴스를 참조
상위클래스의 참조변수는 하위클래스의 인스턴스를 참조할 수 있다.
인스턴스는 SmartPhone 인스턴스인 동시 -> MobilePhone 인스턴스가 된다.
public class Main1 {
public static void main(String[] args) {
SmartPhone phone = new SmartPhone("010-1234-5678", "Nougat");
MobilePhone phone1= new SmartPhone("010-9999-999", "Nougat");
phone1.answer();
phone.answer();
phone.playApp();
}
}
class MobilePhone{
protected String number;
public MobilePhone(String num){
this.number = num;
}
public void answer(){
System.out.println("Hi from" + number);
}
}
class SmartPhone extends MobilePhone{
private String androidVer;
public SmartPhone(String num, String ver){
super(num);
androidVer = ver;
}
public void playApp(){
System.out.println(androidVer);
}
}
모바일 폰 클래스에 정의된 메소드는 당연히 호출 가능, 스마트 폰 클래스에 정의된 메소드의 호출은 불가능. -> phone1.playApp은 호출이 불가능.
Phone1은 모바일 폰에 참조변수. Phone1을 통해서 접근 가능한 멤버는 모바일 폰 클래스에 정의, 상속하는 클래스의 멤버로 제한. 참조하는 인스턴스에 따라서 접근 가능한 멤버가 결정되어야 한다 볼 수 있지만,
실행 시간을 늦추는 결과로 이어지거나,
참조변수 형을 기준으로 접근 가능한 멤버를 제한하는 것은 코드를 단순하게 한다.
클래스의 상속과 참조변수의 참조 가능성
class Cake{
public void yummy(){
System.out.println("Yummy cake");
}
}
class CheseCake extends Cake{
public void yummy(){
System.out.println("Yummy Cheese Cake");
}
}
cake에서 호출 할 수 있는 참조변수는 1개, cheeseCake에서 호출 할 수 있는 참조변수는 2개이다.
참조변수 간 대입과 형 변환
컴파일러는 참조변수의 형만을 갖고 대입의 가능성을 판단. 자바는 참조변수 형 정보를 기준으로 대입의 가능성을 판단한다.
클래스의 상속 & 참조변수의 참조 가능성 (배열 관점에서의 정리)
참조관계는 배열까지도 이어진다고 보면 된다.
메소드 오버라이딩
상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 행위 -> 오버라이딩. 오버라이딩은 “무효화 시킨다” 라고 해석을 볼 수 있다.
class Cake{
public void yummy(){
System.out.println("Yummy cake");
}
}
class CheseCake extends Cake{
public void yummy(){
System.out.println("Yummy Cheese Cake");
}
}
public class Main2 {
public static void main(String[] args) {
Cake c1 = new CheseCake();
CheseCake c2 = new CheseCake();
c1.yummy();
c2.yummy();
}
}
Cake을 상속 하면서 Cake에 정의된 yummy 메소드와 메소드 이름, 반환형, 매개변수 선언을 정의
Yummy 메소드를 cheesecake의 yummy 메소드를 오버라이딩 하였다. 참조변수 관계없이 오버라이딩 한 메소드가 오버라이딩 된 메소드를 대신하게 된다.
Cake c1 = new CheseCake();
Cake의 yummy 메소드가 호출되어야 한다. cheesecake 인스턴스를 참조하고 있는 상황. But Cake의 yummy 메소드는 오버라이딩 되었다 (무효화 되었다.) -> CheeseCake의 yummy 메소드가 대신 호출
메소드 오버라이딩 일반화
결론 적으로 마지막으로 오버라이딩 된 메소드가 나머지 기능을 대신 하게 된다.
오버라이딩 된 메소드를 호출
Cake 클래스의 yummy 메소드는 하위 클래스의 cheesecake에 의해 오버라이딩.
CheeseCake c2 = new CheeseCake(); 의 인스턴스 대상으로 Cake의 yummy 메소드 호출 불가능.
따라서 외부가 아닌 내부 클래스에서 Cake의 yummy 메소드를 호출 하는 방법
class Cake1{
public void yummy(){
System.out.println("Yummy Cake");
}
}
class CheeseCake extends Cake{
public void yummy(){
System.out.println("Yummy Cheese Cake");
}
public void tasty(){
super.yummy();
System.out.println("Yummy Tasty Cake");
}
}
public class Main3 {
public static void main(String[] args) {
CheeseCake cake = new CheeseCake();
cake.tasty();
cake.yummy();
}
}
super.yummy();
Cake의 yummy 호출
상위 클래스에 정의된 오버라이딩 메소드의 호출 목적으로 super 사용.
인스턴스 변수와 클래스 변수도 오버라이딩 대상이 되는가
class Cake2{
public int size;
public Cake2(int size){
size = size;
}
public void showCakeSize(){
System.out.println(size);
}
}
class CheeseCake1 extends Cake2{
public int size;
public CheeseCake1(int size1, int size){
super(size1);
size = size;
}
public void showCakeSize(){
System.out.println(super.size);
System.out.println(size);
}
}
public class Main4 {
public static void main(String[] args) {
CheeseCake1 cake1 = new CheeseCake1(5, 7);
Cake2 cake2 = cake1;
System.out.println(cake2.size);
System.out.println(cake1.size);
System.out.println();
cake1.showCakeSize();
System.out.println();
cake2.showCakeSize();
}
}
class Cake2{
public int size;
class CheeseCake1 extends Cake2{
public int size;
변수 size가 상위 클래스와 하위 클래스에 모두 선언 된 것 이 핵심.
하지만 변수는 오버라이딩 되지 않는다. 따라서 참조변수 형에 따라서 접근하는 변수가 결정
CheeseCake1 cake1 = new CheeseCake1(5, 7);
System.out.println(cake1.size);
Cake size에 접근
클래스 변수와 클래스 메소드의 경우도 마찬가지. 두 가지도 참조변수 형의 따라서 접근하는 클래스 변수와 메소드가 결정. 클래스 변수와 메소드도 오버라이딩 대상이 아니다.
Instanceof
Instanceof는 참조변수가 참조 하는 인스턴스 클래스나, 참조하는 인스턴스가 상속하는 클래스를 묻는 연산자 이다.
If (ca instanceof Cake) -> ca는 참조변수, Cake는 클래스 이름 => 참조하면 true, 아니면 false
class Cake3{
}
class CheeseCake3 extends Cake3{
}
class StrawberryCheeseCake extends CheeseCake3{
}
public class Main5 {
public static void main(String[] args) {
Cake3 cake3 = new StrawberryCheeseCake();
if (cake3 instanceof Cake3){
System.out.println("케익 인스턴스 or");
System.out.println("케익 상속하는 인스턴스");
}
if (cake3 instanceof CheeseCake3){
System.out.println("치즈케익 인스턴스");
System.out.println("치즈케익 상속 인스턴스");
}
if (cake3 instanceof StrawberryCheeseCake){
System.out.println("딸기치즈케익 인스턴스");
System.out.println("딸기치즈케익 상속 인스턴스");
}
}
}
선언된 참조변수 cake가 참조하는 인스턴스는 strawberryCheeseCake 인스턴스이다. 이 인스턴스 클래스는 Cake를 상속하는 CheeseCake를 상속한다.
Instanceof 활용
class Box{
public void simpleWrap(){
System.out.println("simple wrapping");
}
}
class PaperBox extends Box{
public void paperWrap(){
System.out.println("Paper Wrapping");
}
}
class GoldPaperBox extends PaperBox{
public void goldWrap(){
System.out.println("Gold Wrapping");
}
}
public class Main6 {
public static void main(String[] args) {
Box box = new Box();
PaperBox box2 = new PaperBox();
GoldPaperBox box3 = new GoldPaperBox();
wrapBox(box);
wrapBox(box2);
wrapBox(box3);
}
public static void wrapBox(Box box){
if (box instanceof GoldPaperBox){
((GoldPaperBox)box).goldWrap();
}
else if(box instanceof PaperBox){
((PaperBox)box).paperWrap();
} else {
box.simpleWrap();
}
}
}
참조 변수가 참조하는 인스턴스에 따라서 호출하는 메서드를 달리하는 코드를 작성하는 것은 쉽지 않다. 하지만 instanceof 연산자를 활용하면, 비교적 쉽게 작성 가능.
연산자 instanceof 참조변수가 참조하는 인스턴스의 클래스나, 참조하는 인스턴스가 상속하는 클래스를 묻는 연산자이다.
Instanceof는 명시적 형 변환의 가능성을 판단해주는 연산자.
if (box instanceof GoldPaperBox){
((GoldPaperBox)box).goldWrap();
Box가 참조하는 인스턴스가 GoldPaperBox 이거나, GoldPaperBox를 상속하는 클래스의 인스턴스라는 뜻