본문 바로가기

Java

Java- 상속

extends

  자바에서 상속을 하는 경우 extends 키워드를 통해서 상속을 한다.

생성자 실행순서

  상속받은 서브클래스의 인스턴스가 생성이 된다면 부모클래스의 생성자가 먼저 실행된 후

서브클래스의 생성자가 실행된다.

super

  키워드 super를 통해서 상위클래스의 생성자를 명시적으로 호출할 수 있다. 여기서 중요한 것은 상위클래스의 범위가 바로 직전까지로 한정된다는 것이다.

  예를 들어 총 10개의 상위클래스가 있는 클래스 A가 있다고 하면 super로는 총 9번의 상속을 받은 바로 위의 클래스의 생성자만 실행이 가능하다는 것이다.

 

  상위 클래스에 여러개의 생성자가 존재하는 경우 기본생성자를 호출하기 위해선 굳이 super키워드를 사용하지 않고 생략해도 되지만, 특정 매개변수 생성자를 호출하기 위해선 super( ) 키워드의 괄호안에 호출하고 싶은 생성자에 알맞은 인자를 넣어주면 된다.

class AAA{
    AAA(){
        System.out.println("AAA()");
    }
    AAA(int i){
        System.out.println("AAA(int i)");
    }
}

class BBB extends AAA{
    BBB(){ //AAA의 기본생성자 호출
        System.out.println("BBB()");
    }
    BBB(int i){
        super(i);  // 바로 상위 클래스(AAA)의 매개변수 생성자 실행
        System.out.println("BBB(int i)");
    }
}

class CCC extends BBB{
    CCC(){
        super();  // 기본 생성자 실행. 이 코드는 생략해도됨.
        System.out.println("CCC()");
    }
    CCC(int i){
        super(i);  // 바로 상위 클래스(BBB)의 매개변수 생성자 실행
        System.out.println("CCC(int i)");
    }
}
public class Inheritance {
    public static void main(String[] args) {
        CCC c1 = new CCC();
        System.out.println();  //단순 줄바꿈

        CCC c2 = new CCC(1);
    }
}


AAA()
BBB()
CCC()

AAA(int i)
BBB(int i)
CCC(int i)

 super 키워드를 사용하는데에는 몇가지 주의사항이 존재한다.

  • super는 생성자의 첫 문장으로 등장해야 한다.
  • super를 생략하는 경우 컴파일러에 의해 자동으로 super( )가 삽입된다. 따라서 상위클래스의 디폴트 생성자가 자동으로 실행된다.

단일 상속만을 지원하는 자바

  자바는 프로그램이 지나치게 복잡해지는 것을 방지하기 위해 단일 상속만을 지원한다. 따라서 하나의 클래스가 상속할 수 있는 클래스의 수가 최대 하나이다. 그러나 상속의 깊이에는 제한이 없다.

 

 다중 상속이 존재하는 C++

class AAA{
    int aaa;
    AAA(){
    	aaa=1;
    }
}

class BBB{
    int bbb;
    BBB(){
    	bbb=2;
    }
}

class CCC:public AAA, BBB{  //다중 상속이 가능한 C++
    int ccc;
    CCC(){
    	ccc=3;
        cout<< aaa <<< bbb <<< ccc;
    }
}

 다중상속이 없는 Java

class AAA{
    int aaa;
    AAA(){
        aaa = 1;
    }
}

class BBB extends AAA{
    int bbb;
    BBB(){
        bbb=2;
    }
}

class CCC extends BBB{
    int ccc;
    CCC(){
        ccc=3;
        System.out.printf("%d %d %d",aaa,bbb,ccc);
    }
}

  위의 두 코드는 완전히 같은 의미를 갖진 않지만(C++에서는 BBB가 AAA를 상속안함) 그래도 대충 이 정도로 설명이 가능할 듯하다

 

오버라이딩

 

class Cake{
    void cakeFunc(){
        System.out.println("this is cake");
    }
    void yummy(){
        System.out.println("cake is yummy");
    }
}
class CheeseCake extends Cake{
    void cheeseFunc(){
        System.out.println("this is cheese cake");
    }
    void yummy(){
        System.out.println("cheese cake is yummy");
    }
}

class StrawBerryCake extends CheeseCake{
    void strawBerryFunc(){
        System.out.println("this is strawberry cake");
    }
    void yummy(){
        System.out.println("strawberry cake is yummy");
    }
}

public class CakeOverRiding {
    public static void main(String[] args) {
        Cake c1 = new Cake();
        Cake c2 = new CheeseCake();
        Cake c3 = new StrawBerryCake();
        c1.yummy();
        c2.yummy();  //overriding => CheeseCake의 yummy 호출 
        c3.yummy();  //overriding => StrawBerryCake의 yummy 호출
    	System.out.println();
        //c2.cheeseFunc() //참조변수 자료형이 Cake => 호출 불가
        //c3.strawBerryFunc() //참조변수 자료형이 Cake => 호출 불가
        
        CheeseCake chC1 = new CheeseCake();
        CheeseCake chC2 = new StrawBerryCake();
        chC1.yummy();
        chC2.yummy();  //overriding
        
        //chC2.strawBerryFunc();  //참조변수 자료형이 CheeseCake => 호출 불가. 
        //CheeseFunc()까진 호출 가능
    }
}


cake is yummy
cheese cake is yummy
strawberry cake is yummy
this is cheese cake
cheese cake is yummy
strawberry cake is yummy

 참조 변수 간 대입

   상위 클래스의 인스턴스는 하위 클래스의 인스턴스를 참조할 수 있다.

이는 하위 클래스의 인스턴스는 상위클래스의 인스턴스이기도 하기 때문이다. 즉, CheeseCake은 일종의 Cake이다.

 

지금 세개의 클래스 Cake, CheeseCake, StrawBerryCake는 각각 상속관계이다.  결과를 보면 알겠지만 지금 c1,c2,c3의 참조변수 자료형은 모두 Cake로 되어있지만 그럼에도 불구하고 인스턴스는 각각 Cake, CheeseCake, StrawBerryCake로 되어 하위 상위클래스인 Cake 인스턴스가 하위클래스 인스턴스를 참조하고 있는 상황이다.

 

  그리고 그로 인해서 CheeseCake을 예로 들자면 c2는 참조 변수 자료형이 Cake이기 때문에 참조하고 있는 인스턴스가 CheeseCake인 것과는 별개로 CheeseFunc()를 호출할 수 없다. 

다시말해, 참조변수의 형을 기준으로 접근 가능한 멤버가 제한된다.

 

 메소드 오버라이딩

  오버라이딩 이해

  메소드 중 yummy() 메소드는 메소드 오버라이딩된 상태이다. 그렇기에 c1, c2, c3의 자료형이 모두 Cake임에도 불구하고 각각 참조하고 있는 인스턴스의 메소드를 호출하고 있는 상태이다.

  • c1은 Cake.yummy()를 호출
  • c2는 CheeseCake.yummy()를 호출
  • c3는 StrawBerryCake.yummy()를 호출

  이 때 하위 클래스(여기서는 StrawBerryCake이 가장 하위클래스)의 메소드는 상위클래스의 메소드를 오버라이딩 했다고 표현하고, 상위 클래스의 메소드는 오버라이딩 되었다고 표현한다.

 

 오버라이딩 된 메소드를 호출하는 방법 (super 키워드)

  바로 위에서 오버라이딩 된 메소드는 상위 클래스의 메소드라고 하였다.

class Cake{
    void yummy(){
        System.out.println("Cake is yummy");
    }
}

class CheeseCake extends Cake{
    void yummy(){
        System.out.println("CheeseCake is yummy");
    }
}

public class YummyCake {
    public static void main(String[] args) {
        CheeseCake chCake = new CheeseCake();
        chCake.yummy();  //CheeseCake의 yummy
    }
}

CheeseCake is yummy

 

 지금 위의 경우 처럼 상속 관계의 두 클래스가 있다. Cake의 yummy()는 오버라이딩 된 상태이고, CheeseCake의 yummy()는 오버라이딩 한 상태인데, 혹시 cheeseCake 인스턴스 chCake로 Cake의 yummy( )를 호출하는 방법이 있을까?

 다시말해 오버라이딩 된 메소드를 호출할 수 있는 방법이 있을까?

 

 현재상황

  • Cake c1 = new CheeseCake();  -> 이 인스턴스로는 Cake의 yummy() 호출 불가능
  • CheeseCake c1 = new CheeseCake();  -> 이 인스턴스로는 Cake의 yummy() 호출 불가능

  그럴때는 super 키워드를 상위 클래스의 생성자 뿐만 아니라 이용해서 상위클래스에 정의된, 오버라이딩 된 메소드의 호출도 가능하다.

class Cake{
    void yummy(){
        System.out.println("Cake is yummy");
    }
}

class CheeseCake extends Cake{
    void yummy(){
        super.yummy();  //오버라이딩된 메소드를 호출
        System.out.println("CheeseCake is yummy");
    }
}

public class YummyCake {
    public static void main(String[] args) {
        CheeseCake chCake = new CheeseCake();
        chCake.yummy();  
    }
}

Cake is yummy  //오버라이딩 된 메소드를 호출
CheeseCake is yummy

 

변수는 오버라이딩 되지 않는다

  오버라이딩이 무엇인가? 참조변수의 자료형에 관계없이 인스턴스의 자료형에 따라서 해당 메소드에 접근여부가 결정되는 것이 아닌가? 

그러나 메소드와 달리 변수는 오버라이딩이 되지 않는다. 즉, 참조변수의 자료형에 따라서 접근하는 변수가 결정된다는 것이다.

 

class A{
    public int size;
    A(int size){
        this.size = size;
    }
    A(){
        this(0);
    }
    void showSize(){
        System.out.println("A size: "+ size);
    }
}

class B extends A{
    public int size;

    B(int size1,int size2){
        super(size1);
        this.size = size2;
    }
    void showSize(){
        System.out.println("A size: "+super.size);
        System.out.println("B size: "+this.size);
    }
}

public class VariableCouldOverriding {
    public static void main(String[] args) {
        B b = new B(1,2);
        A a = b;  //b는 a를 참조하는 상위클래스 인스턴스

        a.showSize();  // 오버라이딩된 메소드

        a.size = 123;
        // 변수는 오버라이딩 되지 않고 참조변수의 형에 따라서 접근하는 변수가 결정됨.
        a.showSize();
    }
}

A size: 1
B size: 2
A size: 123
B size: 2

 

instanceof 연산자

  - if ( var instanceof Cls ) { ... }

 위에서 var은 참조변수이고, cls는 클래스명이다. 만일 var이 Cls의 인스턴스이거나 Cls를 상속받는 클래스의 인스턴스 인경우 true를 반환하고, 아닌 경우 false를 반환한다.

class Cake{}
class CheeseCake extends Cake{}
class StrawBerryCheeseCake extends CheeseCake{}


public class InstanceofOperator {
    public static void main(String[] args) {
        Cake c = new StrawBerryCheeseCake();  
        //c의 자료형은 Cake이나 이것이 참조하는 인스턴스의 자료형은 StrawBerryCheeseCake
        
        if(c instanceof Cake)
            System.out.println("Cake instance or instance that inherits Cake");
        if (c instanceof CheeseCake)
            System.out.println("CheeseCake instance or instance that inherits CheeseCake");
        if (c instanceof StrawBerryCheeseCake)
            System.out.println("StrawBerryCheeseCake instance or instance that inherits StrawBerryCheeseCake");
    }
}


Cake instance or instance that inherits Cake
CheeseCake instance or instance that inherits CheeseCake
StrawBerryCheeseCake instance or instance that inherits StrawBerryCheeseCake

 instanceof 연산자 활용

  instanceof 연산자는 말 그대로 해당 "참조변수가 특정 클래스나 이를 상속하는 클래스를 참조하는지 여부를 따지는 연산자"이다. 그렇기에 다음과 같이 활용이 가능하다.

  - instanceof 연산자를 활용한 코드의 구현

package TrashCan;

class Box{
    public void simpleWrap(){
        System.out.println("simple wrapping");
    }
}
class PaperBox extends Box{
    public void paperWrap(){
        System.out.println("paper wrapping");
    }
}
class GoldBox extends PaperBox{
    public void goldWrap(){
        System.out.println("gold wrapping");
    }
}

public class InstanceofOperator {
    public static void main(String[] args) {
        Box box = new Box();
        Box pBox = new PaperBox();
        Box gBox = new GoldBox();

        wrapping(box);
        wrapping(pBox);
        wrapping(gBox);
    }
    
    public static void wrapping(Box box){
        //하나의 함수로 참조하는 인스턴스 별로 구분하여 메소드를 달리 실행해준다
        if(box instanceof GoldBox){  //GoldBox 인스턴스 전용
            ((GoldBox)box).goldWrap();
            //형 변환후 함수 호출
        }
        else if(box instanceof PaperBox){  //PaperBox 인스턴스 전용
            ((PaperBox)box).paperWrap();
        }
        else if(box instanceof Box){  //Box 인스턴스 전용
            //box는 Box의 인스턴스. 형변환 필요 없음
            box.simpleWrap();
        }
        else{
            System.out.println("this is not a box");
        }
    }
}


simple wrapping
paper wrapping
gold wrapping

  - instanceof 연산자를 활용하지 않고 오버라이딩을 이용한 코드의 구현

package TrashCan;

class Box{
    public void wrap(){
        System.out.println("simple wrapping");
    }
}
class PaperBox extends Box{
    public void wrap(){
        System.out.println("paper wrapping");
    }
}
class GoldBox extends PaperBox{
    public void wrap(){
        System.out.println("gold wrapping");
    }
}

public class InstanceofOperator {
    public static void main(String[] args) {
        Box box = new Box();
        Box pBox = new PaperBox();
        Box gBox = new GoldBox();

        wrapping(box);
        wrapping(pBox);
        wrapping(gBox);
    }
    public static void wrapping(Box box){
        box.wrap();
    }
}

simple wrapping
paper wrapping
gold wrapping

  오버라이딩을 통해서 위의 코드와 동일한 결과를 구현하기 위해선 각 Box 클래스들의 메소드 이름을 통일해서 오버라이딩을 시켜야한다.

 

@Override 어노테이션

  @가 붙어있는 애들을 어노테이션이라고한다. @Override는 해당 메소드가 상위 클래스의 메소드를 오버라이딩하지 않는다면 컴파일 에러를 발생시키는 기능을 가진다.

package TrashCan;
class ParentAdder{
    public int add(int a, int b){
        System.out.println("Original");
        return a+b;
    }
}
class ChildAdder extends ParentAdder{
    @Override  //컴파일 에러 발생
    public double add(double a, double b){  //오버라이딩 안함
        System.out.println("not Overrided");
        return a+b;
    }
}
public class OverrideMistake {
    public static void main(String[] args) {
        ParentAdder adder = new ChildAdder();

        System.out.println(adder.add(3,4));  
    }
}

java: method does not override or implement a method from a supertype

 

'Java' 카테고리의 다른 글

Java- interface  (0) 2022.12.30
Java- Object 클래스  (0) 2022.12.29
Java- 배열  (0) 2022.12.27
Java- 콘솔 입출력  (0) 2022.12.27
Java- 문자열 결합의 최적화(StringBuilder 클래스)  (0) 2022.12.27