JAVA SPRING/java

JAVA_011) 클래스의 상속과 다형성

오동순이 2023. 4. 27. 02:42

클래스의 상속과 다형성

클래스의 상속은 부모 클래스의 멤버(필드, 메서드, 이너클래스)를 내려받아 자식 클래스 내부에 포함시키는 것

 

Do it 자바 자료 참고

공통적인 부분이 부모클래스

 

 

상속의 장점은 ?  코드의 중복성 제거 (공통부분은 여러번 적을필요 X)

                           다형적 표현 가능O (부모(사람) ← 자식(직장인or대학생) O) 직장인은 사람이다. O

                                                          (부모)사람→ (자식)직장인 X                   사람은 직장인이다. X

 

상속 문법

상속 문법 O 자바의 클래스는 다중 상속 불가
class 자식클래스 extends 부모클래스 {

     }
class 자식클래스 extends 부모클래스1, 부모클래스2 {

     }

부모클래스가 하나인데 자식클래스 여러개는 가능!

 

클래스의 상속과 사용할 수 있는 멤버변수

class Human{
    String name;
    int age;
    void eat() {}
    void sleep() {}
}

class Student extends Human{
    int studentID;
    void goToSchool() {}
}

class Worker extends Human{
    int workerID;
    void goToWork() {};
}

public class Inheritance {
    public static void main(String[] args) {
        // Human 객체 생성
        Human h = new Human();
        h.name = "김현지";
        h.age = 11;
        h.eat();
        h.sleep();

        // Student 객체 생성
        Student s = new Student();
        s.name = "이민성";       // Human이 가지고 있는 속성
        s.age = 18;
        s.eat();
        s.sleep();

        s.studentID = 20230427; // 추가로 정의한 멤버
        s.goToSchool();         // 추가로 정의 한 멤버

        // Worker 객체 생성
        Worker w = new Worker();
        w.name = "박영훈";
        w.age = 33;
        w.eat();
        w.sleep();

        w.workerID = 19901211;  // 추가로 정의한 멤버
        w.goToWork();           // 추가로 정의한 멤버
    }
}

 

 

 

상속을 이용한 객체의 다형적 표현

책의 예제

// 상속관계 만들기
class A {}
class B extends A {}
class C extends B {}
class D extends B {}

public class Polymorphism {
    public static void main(String[] args) {
        // A 타입의 다형적 표현
        A a1 = new A();     // A는 A이다 O
        A a2 = new B();     // B는 A이다 O
        A a3 = new C();     // C는 A이다 O
        A a4 = new D();     // D는 A이다 O

        // B 타입의 다형적 표현
        // B b1 = new A();  // A는 B이다 X
        B b2 = new B();     // B는 B이다 O
        B b3 = new C();     // C는 B이다 O
        B b4 = new D();     // D는 B이다 O

        // C 타입의 다형적 표현
        // C c1 = new A();  // A는 C이다 X
        // C c2 = new B();  // B는 C이다 X
        C c3 = new C();     // C는 C이다 O
        // C c4 = new D();  // C는 D이다 X

        // D 타입의 다형적 표현
        // D d1 = new A(); // A는 D이다 X
        // D d2 = new B(); // B는 D이다 X
        // D d3 = new C(); // C는 D이다 X
        D d4 = new D();    // D는 D이다 O
    }
}

 

다형적 표현이라는 것을 개념적으로 이해 하기 위해서 한 번 바꿔서 해 봄

// 상속관계 만들기Animal
class Animal {}
class Bird extends Animal {}
class Charly extends Bird {}
class Demian extends Bird {}

public class Polymorphism {
    public static void main(String[] args) {
        // Animal 타입의 다형적 표현
        Animal a1 = new Animal();     // Animal는 Animal이다 O
        Animal a2 = new Bird();       // Bird는 Animal이다 O
        Animal a3 = new Charly();     // Charly는 Animal이다 O
        Animal a4 = new Demian();     // Demian는 Animal이다 O

        // Bird 타입의 다형적 표현
        // Bird b1 = new Animal();  // Animal는 Bird이다 X
        Bird b2 = new Bird();       // Bird는 Bird이다 O
        Bird b3 = new Charly();     // Charly는 Bird이다 O
        Bird b4 = new Demian();     // Demian는 Bird이다 O

        // Charly 타입의 다형적 표현
        // Charly c1 = new Animal();  // Animal는 Charly이다 X
        // Charly c2 = new Bird();    // Bird는 Charly이다 X
        Charly c3 = new Charly();     // Charly는 Charly이다 O
        // Charly c4 = new Demian();  // Charly는 Demian이다 X

        // Demian 타입의 다형적 표현
        // Demian d1 = new Animal(); // Animal는 Demian이다 X
        // Demian d2 = new Bird();   // Bird는 Demian이다 X
        // Demian d3 = new Charly(); // Charly는 Demian이다 X
        Demian d4 = new Demian();    // Demian는 Demian이다 O
    }
}

 

객체의 타입 변환

객체 타입 변환 - 업캐스팅과 다운캐스팅

사람 업캐스팅 학생은 사람이다 항상 O
     
학생 다운캐스팅 사람은 학생이다. 학생인 사람이라면 O
학생이 아닌 사람이라면 X

* 객체는 항상 업캐스팅을 할 수 있으므로 명시적으로 적어주지 않아도 컴파일러가 알아서 넣어준다.

* 다운캐스팅은 개발자가 직접 명시적으로 넣어줘야한다.

* 잘못된 다운캐스팅을 수행하게 된다면 ClassCastException이라는 예외가 발생하고, 프로그램이 종료된다.

 

선언 타입에 따른 사용할 수 있는 멤버

class A {
    int m = 3;
    void abc() {
        System.out.println("A 클래스");
    }
}

class B extends A {
    int n =4;
    void def() {
        System.out.println("B 클래스");
    }
}


public class Typecastion_2 {
    public static void main(String[] args) {
        // A 타입 / A 생성자
        A aa = new A();
        System.out.println(aa.m);   // 3
        aa.abc();                   // A 클래스

        // B 타입 / B 생성자
        B bb = new B();
        System.out.println(bb.m);   // 3
        System.out.println(bb.n);   // 4
        bb.abc();                   // A 클래스
        bb.def();                   // B 클래스

        // A 타입 / B 생성자 : 다형적 표현
        A ab = new B();
        System.out.println(ab.m);   // 3
        ab.abc();                   // A 클래스

    }
}

 

캐스팅 가능 여부를 확인하는 instanceof 키워드

규모가 커지거나 소스코드가 길어지면 생성 객체 타입 확인이 어려우므로, 캐스팅 가능여부를 확인 할 수 있는 키워드

참조변수 instanceof 타입 true 캐스팅가능 false 캐스팅불가

캐스팅 가능 여부를 확인할 수 있는 instanceof

/* 캐스팅 가능 여부를 확인 할 수 있는 instanceof */
class AA {}
class BB extends AA{} 

public class Typecasting_3 {
    public static void main(String[] args) {
        // instanceof
        AA aa = new AA();
        BB aabb = new BB();

        System.out.println(aa instanceof AA);    // true
        System.out.println(aabb instanceof AA);  // true

        System.out.println(aa instanceof BB);    // false
        System.out.println(aabb instanceof BB);  // true

        if(aa instanceof BB) {
            BB bb = (BB)aa;
            System.out.println("aa 를 BB 로 캐스팅 했습니다.");
        } else {
            System.out.println("aa는 BB 타입으로 캐스팅 불가능 !!"); // 출력
        }
        if(aabb instanceof BB) {
            BB bb = (BB)aabb;
            System.out.println("aabb 는 BB 캐스팅 했습니다.");      // 출력
        } else {
            System.out.println("aabb는 BB 타입으로 캐스팅 불가능 !!");
        }
        if("안녕" instanceof String){
            System.out.println("\"안녕\"은 String 클래스입니다. "); // 출력
        }

    }
}

 

메서드 오버라이딩

메서드 오버라이딩의 개념과 동작

메서드 오버라이딩은 부모 클래스에게 상속받은 메서드와 동일한 이름의 메서드를 재정의 하는 것

 ex. 우리가 동일한 위치에 동일한 파일을 저장할때 덮어쓰기가 수행되는 것과 같은 원리(똑같지는 않음)

 

* 부모 클래스의 메서드와 시그니처(매서드명, 입력매개변수의 타입과 개수) 및 리턴 타입이 동일해야 한다.

* 부모 클래스의 메서드보다 접근 지정자의 범위가 같거나 넓어야 한다.

 

메서드 오버라이딩의 대표적인 예

class Animal {
    void cry() {}
}

class Bird extends Animal {
    @Override
    void cry() {
        System.out.println("짹짹");
    }
}

class Cat extends Animal{
    @Override
    void cry() {
        System.out.println("야옹");
    }
}

class Dog extends Animal {
    @Override
    void cry() {
        System.out.println("멍멍");
    }
}

public class MethodOverriding_2 {
    public static void main(String[] args) {
        // 각 타입으로 선언 + 각 타입으로 생성
        Animal aa = new Animal();
        Bird bb = new Bird();
        Cat cc = new Cat();
        Dog dd = new Dog();

        aa.cry();
        bb.cry();       // 짹짹
        cc.cry();       // 야옹
        dd.cry();       // 멍멍
        System.out.println();

        // Animal 타입으로 선언 + 자식 클래스 타입으로 생성
        Animal ab = new Bird();
        Animal ac = new Cat();
        Animal ad = new Dog();

        ab.cry();       // 짹짹
        ac.cry();       // 야옹
        ad.cry();       // 멍멍
        System.out.println();

        // 배열로 관리
        Animal[] animals = {ab, ac, ad};
        for(Animal animal : animals) {
            animal.cry(); // 짹짹 야옹 멍멍
        }

    }
}

 

 

 

* 메서드 오버라이딩과 메서드 오버로딩

 

메서드 오버라이딩과 메서드 오버로딩 예

class AAA {
    void print1() {
        System.out.println("A 클래스 print1");
    }
    void print2() {
        System.out.println("A 클래스 print2");
    }
}

class BBB extends AAA {
    @Override
    void print1() {
        System.out.println("B 클래스 print1");
    }
    void print2(int a) {
        System.out.println("B 클래스 print2");
    }
}

public class MethodOverriding_3 {
    public static void main(String[] args) {
        // A 타입 선언 / A 생성자 사용
        AAA aaa = new AAA();
        aaa.print1();   // A클래스 print1
        aaa.print2();   // A클래스 print2
        System.out.println();
        
        // B 타입 선언 / B 생성자 사용
        BBB bbb = new BBB();
        bbb.print1();   // A클래스 print1
        bbb.print2();   // A클래스 print2
        bbb.print2(3);  // B클래스 print2
        System.out.println();

        // A 타입 선언 / B 생성자 사용
        AAA aba = new BBB();
        aba.print1();   // B클래스 print1
        aba.print2();   // A클래스 print2

    }
}

 

메서드 오버라이딩과 접근 지정자

부모 클래스 메서드의 접근 지정자 메서드 오버라이딩 할 때 사용할 수 있는 접근 지정자
public public
protected public, protected
default public, protected, default
private public, protectec, default, private
메서드 오버라이딩 할 때 사용할 수 있는 접근 지정자

* 사실 이건 잘 모르게..따리

 

인스턴스 필드와 정적 멤버의 중복

인스턴스 필드의 중복

class Apple {
    int m = 3;
}
class Banana extends Apple {
    int m = 4;
}

public class OverlapInstanceField {
    public static void main(String[] args) {
        // 객체 생성
        Apple aa = new Apple();
        Banana bb = new Banana();
        Apple ab = new Banana();

        // 인스턴스 필드
        System.out.println(aa.m); // 3
        System.out.println(bb.m); // 4
        System.out.println(ab.m); // 3


    }
}

 

정적필드의 중복

class E {
    static int m = 3;
}

class F extends E {
    static int m = 4;
}

public class OverlapStaticField {
    public static void main(String[] args) {
        // 클래스명으로 바로 접근
        System.out.println(E.m);    // 3
        System.out.println(F.m);    // 4
        System.out.println();

        // 객체 생성
        E ee = new E();
        F ff = new F();
        E ef = new F();

        // 생성한 객채로 정적 필드 호출
        System.out.println(ee.m);   // 3
        System.out.println(ff.m);   // 4
        System.out.println(ef.m);   // 3
    }
}

 

정적 메서드의 중복

class W {
    static void print() {
        System.out.println("W 클래스");
    }
}

class Q extends W {
    static void print() {
        System.out.println("Q 클래스");
    }
}

public class OverlapStaticMethod {
    public static void main(String[] args) {
        // 클래스명으로 바로 접근
        W.print();  // W 클래스
        Q.print();  // Q 클래스
        System.out.println();

        // 객체 생성
        W ww = new W();
        Q qq = new Q();
        W wq = new Q();

        // 객체를 통한 메서드 호출
        ww.print();     // W 클래스
        qq.print();     // Q 클래스
        wq.print();     // W 클래스

    }
}

 

 

* 인스턴스 멤버와 정적 멤버의 중복 정리

인스턴스 멤버  /  static 멤버 오버라이딩Overriding 여부

인스턴스 필드 인스턴스 메서드 정적 static 필드 정적 static 메서드
오버라이딩 X 오버라이딩 O 오버라이딩 X 오버라이딩 X
  메서드 오버라이딩    
A        a       =      new      B();
A
인스턴스(instance) 필드
   정적(static)필드 
   정적(static)메서드
B()
인스턴스(instance) 메서드

 

 

super 키워드와 super() 메서드

부모의 객체를 가리키는 super 키워드

 

멤버 앞에 있는 참조변수를생략(this.) 했을 때의 메서드 호출

class A {
    void abc() {
        System.out.println("A클래스의 abc()");
    }
}

class B extends A {
    void abc() {
        System.out.println("B클래스의 abc()");
    }
    void bcd() {
        abc(); // this.abc();
    }
}

public class SuperKeyword_1 {
    public static void main(String[] args) {
        // 객체 생성
        B bb = new B();

        // 메서드 호출
        bb.bcd();       // B클래스의 abc()
    }
}

 

멤버 앞에 있는 super 키워드를 사용했을 때으 메서드 호출

class C {
    void abc() {
        System.out.println("C클래스의 abc()");
    }
}

class D extends C {
    void abc() {
        System.out.println("D클래스의 abc()");
    }
    void bcd() {
        super.abc(); // 부모 클래스 객체의 abc() 메서드 호출
    }
}

public class SuperKeyword_2 {
    public static void main(String[] args) {
        // 객체 생성
        D dd = new D();

        // 메서드 호출
        dd.bcd(); // C클래스의 abc()
    }
}

 

super()메서드의 기능 및 컴파일러에 따라 super() 자동 추가

package superMethod;

class A {
    A() {
        System.out.println("A 생성자");
    }
}
class B extends A {
    B() {
        super(); // 생략했을 때 컴파일러가 자동 추가(부모클래스의 생성자 호출)
        System.out.println("B 생성자");
    }
}
class C {
    C(int a) {
        System.out.println("C 생성자");
    }
}
class D extends C {
    D() {
        super(3);
    }
}

public class SuperMethod_1 {
    public static void main(String[] args) {
        // A 객체 생성
        A aa = new A();     // A 생성자
        System.out.println();

        // B 객체 생성
        B bb = new B();     // B 생성자
    }
}

 

this() 메서드와 super() 메서드의 혼용

package superMethod;

class AA {
    AA() {
        this(3);
        System.out.println("AA 생성자 1");
    }
    AA(int a) {
        System.out.println("AA 생성자 2");
    }
}
class BB extends AA{
    BB() {
        this(3);
        System.out.println("BB 생성자 1");
    }
    BB(int a) {
        System.out.println("BB의 생성자 2");
    }
}

public class SuperMethod_2 {
    public static void main(String[] args) {
        // AA 객체 생성
        AA aa1 = new AA();
        System.out.println();
        AA aa2 = new AA(3);
        System.out.println();

        // BB 객체 생성
        BB bb1 = new BB();
        System.out.println();
        BB bb2 = new BB(3);

    }
}

출력 결과

AA 생성자 2
AA 생성자 1         // aa1

AA 생성자 2         // aa2

AA 생성자 2
AA 생성자 1
BB의 생성자 2
BB 생성자 1         // bb1

AA 생성자 2
AA 생성자 1
BB의 생성자 2     // bb2

왜 2가 먼저 나올까?? this. 로가서 해당하는 int a 가 있는 거 먼저 받고 다음으로 실행되는거 같긴함..

으아아ㅏ..

 

최상위 클래스 Object 

자바의 모든 클래스는 Object를 상속받는다.

Object 클래스

자바의 모든 클래스는 Object의 자식 클래스 = 자바의 모든 클래스는 Object의 메서드를 가짐

 

ObjectMethod_toString

class A {
    int a = 3;
    int b = 4;
}
class B {
    int a =3;
    int b =4;
    
    public String toString() {
        return "필드값(a, b) = " + a +" "+ b;
    }
}


public class ObjectMethod_toString {
    public static void main(String[] args) {
        // 객체 생성
        A a = new A();
        B b = new B();

        // 메서드 호출
        System.out.printf("%x\n", a.hashCode()); // hashcode를 16진수로 표현
        System.out.println(a.toString()); // toString()을 생략했을때는 자동으로 추가
        System.out.println(b);
    }
}

 

ObjectMethod_equals

class AA {
    String name;
    AA(String name) {
        this.name = name;
    }
}

class BB {
    String name;
    BB(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if(this.name == ((BB)obj).name) {
            return true;
        } else return false;
    }
}

public class ObjectMethod_equals {
    public static void main(String[] args) {
        AA a1 = new AA("안녕");
        AA a2 = new AA("안녕");
        
        System.out.println(a1 == a2);       //false
        System.out.println(a1.equals(a2));  // false

        BB b1 = new BB("안녕");
        BB b2 = new BB("안녕");
        System.out.println(b1 == b2);       // false
        System.out.println(b1.equals(b2));  // true
    }
}

 

ObjectMethod_hashcode

import java.util.HashMap;

class AAA {
    String name;
    AAA(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if(this.name == ((AAA)obj).name) {
            return true;
        } else return false;
    }
    @Override
    public String toString() {
        return name;
    }
}

class BBB {
    String name;
    BBB(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if(this.name == ((BBB)obj).name) {
            return true;
        } else return false;
    }
    @Override
    public String toString() {
        return name;
    }
}

public class ObjectMethod_hashcode {
    public static void main(String[] args) {
        HashMap<Integer, String> hm1 = new HashMap<>();
        hm1.put(1, "데이터1");
        hm1.put(1, "데이터2");
        hm1.put(2, "데이터3");
        
        System.out.println(hm1); 
        // {1=데이터2, 2=데이터3}

        HashMap<AAA, String> hm2 = new HashMap<>();
        hm2.put(new AAA("첫 번째"), "데이터1");
        hm2.put(new AAA("첫 번째"), "데이터2");
        hm2.put(new AAA("두 번째"), "데이터3");

        System.out.println(hm2); 
        // {첫 번째=데이터2, 첫 번째=데이터1, 두 번째=데이터3}

        HashMap<BBB, String> hm3 = new HashMap<>();
        hm3.put(new BBB("첫 번째"), "데이터1");
        hm3.put(new BBB("첫 번째"), "데이터2");
        hm3.put(new BBB("두 번째"), "데이터3"); 

        System.out.println(hm3);
        // 첫 번째=데이터2, 두 번째=데이터3, 첫 번째=데이터1}
    }
}