Java_OOP2

ChoRong0824·2023년 5월 10일
0

Java

목록 보기
16/31
post-thumbnail

Java_OOP2
추상클래스, 인터페이스, 다형성에 대해 문제 위주의 포스팅 할 예정입니다.


아래의 질문에 답해보시오

1) 추상 클래스란 ?
2) 추상 클래스와 일반 클래스의 차이점은?
3) 추상 클래스는 객체 생성이 가능한가?
4) 추상 클래스는 어떻게 사용하는가?
5) 추상 클래스의 역할은 무엇이고 왜 필요한 것인가?
6) 추상 클래스는 상속이 가능한가?
7) 추상 클래스의 추상 메서드는 꼭 오버라이딩하여 사용해야만 하는가?
8) 추상 메서드를 포함하고 있다면 추상 클래스여야 하는가?
9) 추상 클래스를 만들려면 abstract를 꼭 써야 하는가?


추상 클래스와 추상 메서드란 무엇인지 설명하고 관련된 예제 코드를 구현


추상클래스란,

  • abstract 를 작성해줌으로써 명시적으로 추상클래스라고 알려줌.
  • 구체적인 내용은 작성하지 않고, 공통적인 특징을 추상적으로 선언만 해둠.--> 리턴값 조차도 없이 메서드명만 선언
  • 구체적인 내용이 없기 때문에, 객체 생성을 못함.
abstract class Animal{ // abstract 를 작성해줌으로써 명시적으로 추상클래스라고 알려줌.
    // 추상클래스란, 구체적인 내용은 작성하지 않고, 공통적인 특징을 추상적으로 선언만 해둠.--> 리턴값 조차도 없이 메서드명만 선언
    abstract void cry();
}

그럼 도대체 왜 추상클래스를 만들고, 언제, 어떻게 써먹는 것인가 ?

  • 상속을 받아서 사용가능
    -> 즉, 추상클래스를 상속받은 자식클래스에서 해당 메서드를 용도에 맞게끔 오버라이딩(재정의)하여 사용한다.
    -> 즉, 추상클래스는 부모클래스가 되는 것임.

전체코드 및 내용 정리

  1. 추상(부모)클래스는 다른(자식) 클래스들의 공통적인 특징을 변수나 메서드로 정의만 해놓는 것을 말한다 --> 추상 메서드

  2. abstract 를 앞에 붙이고, 클래스 안에 추상 메서드를 포함하고 있다는 것을 제외하면 일반 클래스와 별반 차이가 없습니다.

  3. Field, Constructor, Method(추상메서드 말고 일반메서드)도 포함할 수 있다. --> void eat() 코드 확인

  4. 추상메서드를 하나라도 가지고 있는 클래스라면, 반드시 abstract 로 정의되어져 있어야합니다.

  5. 메서드 선언만 있고, 구체적인 내용은 없으므로, 객체를 생성할 수 없습니다.

  6. 따라서, 부모 클래스로서의 역할은 하지만, 구체적인 사용은 상속받은 자식 클래스에서 오버라이딩하여 사용해야 합니다 -- > 강제성

  7. 추상클래스에서 선언만 해놓음으로써 이후 새로운(자식) 클래스들이 이를 상속 받아 구현하므로 새로운 클래스를 작성할 때, 하나의 틀이 됩니다.

abstract, 왜 쓰냐 ?

  • 강제하기 위함 (일반 클래스에서는 강제성을 부여할 수 없어서)
    -> 부모(추상) 클래스가 선언해놓은 메서드를 상속받은 자식 클래스들이 이름 그대로 오버라이딩해서 구현하라고 강제하는 것이다.
    -> 상속받은 자식 클래스 입장에서는 자칫 상속만 받고, 재정의해서 사용을 안할 수도 있기 때문이다. (이때문에 abstract의 강제성이 필요)

    즉,
    일반 메서드로 구현하면, 누군가는 해당 메서드를 구현 안 할수도 있다.
    무조건 상속받은 자식 클래스 입장에서는 추상 메서드를 재정의해서 구현하도록 강제하기 위함 입니다.

    예) class Cat extends Animal{} // 이렇게만 구현해두면 ERR.
    반드시 재정의해서 사용해야함.
    Err : 추상 메서드를 오버라이딩 하지 않아서 발생.
    다시 말해,
    자식 클래스는 일단 부모(추상)클래스로 부터 상속받은 추상 메서드는 오버라이딩해야합니다.
    1개라도 추상 메서드를 포함하고 있다면, 추상 클래스여야만 합니다.


만약, 재정의 하고 싶지 않다면?

(참고로, 거의 재정의해서 사용함)

  • 자식 클래스에서 상속받은 추상 메서드를 구현하지 않는다면 자식 클래스도 abstract를 붙여서 추상으로 만들어준다.
  • class 앞에다가 abstract를 붙이면됨.


강제성 (추상클래스의 특징)

추상클래스로 만드는 이유,
강제성을 부여하기 위해 추상클래스를 사용함.
앞으로 만들어지는 자식 클래스들이, 적어도 추상클래스를 상속 받아서 목적에 맞게 오버라이딩(재정의)하여 구체적으로 사용하도록 함 (책임지게 하려고)
--> 추상클래스에 동일 특징을 메서드로 선언해둠

  • 예) 동물들은 모두 crying 하기때문에 cry 이라는 메서드를 추상클래스에 선언해둠.

전체코드

abstract class Animal{ // abstract 를 작성해줌으로써 명시적으로 추상클래스라고 알려줌.
    // 추상클래스란, 구체적인 내용은 작성하지 않고, 공통적인 특징을 추상적으로 선언만 해둠.--> 리턴값 조차도 없이 메서드명만 선언
    abstract void cry();

    // 일반메서드 생성
    void eat(){
        System.out.println("먹다.");
    }
}
class Dog extends Animal{
    @Override
    void cry(){ System.out.println("멍 멍");}
}

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

class Cow extends Animal{
    @Override
    void cry(){System.out.println("umme~~");}
}

class Animal2{  
    void fly(){System.out.println("FLYING ! ! !");}
}

public class AbstractClassMethod {
    public static void main(String[] args) {
        // 1. 추상 클래스는 구체적인 내용이 없기 때문에, 객체를 생성할 수 없다.
        // Animal dog = new Animal(); // ERR
        Animal2 dragonfly=new Animal2(); // No ERR
        dragonfly.fly();

        // [2] : 추상클래스 사용은 ? --> 상속을 받아서 사용가능
        // 즉, 추상(부모)클래스를 상속받은 자식클래스에서 해당 메서드를 오버라이딩(재정의)하여 사용한다.
        Dog Chorong =new Dog();
        Chorong.cry();
        Cat Ya = new Cat();
        Ya.cry();
        Cow cow = new Cow();
        cow.cry();

        // [3] :: Summary
        // 추상(부모)클래스는 다른(자식) 클래스들의 공통적인 특징을 변수나 메서드로 정의만 해놓는 것을 말한다 --> 추상 메서드
        // abstract 를 앞에 붙이고, 클래스 안에 추상 메서드를 포함하고 있다는 것을 제외하면 일반 클래스와 별반 차이가 없습니다.
        // Field, Constructor, Method(추상메서드 말고 일반메서드)도 포함할 수 있다. --> void eat() 코드 확인
        // 추상메서드를 하나라도 가지고 있는 클래스라면, 반드시 abstract 로 정의되어져 있어야합니다.
        // 메서드 선언만 있고, 구체적인 내용은 없으므로, 객체를 생성할 수 없습니다.
        // 따라서, 부모 클래스로서의 역할은 하지만, 구체적인 사용은 상속받은 자식 클래스에서 오버라이딩하여 사용해야 합니다 -- > 강제성

    }
}

여기서 문제,

class Cow extends Animal{
    //@Override
    void cry(){System.out.println("umme~~");}
}

클래스 부분의 메서드도 주석처리 하고, 객체생성 부분도 주석처리한다면 어떻게 될까?

정답 : Err. 이유 : (상속받는 순간)문법적으로 이미 틀렸기 때문에.


Err 해결

  1. 재정의
  2. 자식도 추상클래스로 만들어줌 (부모 추상클래스)
abstract class Cow extends Animal{
}

결론

  • 부모(추상)클래스에서 구현을 하지 않는 이유,
    해당 메서드의 구현이 상속받는 클래스에 따라서 달라질 수 있기 때문에 선언만 해둔 것입니다.
  • 마치 돈 많은 부모님이 엄청난 땅들을 상속해주고, 용도는 자식들이 알아서 사용해라~ 라는 느낌

쓰는 이유

  • 이러한 추상클래스는 여러명의 개발자가 작업할 때, 코드의 확장과 분업을 효율적으로 처리할 수 있게 해줍니다.

  • 분업화된 시스템에서 공통의 프로젝트를 진행할 때, 많이 사용되어지는 중요한 문법입니다.


인터페이스란 ?

(규격)

  • 사전적 의미
    결합부, 접속기 --> 사용자간 또는 컴퓨터간 통신이 가능하도록 해주는 디바이스나 프로그램
  • 큰 틀에서 본다면,
    자바에서의 인터페이스 개념도 사전적 의미와 비슷합니다.
  • 상호간 통신을 위해서는 "규격" 중요 --> (예) 일본 110v 가전제품을 한국으로 가지고 와도 "규격"이 맞지 않으므로 사용할 수 없습니다.
  • 일본의 가전기업들이 한국에서 전자 제품을 팔고 싶다면, 한국내 220v "규격"을 지켜서 만들어야만 팔 수 있습니다.
  • 이러한 "규격"을 인터페이스라 할 수 있고, 인터페이스는 하나의 "표준화"를 제공하는 것이라 할 수 있습니다.

추상클래스와 인터페이스는 매우매우 비슷하기 때문에, 헷갈려 할 수 있습니다.



추상클래스 VS 인터페이스 ?

추상 클래스와 인터페이스의 가장 큰 차이점

  • 추상클래스와 거의 비슷합니다 --> BUT, 그 추상화 정도가 더 높습니다 (더 엄격함)
    --> 따라서, 인터페이스에서는 일반 메서드나 멤버 필드(변수)를 가질 수 없습니다. (추상클래스는 가질 수 있었음) 즉, 추상메서드만 가질 수 있음

자바에 인터페이스 문법

  • 표준화 및 규격을 인터페이스로 제공합니다.

  • 따라서,
    어떤 클래스가 해당 인터페이스를 (상속해서)사용 한다면 인터페이스에 선언되어져 있는 메서드를 구현해야함. -> (이것만 보면, 추상클래스 같지만 추상클래스 x)
    쉽게 생각해서, 인터페이스는 클래스라고 생각하면 쉬움. but, 부를 땐 인터페이스.

  • 인터페이스는 class 키워드를 사용하지 않고 --> 별도의 interface 키워드를 사용함.
    a ) extends 키워드 사용해서 상속하지 않습니다.
    b ) 자바에서 별도의 인터페이스 표현을 통해서 상속을 합니다.
    c ) class --> extends VS interface --> implements , extends는 사용안함. (참고, implement는 사전적으로 구현하다라는 뜻을가짐)

  • 추상 클래스와 같이 메서드의 구체적인 내용은 기술되어져 있지 않으므로, 인터페이스를 상속받은 클래스에서 재정의(오버라이딩)하여 사용해야 합니다.

상속 VS 구현

  • 클래스와 인터페이스의 가장 큰 차이점중 하나는 상속입니다.

    클래스는 단일 상속만 가능합니다. (부모로부터, 하나만 상속 받을 수 있음)
    인터페이스는 다중 상속 가능.

  • (재차 강조)
    자바에서 클래스는 "단일 상속"만 가능하지만, 인터페이스는 "다중 상속"이 가능합니다.
    그러나, 인터페이스에서는 상속이라는 표현(extends)을 쓰지않고,
    "구현"의 의미를 강조하는 implements 사용


자바의 인터페이스 문법을 예제 코드로 구현

결과 출력

씻다.
공부하다.
놀다.
엄마 에게서 10000원 용돈을 받았습니다.
5000원 금액을 지출했습니다. [지출용도→편의점]
[훈련비→20000원 입금완료]

코드를 작성하기 전, 아래 4개의 인터페이스 질문에 답해보세요

(많이 맞출수록 고수)
  1. 인터페이스란 ?
  • 추상클래스와 거의 비슷하나 그 추상화 정도가 더 높다(더 엄격) -- > 일반 메서드나 멤버 필드(변수)를 가질 수 없습니다.
  • 표준화 및 규격을 인터페이스로 제공 --> 일종의 "설계도"
  • 따라서, 어떤 클래스가 해당 인터페이스를 사용(상속)한다면, 인터페이스에 선언되어져 있는 메서드를 구현.
  • 인터페이스는 interface 키워드 사용
  • 추상클래스와 같이 메서드의 구체적인 내용은 기술되어져 있지 않으므로,
    인터페이스를 상속받은 클래스에서 오버라이딩하여 사용
  1. 상속이란 ?
  • 클래스는 "단일 상속"만 가능, 인터페이스는 "다중 상속"이 가능 --> 가장 큰 차이점
  • class --> extends,
    interface --> implements --> 다중 상속을 구현 --> A,B --> 콤마( , )로 분리
  • 즉, 이를 이용하면 여러 개의 인터페이스로 부터 메서드를 받아올 수 있게 됨
    --> 다중 상속 구현
  1. 장점은 ?
  • 인터페이스를 이용하면 메서드의 추상적인 "선언"과 그 메서드들을 구체적인 "구현"부분을 분리시킬 수 있다 --> 매우 큰 창점.
    --> 즉, 분업화된 시스템에서 여러명의 개발자들이 작업(프로젝트)를 공동으로 할 수 있어 매우 효율적임.
  • 예) 하청을 주는 대기업(갑)은 하청업체(을)에 인터페이스만 제공 --> 각 하청업체(을)들이 이를 준수하여(=상속 받아) 개발.
    분업화된 시스템을 구축하여 "갑"과 "을"이 독립적으로 프로젝트 개발을 해나갈 수 있음 --> 매우 큰 장점
  1. 우선순위 (extends VS implements)
  • 상속을 받는 extends 키워드와 구현을 하는 implements 키워드가 동시에 쓰일 때 --> extends 키워드가 항상 먼저 쓰임
  • 예) class Student extends Person implements A,B
    --> Person을 먼저 상속받고, A,B 상속

코드

class Person{
    //Field
    String name;
    int age;
    int weight;

    //Constructor
    Person(String name, int age, int weight){
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
    //Method
    void wash(){System.out.println("Washing");}
    void study(){System.out.println("studying");}
    void play(){System.out.println("playing");}
}
interface Allowance{
    // void in(int price, String name); // 인터페이스는 일반 메서드 구현 못함.
    // abstract Method 만 사용가능.
    abstract void in(int price, String name);
    abstract void out(int price, String name);
}
interface Training{
    // abstract Method
    abstract void train(int training_pay, String name);
}
// 자식클래스
class Student extends Person implements Allowance,Training{
    // implements 는 Allowance,Training 를 구현하라고 강조 -- > 강제성
    //Field

    //Constructor
    //Student(){}
    Student(String name, int age, int weight){
        super(name, age, weight); // 값들을 부모에게 넘겨주는 부분 구현
    }
    //Method
    //인터페이스 부분 구현해줘야함
    @Override
    public void in(int price, String name){
        System.out.print(name + "에게서 " + price + "원 용돈을 받았습니다.\n");
    }
    @Override
    public void out(int price, String name){
        System.out.println(price + "원 금액을 지출. 지출용도 -->" + name);
    }
    @Override
    public void train(int training_pay,String name){
        System.out.println(name + "-->" + training_pay + "원 입금완료");
    }
}

public class Interface2 {
    public static void main(String[] args) {
        // 1. 객체 생성 (자식 클래스를 이용해서 객체생성)
        Student s1 = new Student("홍길동",20,85);
        
        // 클래스와 인터페이스로 부터 상속(Person)과 
        // 구현 (Allowance, Train)을 한 메서드들 호출하기
        s1.wash();
        s1.study();
        s1.play();
        s1.in(10000,"엄마");
        s1.out(5000, "편의점");
        s1.train(20000,"훈련비");
    }
}

참고

  • 인터페이스의 필드에서
    변수는 안되나 상수는 되므로 상수로 지정해주면 됨 --> public staticfinal을 붙여주면 됨.
interface Allowance{
    //Field
    public static final String aaa = "KOREA";
    public static final int bbb = 100;

    // abstract Method
    abstract void in(int price, String name);
    abstract void out(int price, String name);
}
  • 즉, 인터페이스 내 모든 멤버 필드(변수)는 public static final 이어야함.
    --> (예외 없음 = 항상 오는거 = 생략가능) --> 그냥 "타입 상수명" 지정해서 쓰면 됨
interface Allowance{
    //Field
     String aaa = "KOREA";
     int bbb = 100;
}
  • 상수 필드 사용 (객체 있을때 사용)
System.out.println(s1.bbb);
System.out.println(s1.aaa);
  • 객체 생성 없이 사용
System.out.println(Allowance.bbb);
System.out.println(Allowance.aaa);
  • 인터페이스내 모든 메서드는 public abstract 이어야함 --> 생략가능
    (컴파일러가 알아서 붙여줌)
  • 추상메서드는 선언만 되어있어야함.(일반 메서드 들어오면 안됨. 즉, 재정의 하면안됨)
  • private 사용불가
	 // abstract Method , 추상메서드를 구현해둔거
     void in(int price, String name);
     void out(int price, String name);

--> 개인적으로, public만 생략하고, abstract는 넣어주는게 직관적. (취향 차이)


다형성

다음의 객체 생성 방법중 틀린 것은?

(뒷부분에 답 기재해둠)

class Person {}
class Student extends Person {}

[1] Student s1 = new Student(); 
[2] Person s2 = new Student(); 
[3] Person p1 = new Person(); 
[4] Student s1 = new Person();

다형성이란 ?

  • 다형성이란 다양한 형태 또는 특성을 가진다는 의미
  • 자바와 같은 객체 지향 언어에서의 의미는 부모 클래스를 상속받은 자식 클래스의 인스턴스가 부모의 객체로도 사용되고,
  • 뿐만 아니라, 자식 클래스의 객체로도 사용될 수 있다는 다양한 상황을 의미합니다.

로직

자식클래스로부터 생성된 객체를 부모의 타입으로 만들어서 마치 부모의 객체처럼 사용 가능
(부모 클래스의 타입을 사용해서, 부모 객체를 생성함)

예시

부모 클래스 Bird가 있고, 이를 상속받은 자식 클래스 Parrot 이 있다고 가정하자.
이때, "앵무새가 말을 하네.. 허허~" 사람들이 이렇게 말할 수 있다.
그런데, 앵무새는 기 때문에 "새가 말을 하네.. 허허~" <-- 이렇게도 말할 수 있다.

  • (자식) 학생이 지나가네, (부모)누군가는 사람이 지나간다. 라고 할 수 있음.

  • (자식) 붕어빵 맛있네, (부모) 간식 맛있다.

  • (자식) 우동 맛있네, (부모) 음식 맛있다.

    즉,
    자식클래스를 통해서 객체를 생성하고 있지만, 타입은 부모클래스 타입으로 받을 수 있음.


핵심 정리

정리하면,
하위 클래스의 인스턴스(객체)는 보다 위인 상위 클래스의 인스턴스 (객체)로도 사용될 수 있습니다.
BUT 그 반대는 절대 절대 절대 안됩니다.
왜냐하면, 앵무새는 분명 그 상위인 라고 말할 수 있지만, 새는 종류가 많기 때문에 모든 새가 앵무새는 아니라는 것입니다.

Parrot p1 = new Parrot(); 
Bird p1 = new Parrot(); // 로도 생성가능

(상위 클래스의 인스턴스는 보다 밑인 하위 클래스의 인스턴스로도 사용될 수 있다 --> 절대 절대 틀림)

Bird aaa = new Parrot(); ========> 가능 OOOOOOOOOO
Parrot bbb = new Bird(); ==========> 불가능 XXXXXXXXXX

따라서,
상위(부모) 클래스의 인스턴스는 하위(자식)클래스의 인스턴스로 사용될 수 없습니다.

객체생성

부모타입 = 자식 클래스 // 가능
자식타입 = 자식 클래스 // 가능
부모타입 = 부모 클래스 // 가능
자식타입 = 부모 클래스 // 불가능
class Person2{}
class Student2 extends Person2{}

public class Polymorphism {
    public static void main(String[] args) {
        // 객체 생성
        Student2 s1 = new Student2(); //
        // 객체 생성 --> 타입은 부모 타입으로 생성
        Person2 s2 = new Student2(); // 정상. --> 하위(자식) 클래스로 객체를 만들면서,
        									  // 타입은 상위(부모) 타입을 쓰는 것이 가능 --> 다형성
        // 객체 생성
        Person2 aaa = new Person2(); // 정상
        // 객체 생성 --> 상위(부모)클래스로 객체를 생서앟면서 타입은 하위(자식) 타입을 쓰는 경우 --> ERR
        Student2 bbb = new Person2(); // 불가능. 
   }}

부모타입 객체 = new 자식클래스 //사용범위

자식 클래스로 생성하는 객체를 부모의 타입으로 받아서 객체를 생성하면 사용범위가 어떻게 되는지 ? (어려운 부분입니다. 여러번 보시는 것을 추천)

class Person{
	String str1= "난 부모 ㅋ르래스";
    void method1() {System.out.print("에이에잉에엥");}
    void pop(){System.out.print("POP");}
}
class Student extends Person{
	String str2 = "난 자식 클래스";
    void method1(){System.out.print("Overriding - AAA");}
    void sos(){System.out.print("SOS");}
}

--> 객체를 어떻게 생성하냐에 따라서 사용 범위가 정해짐
--> 사용이 안되면, 되게끔 만들어야함.

코드

class Person2{
    String str1 = "부모 클래스";
    void method1(){System.out.print("에이에잉에엥\n");}
    void pop(){System.out.print("POP\n");} // 부모가 가진 독자적인 메서드
}
class Student2 extends Person2{
    String str2 = "자식 클래스";
    void method1() {System.out.print("AAA\n");} // 오버라이딩해서 사용함
    void sos(){System.out.print("SOS\n");} // 자식이 갖는 독자적인 메서드
}

public class Polymorphism {
    public static void main(String[] args) {
        // [1] 객체 생성-- > 부모 + 자식 클래스의 모든 자원을 다 쓸 수 있다.
        Student2 s1 = new Student2(); //
        System.out.println(s1.str1);
        System.out.println(s1.str2);
        s1.method1(); // 오버라이딩된 것을 출력
        s1.sos(); // 자식 클래스의 모든 자원을 다 쓸 수 있어서 가능.
        s1.pop(); // POP
        System.out.println("===================================");
        // [!] 그런데, 만약 --> 자식클래스에서 오버라이딩된 부모클래스의 원본 메서드를 호출하고 싶다면 ? --> ???

        // [2] 객체 생성 --> 범위는 부모의 자원만을 쓸 수 있다. (?) -->
        Person2 s2 = new Student2(); // 부모타입으로 자식클래스를 통해 객체생성,
        System.out.println(s2.str1);
        // System.out.println(s2.str2);  // ERR : 부모자원만 사용가능.(자식 클래스에 있는 자원 사용 불가능)--> Person 타입으로 객체를 생성했기 때문에.
        s2.pop();
        // s2.sos(); // ERR --> 자식 자원 쓸 수 없음.
        s2.method1(); // ??? --> 에에엥 나올 줄 알았는데 에에엥이 안나오고, 자식 클래스의 오버라이딩 된 부분이 출력됨. --> 오버라이딩한거는 무조건 자식의 메서드로 실행됨

        // [!] : 그런데, 만약 --> 자식의 메서드를 바로 호출하고 싶다면 ? --> ???

        System.out.println("=================================");
        // [3] 객체 생성 --> 본인 클래스의 자원을 사용하는 부분임
        Person2 aaa = new Person2(); // 정상
        aaa.method1(); // 에이에잉에엥 출력
        // aaa.sos(); //ERR

        // 객체 생성 --> 불가능. ERR
        //Student2 bbb = new Person2(); // 불가능.
    }
}

--> 오버라이딩한거는 무조건 자식의 메서드로 실행됨


사용범위 문제

  1. Person s2 = new Student(); // 자식의 메서드를 바로 호출하고 싶다면?
  2. Student s1 = new Student(); // 자식 클래스에서 오버라이딩된 부모 클래스의 원본 메서드를 호출하고 싶다면?

Hint,
(이 문제는 다형성의 관계에서 부모, 자식 클래스 자원을 어떻게 쓸 수 있는지를 묻는 문제)

정답

  1. super 사용
클래스 코드
class Person2{
    String str1 = "부모 클래스";
    void method1(){System.out.print("에이에잉에엥\n");}
    void pop(){System.out.print("POP\n");
    } 
class Student2 extends Person2{
    String str2 = "자식 클래스";
    void method1() {System.out.print("AAA\n");} 
    void sos(){System.out.print("SOS\n");} 
   
   // 추가 
    void x(){
        method1();
        super.method1();
    }
}

// 메인 함수에서
// s1.x(); 를 해주면  method1(); 를 호출해줘서 "AAA"가 출력되는데,
// 클래스 x 메서드에서 super.method1(); 를 해주면 부모클래스의 메서드1을 호출하기 때문에,
// "에이에잉에엥" 이 출력됩니다.
  • 자식 클래스에서 오버라이딩된 부모 클래스의 원본 메서드를 호출하고 싶다면? --> super 사용

  1. 캐스트 사용
    ((자식클래스타입)자식객체).sss() --> 캐스트 해줌
코드
public class Polymorphism {
    public static void main(String[] args) {
        System.out.println("===================================");
        // [2] 객체 생성 --> 범위는 부모의 자원만을 쓸 수 있다. (?) -->
        Person2 s2 = new Student2(); // 부모타입으로 자식클래스를 통해 객체생성,
        System.out.println(s2.str1);
        s2.pop();
        s2.method1(); // ??? --> 에에엥 나올 줄 알았는데 에에엥이 안나오고, 
        // 자식 클래스의 오버라이딩 된 부분이 출력됨. --> 오버라이딩한거는 무조건 자식의 메서드로 실행됨
        
        // [!] : 그런데, 만약 --> 자식의 메서드를 바로 호출하고 싶다면 ? --> 캐스트 필요
        // s2.sss(); // ERR --> 캐스트 해줘야함.
        ((Student2)s2).sos(); // ((자식클래스타입)자식객체).sss() --> 캐스트 해줌
    }
}


추상 클래스와 상속을 사용한 다형성 예제

출력

앰블런스 지나가요~ 삐뽀삐뽀~
경운기 지나가요~ 덜컹덜컹~
스포츠카 지나가요~ 씽~

코드

abstract class Car{
    abstract void run();
}
class Ambulance extends Car{
    void run(){
        System.out.println("앰블런스 지나가요~ 삐뽀삐뽀~");
    }
}
class Cultivator extends Car{
    void run(){
        System.out.println("경운기 지나가요~ 덜컹덜컹~");
    }
}
class SportsCar extends Car {
    void run(){
        System.out.println("스포츠카 지나가요~ 씽~");
    }
}
public class Example {
    public static void main(String[] args) {
        // 1. 객체 생성
        Car c1= new Ambulance();
        Car c2 = new Cultivator();
        Car c3 = new SportsCar();
        
        // run() 메서드 호출
        c1.run();
        c2.run();
        c3.run();
    }
}

다형성을 활용한 객체 생성시 배열과 반복문을 사용하여 객체를 생성

  1. 반복문은 향상된 for문으로도 출력
    hint: 다형성을 이용한 객체 생성시 배열과 for문을 통해서 자동으로 객체를 생성할 수 있는지

출력

삐뽀삐뽀~
덜컹덜컹~
씽~ ------------------------- 삐뽀삐뽀~
덜컹덜컹~ 씽~

  1. 배열 길이가 3인 Car 객체 배열
		// [1] 배열 길이가 3인 Car 객체 배열 선언
        Car[] cars = new Car[3];
        System.out.println(cars[0]); 
        // __코드속의 문제__ 이걸 출력해본다면 뭐가 나올까? --> NULL --> 각 배열에는 아직 NULL 값만 존재함.
        cars = new Car[]{new Ambulance(), new Cultivator(), new SportsCar()};

        for (int i = 0; i < 3; i++) {
            System.out.println(cars[i]);
        }


  1. 부모 타입으로 자식 클래스로 객체 생성 (다형성)
		// [2] :: 1번 방법 말고 --> 자식 클래스로 객체 생성 --> 
        // 타입은 부모 타입으로 --> 이렇게 객체들로 바로 배열 초기화 --> 다형성 덕분.
        Car[] cars = {new Ambulance(), new Cultivator(), new SportsCar()};

향상된 for문

for( 타입 아무이름 : 배열명 )

        // [3] :: 반복문 돌면서 각 객체의 run() 메서드 호출
        for (int i = 0; i < cars.length; i++) {
        	System.out.println(cars[i]); // 각 객체가 생성된 메모리 공간의 주소 값을 출력함
            cars[i].run();
        }
        System.out.println("==============================");
        for(Car obj:cars) // 타입 아무이름 배열명
            obj.run();
        // for(Car i:cars) cars[i].run();  ----->>>> ERR. 향상된 for 문에서는 i.run(); 만 쓰면 됨.

향상된 for문 2개 이상일경우

		for(Car obj:cars) {   // 타입 아무이름 배열명
            System.out.println(obj); // 주소값 출
            obj.run();
        }

다형성을 사용하면 배열이나 파라미터 활용에서 좋다는데 자세히 코드로 설명

hint, 다형성의 개념과 필요성 그리고 어떤 점에서 활용할 때 좋은지


  • 아래의 객체 생성중 틀린 것은?
class Person {}
class Batman extends Person {}

Person[] persons = new Person[10]; 
persons[0] = new Person(); 
persons[1] = new Person(); 
persons[2] = new Batman();

Batman[] batmans = new Batman[10]; 
batmans[0] = new Batman(); 
batmans[1] = new Batman(); 
batmans[2] = new Person();

필자는, System.out.println(); 이 왜 모든 것을 다 받아 줄 수 있는지 생각안해보고 막연하게 써왔습니다.
이에 궁금했습니다. 왜 그런 것인지.
아래 코드를 보고, 이해하시면 좋을 것 같습니다 !

매개변수의 다형성 코드

class Person3 {}
class Batman extends Person3 {}

class Human{}
class Superman extends Human{}

public class Polymorphism2 {
    public static void main(String[] args) {

        // [1] :: 배열에서 다형성을 사용할 수 없다면..
        // 배열이란 ? --> 동일한 타입의 데이터를 하나로 묶어서 관리하는 자료구조 --> 다형성이 없다면, 각 객체별로 관리를 해야함.
        Person3[] person3s = new Person3[10]; // 만약, 다형성이 없다면 --> 이건 Person 전용이 됨.
            person3s[0] = new Person3();
            person3s[2] = new Batman();

        Batman[] batmens = new Batman[10]; // 이건 Batman 전용 --> Batman 객체만 생성할 수 있음
            batmens[0] = new Batman();
            //batmens[2] = new Person3();  --> ERR       // 부모클래스는 자식 타입으로 받아줄 수 없음


        // [2] :: 배열에서 다형성을 사용할 수 있기에,,
        Human[] humans = new Human[10];
        humans[0] = new Human();
        humans[1] = new Superman();

        // [3] :: 매개변수의 다형성
        // 프로그래밍 언어에서 함수나 메서드를 호출할 때는, 그에 맞는 적절한 파라미터를 보내줘야 한다.
        System.out.println(person3s[0]);
        System.out.println(person3s[2]);
        System.out.println(new Person3()); // 객체를 넣어줘도 출력이 됨.
        System.out.println(new Batman());
        System.out.println(new Human());
        System.out.println(new Superman()); // 결국 얘가 다형성이라는 문법이 가능하기 때문에, 얘가 만능인거임.
        // 즉,  System.out.println(); 메서드의 경우 어떤타입, 객체를 매개변수로 받더라도 에러없이 해당 객체의 값을 출력함 --> ???????????
        // 이러한 것이 가능한 이유 --> 바로 다형성을 활용하고 있기 떄문입니다.
        // 실제 메서드의 API 를 보면 --> public void println(Object x)로 되어있기 때문에(오브젝트로 되어있어서-->최상위파일)
        // 어떤 객체 타입이 전달되더라도 에러없이 출력이 되는 것임.
		// 결론적으로, Object는 가장 최상위 조상(창조주)이므로 어떤 객체를 보내도 그 보다 상위 타입이 되기 때문입니다.
    }
}
profile
컴퓨터공학과에 재학중이며, 백엔드를 지향하고 있습니다. 많이 부족하지만 열심히 노력해서 실력을 갈고 닦겠습니다. 부족하고 틀린 부분이 있을 수도 있지만 이쁘게 봐주시면 감사하겠습니다. 틀린 부분은 댓글 남겨주시면 제가 따로 학습 및 자료를 찾아봐서 제 것으로 만들도록 하겠습니다. 귀중한 시간 방문해주셔서 감사합니다.

0개의 댓글