순간을 성실히, 화려함보단 꾸준함을

6주차 과제: 상속 본문

백기선님과 함께 하는 자바 스터디

6주차 과제: 상속

폭발토끼 2021. 6. 12. 22:20

목표

자바의 상속에 대해 학습하세요.

학습할 것 (필수)

  1. 자바의 상속의 특징
  2. super 키워드
  3. 메소드 오버라이딩
  4. 다이나믹 메소드 디스패치(Dynamic Method Dispatch)
  5. 추상 클래스
  6. final 키워드
  7. Object 클래스

자바의 상속의 특징

상속이란?

- 부모클래스의 변수 또는 메소드를 자식 클래스가 물려받아 그대로 사용 가능하게 해주는 것을 뜻한다. 부모클래스를 superclass 자식클래스를 subclass라고 칭한다.

extends라는 키워드를 사용하여 상속을 지정할 수 있다.

class Parent{
    String parent = "I'm Parent";
    public void ret_Parent(){
        System.out.println("I'm Parent Method");
    }
}
public class Child extends Parent{
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Child cur = new Child();

        System.out.println(cur.parent); //I'm Parent 출력
        System.out.println(cur.child); //I'm child 출력

        cur.ret_Parent(); //I'm Parent Method 출력
        cur.ret_Child(); //I'm Child Method 출력
    }
}

- 자바는 다중상속을 지원하지 않는다.

package com.example.demo;

class Ancestor{
    String ancestor = "I'm Ancestor";
    public void ret_Ancestor(){
        System.out.println("I'm Ancestor Method");
    }
}
class Parent{
    String parent = "I'm Parent";
    public void ret_Parent(){
        System.out.println("I'm Parent Method");
    }
}
public class Child extends Parent,Ancestor{  //컴파일 에러 발생
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Child cur = new Child();

        System.out.println(cur.parent);
        System.out.println(cur.child);

        cur.ret_Parent();
        cur.ret_Child();
    }
}

- 모든 클래스의 최상위 클래스는 Object 클래스이다. 이 말이 무엇인지 알아보자

class Parent{
    String parent = "I'm Parent";

    /*@Override
    public String toString(){
        return parent;
    }*/
}
public class Child {
    public static void main(String[] args) {
        Parent p = new Parent();
        System.out.println(p.toString()); //Parent@79fc0f2f 출력
    }
}

위의 소스를 실행하면 어떤 이상한 값이 나올 것 이다. 바로 그 이상한 값p주소값이 해당된다. 근데 toString 이라는 메소드를 따로 정의를 한 것도 아닌데 왜 우린 사용할 수 있을까???
바로 최상위 클래스인 Object클래스가 가지고 있는 메소드이기 때문이다.
즉, 모든 클래스는 Object클래스의 하위 클래스이므로 Object클래스가 가지고 있는 메소드를 전부 사용할 수 있는 것 이다.
우린 이 메소드를 재정의할 수도 있다.

class Parent{
    String parent = "I'm Parent";

    @Override
    public String toString(){
        return parent;
    }
}
public class Child {
    public static void main(String[] args) {
        Parent p = new Parent();
        System.out.println(p.toString()); //I'm Parent 출력
    }
}

위의 소스에서 주석을 해제한 소스이다. 전과는 다르게 I'm Parent가 출력되는 것을 확인 할 수 있다. 바로 toString() 메소드를 재정의 해주었기 때문에 이런 결과값이 나오는 것이다.

특징

  • 맴버변수, 맴버메소드는 상속이 되지만 생성자는 상속이 가능하지 않다.
  • 상속을 통해 클래스들을 계층적으로 구성할 수 있는 방법을 제공해 준다
  • 상속받은 자식 클래스는 부모 클래스의 맴버변수와 메소드를 재정의 할 수 있다.
  • 상속의 목적
  • 상속을 통해 일련의 클래스에 대한 공통적인 규약을 정의하고 적용하는 것

super 키워드

super 키워드란?
- 부모 클래스 객체를 즉시 참조할 때 사용되는 참조변수 입니다.

어떤 경우에 사용을 하는가?

- 부모 클래스의 인스턴스 변수와 메소드를 즉시 참조할때

class Ancestor{
    int age=100;
    String ancestor = "I'm Ancestor";
}
class Parent extends Ancestor{
    int age=50;
    String parent = "I'm Parent";
    public void display(){
        System.out.println(super.age);
    }
}
public class Child {
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Parent p = new Parent();
        p.display(); //100출력
    }
}

분명히 Parent에 대한 인스턴스를 생성했음에도 불구하고 super키워드로 부모클래스에 접근하여 age=100의 값을 도출하는 것을 확인 할 수 있습니다.

- super()를 통해 부모 클래스의 생성자를 호출할때

class Ancestor{
    int age=100;
    String ancestor = "I'm Ancestor";
    public Ancestor(){
        System.out.println(ancestor);
    }
}
class Parent extends Ancestor{
    int age=50;
    String parent = "I'm Parent";
    public Parent(){
        super();
    }
}
public class Child {
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Parent p = new Parent(); //I'm Ancestor 출력
    }
}

지난 포스팅에도 언급한 대로 하위클래스의 인스턴스를 생성하면 부모클래스의 생성자는 자동호출이 됩니다.
즉, 반드시 super키워드를 사용하지 않더라도 부모 클래스의 생성자를 호출하게 되는 것 이죠.
더 정확히는 super() 라는 소스가 생략 되어 있는 것 처럼 보이는 것 입니다.

class Ancestor{
    int age=100;
    String ancestor = "I'm Ancestor";
    public Ancestor(){
        System.out.println(ancestor);
    }
}
class Parent extends Ancestor{
    int age=50;
    String parent = "I'm Parent";
    public Parent(){
        //super();
    }
}
public class Child {
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Parent p = new Parent(); //I'm Ancestor 출력
    }
}

분명히 Parent class에서 super() 키워드를 주석처리 했음에도 불구하고 정상적으로 실행이 되고 출력또한 전의 소스와 동일하다는 것을 볼 수 있습니다.
즉, 따로 super()라는 키워드를 선언해 주지 않더라도 기본적으로 super()는 실행이 되는 것 이고 이 때문에 부모 클래스의 생성자를 호출하게 되는 것 입니다.

class Ancestor{
    int age=100;
    String ancestor = "I'm Ancestor";
    /*public Ancestor(){
        System.out.println(ancestor);
    }*/
    public Ancestor(int age){

    }
}
class Parent extends Ancestor{
    int age=50;
    String parent = "I'm Parent";
    public Parent(){  //Error 발생
        //super();
    }
}
public class Child {
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Parent p = new Parent();
    }
}

따라서 부모클래스의 NO-args생성자를 선언해주지 않고 args생성자만 떡하니 선언해 준 상태라면 Error가 발생하게 되는 것 입니다.
이를 No-args 생성자를 선언해 줌으로써 해결을 할 수 있지만 우린 super키워드를 배웠으니 super(int값)을 써줌으로써 해결할 수 있는 것 입니다.

class Ancestor{
    int age=100;
    String ancestor = "I'm Ancestor";
    /*public Ancestor(){
        System.out.println(ancestor);
    }*/
    public Ancestor(int age){
        System.out.println("Ancestor age : "+age);
    }
}
class Parent extends Ancestor{
    int age=50;
    String parent = "I'm Parent";
    public Parent(){
        super(10);
    }
}
public class Child {
    String child = "I'm child";
    public void ret_Child(){
        System.out.println("I'm Child Method");
    }
    public static void main(String[] args) {
        Parent p = new Parent(); //10 출력
    }
}

정상적으로 10이 출력되는 것을 확인할 수 있습니다.

반드시 기억해야 할 것
- super() 키워드는 생성자를 선언하면 디폴트로 정의되어 있다. 다만 따로 선언을 해주지 않더라도 보이지 않을 뿐이다.
- 부모 클래스에서 args생성자를 따로 선언을 해주었다면 반드시 No-args생성자를 선언을 해주거나 super(args) 를 선언해주자

메소드 오버라이딩

메소드 오버라이딩이란?
- 상속관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메소드로 다시 정의하는 것이라고 할 수 있다.
자바에서는 private으로 정의된 맴버를 제외한 모든 메소드를 상속받는다

조건

1) 메소드의 선언부는 기존 메소드와 완전히 같아야 한다. 즉, 이름이 무조건 같아야 한다.(단, 타입은 변경할 수 있다)

class Parent {
    int age = 50;
    String str = "I'm Parent";

    public void Print_Str() {
        System.out.println(str);
    }
}

public class Child extends Parent {
    String str = "I'm Child";

    public void Print_str() {
        System.out.println(str);
    }
    public static void main(String[] args) {
        Parent parent = new Parent();
        parent.Print_Str(); //I'm Parent 출력 Child child = new Child(); child.Print_str(); //I'm Child 출력
    }
}

2) 부모 클래스의 메소드보다 접근 제어자를 더 좁은 범위로 변경할 수 없다.

class Parent {
    int age = 50;
    String str = "I'm Parent";

    public void Print_Str() {
        System.out.println(str);
    }
}

public class Child extends Parent {
    String str = "I'm Child";

    private void Print_Str() { //Error 발생 
        // System.out.println(str); 
    } public static void main(String[] args) { 
        Child child = new Child(); 
    }
}

3) 부모 클래스의 메소드보다 더 큰 범위의 예외를 선언할 수 없다.주의할 점은 단순히 예외의 갯수 가 문제되는 것이 아니라 범위 가 문제가 되는 것이다.부모 클래스의 메소드에는 2개의 예외처리를 해주었지만, 자식클래스에선 단 1개의 예외처리만 해주었다. 하지만 Exception 예외가 부모클래스의 2개의 예외보다 더 넓은 범위의 예외 범위를 가지고 있기 때문에 Error 가 발생하는 것이다.

class Parent {
    int age = 50;
    String str = "I'm Parent";

    public void Print_Str() throws IOException, SQLDataException {
        System.out.println(str);
    }
}

public class Child extends Parent {
    String str = "I'm Child";

    public void Print_Str() throws Exception { //Error 발생 
        // System.out.println(str); 
    } 
    public static void main(String[] args) { 
        Child child = new Child(); 
    } 
}

다이나믹 메소드 디스패치(Dynamic Method Dispatch)

디스패치란?
- 어떤 메소드를 호출할 것인가를 결정하는 과정을 뜻한다. 즉, 메소드의 의존성을 결정하는 과정이라고 할 수 있다.
다이나믹 메소드 디스패치란?
- 컴파일 시점에선 어떤 메소드를 호출하는지 알 수 없음을 뜻한다.즉, 런타임 시점에 할당된 객체의 타입을 파악하고 메소드를 실행하는 것을 뜻한다

    class Parent{
     int age=50;
     String str = "I'm Parent";
     public void Print_Str(){
         System.out.println(str);
     }
    }
    public class Child extends Parent{
     String str = "I'm Child";
     public static void main(String[] args) {
         Parent parent1 = new Child();
         parent1.Print_Str();            //???
     }
    }

뭐가 출력이 될까?
정답은 I'm Parent이다.
이 이유는 바로 다형성 때문이다.
소스를 하나씩 살펴보면 Parent라는 자료형의 변수를 선언하고 Child 에 대한 객체를 생성하였다.
Parent parent1 = new Child() 이 한문장을 그림으로 나타내어 보자

이렇게 parent1 의 인스턴스는 stack메모리에 적재되고 이 parent1이라는 변수에 Child의 객체의 주소값을 담게 된다.
그러면 이때 우린 Print_Str() 이라는 메소드를 호출했지만 현재 parnet1 이 가리키고 있는 Child 객체에는 Print_Str() 이라는 메소드는 존재하지 않는다.
이때 상속을 받고 있는 지 확인한 후 상속을 받고 있는 부모클래스에서 Print_Str() 이라는 메소드가 있는 지 확인하고 있으면 호출하는 것이다.
따라서 I'm Parent 가 출력이 되는 것이다.

그럼 만약에 ChildPrint_Str() 이 존재했더라면????
그러면 당연히 ChildPrint_Str() 을 실행하게 될 것이다.

이와 같이 어떤 메소드를 실행할 것인지 런타임 시점이 아니면 알 수 없다. 이 때문에 다이나믹 메소드 디스패치 라고 부른다.

+더블 디스패치(Double Dispatch)

더블 디스패치란?
- 다이나믹 디스패치가 2번 발생하는 것을 뜻한다. 말 보단 소스를 통해 이해해보자

public class Double_Dispatch {
    interface Post {void postOn(SNS sns);}
    static class Text implements Post{
        public void postOn(SNS sns){
            System.out.println("Text => " + sns.getClass().getSimpleName());
        }
    }
    static class Picture implements Post{
        public void postOn(SNS sns){
            System.out.println("Pictrue => " + sns.getClass().getSimpleName());
        }
    }
    interface SNS{void post();}
    static class Facebook implements SNS{
        public void post(){ }
    }
    static class Twitter implements SNS{
        public void post(){ }
    }
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(),new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(),new Twitter());

        posts.forEach(p->sns.forEach(s->p.postOn(s)));
        /* 위의 한문장과 동일한 로직
        for(Post p:posts){
            for(SNS s:sns){
                p.postOn(s);
            }
        }
        */
    }
}

대표적인 더블 디스패치에 관한 소스이다. 총 2번의 다이나믹 디스패치 발생하는데

1. Post 에서 어떤 postOn 메소드를 사용해야 하는지?
2. SNS 에서 어떤 post 메소드를 사용해주어야 하는지?

이렇게 총 2번의 다이나믹 디스패치가 이루어진다.

그러나 위의 소스를 곰곰이 생각해보면 왜 굳이(?) 저렇게 로직을 만들어 놓았을까??하는 궁금증이 생기기 마련이다.
이유는 postOn이라는 메소드는 하는 일이라곤 그냥 자기의 클래스 이름을 뱉어내는 것 뿐 더도 덜도 일을 안하기 때문이다. 그러나 우린 실제로 다양한 상황에 놓이게 되면 각각의 조합에 해당하는 일들이 많아지게 된다. 그러면 if문을 사용하여 구분지어주면 된다. 이렇게 말이다.

public class Double_Dispatch {
    interface Post {void postOn(SNS sns);}
    static class Text implements Post{
        public void postOn(SNS sns){
            if(sns instanceof Facebook){
                //sns가 Facebook 일때
                System.out.println("Text => "+sns.getClass().getSimpleName());
            }
            if(sns instanceof Twitter){
                //sns 가 Twitter 일때
                System.out.println("Text => "+sns.getClass().getSimpleName());
            }
        }
    }
    static class Picture implements Post{
        public void postOn(SNS sns){
            if(sns instanceof Facebook){
                //sns가 Facebook 일때
                System.out.println("Pictrue => " + sns.getClass().getSimpleName());
            }
            if(sns instanceof Twitter){
                //sns 가 Twitter 일때
                System.out.println("Pictrue => " + sns.getClass().getSimpleName());
            }

        }
    }
    interface SNS{void post();}
    static class Facebook implements SNS{
        public void post(){ }
    }
    static class Twitter implements SNS{
        public void post(){ }
    }
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(),new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(),new Twitter());

        posts.forEach(p->sns.forEach(s->p.postOn(s)));
        /* 위의 한문장과 동일한 로직
        for(Post p:posts){
            for(SNS s:sns){
                p.postOn(s);
            }
        }
        */
    }
}

어떤가??매우 간단하게 사용할 수 있을 것 처럼 보이는가?? 그러나 이 코드는 치명적인 단점을 가지고 있다.

>오브젝트의 타입을 판별하는데 if문을 사용하는 것
만약 Google 이라는 클래스를 추가한 상태라고 하면 우린 postOn 이라는 메소드에 if문을 추가하여 Google 에 대한 실행처리를 코딩해야된다. 그러나 만약 수백개의 클래스들이 존재한다고 한다면 이는 너무 노가다성이 되버리기 때문에 생산성이 최악이다.
static class Text implements Post{
        public void postOn(SNS sns){
            if(sns instanceof Facebook){
                //sns가 Facebook 일때
                System.out.println("Text => "+sns.getClass().getSimpleName());
            }
            if(sns instanceof Twitter){
                //sns 가 Twitter 일때
                System.out.println("Text => "+sns.getClass().getSimpleName());
            }
            if(sns instanceof Google){
                //sns 가 Google 일때
                System.out.println("Text => "+sns.getClass().getSimpleName());
            }
        }
    }

이런 문제점을 어떻게 해결해야 될까???바로 SNS interface에서 메소드들을 구분지어 선언해주는 것이다.

public class Double_Dispatch {
    interface Post {void postOn(SNS sns);}
    static class Text implements Post{
        public void postOn(SNS sns){
            sns.post(this);
        }
    }
    static class Picture implements Post{
        public void postOn(SNS sns){
            sns.post(this);
        }
    }
    interface SNS{
        void post(Text text);
        void post(Twitter twitter);
    }
    static class Facebook implements SNS{
        public void post(Text text){ }
        public void post(Twitter twitter){ }
    }
    static class Twitter implements SNS{
        public void post(Text text){ }
        public void post(Twitter twitter){ }
    }
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(),new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(),new Twitter());

        posts.forEach(p->sns.forEach(s->p.postOn(s)));
        /* 위의 한문장과 동일한 로직
        for(Post p:posts){
            for(SNS s:sns){
                p.postOn(s);
            }
        }
        */
    }
}

어떤가??instanceof를 사용한 것보다 훨씬 객체지향적이지 않는가?? 바로 이런 방법을 통해 클래스들이 추가가 되더라도 if문을 덕지덕지 사용하지 않고도 깔끔하게 사용할 수 가 있는 것이다.
어떤 코드를 추가하더라도 의존하고 있는 이전 코드의 변경이 이루어지지 않는다는 것이 굉장히 매력적인 것이다.

하지만 위 소스를 보니 다시 궁금증이 생긴다. 왜 굳이 SNSinterface에 메소드들을 구분시켜주는가? 그냥 Post에다가도 메소드들을 구분지어주면 되는거 아니야???라는 의문점 말이다.
한번 해보자

public class Double_Dispatch {
    interface Post {
        void postOn(Text text);
        void postOn(Picture picture);
    }
    static class Text implements Post{
        public void postOn(Text text){ }
        public void postOn(Picture picture){ }
    }
    static class Picture implements Post{
        public void postOn(Text text){ }
        public void postOn(Picture picture){ }
    }
    interface SNS{
        void post();
    }
    static class Facebook implements SNS{
        public void post(){}
    }
    static class Twitter implements SNS{
        public void post(){}
    }
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(),new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(),new Twitter());

        posts.forEach(p->sns.forEach(s->p.postOn(s)));            //Compile Error 발생
        /* 위의 한문장과 동일한 로직
        for(Post p:posts){
            for(SNS s:sns){
                p.postOn(s);
            }
        }
        */
    }
}

posts.forEach(p->sns.forEach(s->p.postOn(s))); 이 문장에서 컴파일 에러가 발생하는 것을 확인할 수 있다.
왜????도대체 왜 컴파일 에러가 발생하는 것일까?
바로 파라미터다이나믹 디스패치의 조건을 걸라고 했기 때문이다.
다이나믹 디스패치란 어떤 메소드를 선택할지에 관한 내용이다. 이는 굳이 컴파일 시점에 미리 정해져있지 않아도 된다. 그러나 파라미터는 반드시 컴파일 시점에 어떤 자료형인지 정해져있어야 한다.
위의 소스는 p.postOn(s) 라는 코드에서 s의 자료형이 무엇인지 생각을 해보자.
바로 SNS 이다. 그러나 SNS에 대한 파라미터값을 받는 메소드는 단 한개도 존재하지 않는다. 그렇기 때문에 컴파일 에러가 발생하는 것이고, 이러한 이유 때문에 Post interface가 아닌 SNS interface에서 메소드를 구분시켜 주는 것이다.

참고로 토비님은 이러한 기법은 10년에 한번 사용할까 말까 했다고 한다.

추상 클래스

추상 클래스란?
- 여러 클래스들 중에 비슷한 필드와 메소드를 공통적으로 추출한 클래스를 의미한다.

선언방법

abstract class 클래스이름{}

규칙

  1. 반드시 하나 이상의 추상메소드를 포함하고 있어야 한다.
  2. 자체 인스턴스는 생성하지 못한다.
  3. 생성자와 맴버변수,일반 메소드를 가질 수 있다.

추상 메소드

추상메소드란?
- 구현부는 비워놓고 메소드의 선언부만 작성한 메소드를 뜻한다.
보통 주석을 통해 어떤 동작을 하는 메소드인지 설명한 후 각 동작은 하위 클래스들의 메소들에서 구현한다.

abstract 리턴타입 메소드이름{}

규칙

  1. 자식클래스는 반드시 추상메서드를 구현해야하며, 만약 구현하지 않을 경우 자식클래스도 추상클래스가 되어야 한다.
  2. 접근 지정자는 private이 될 수가 없다.

인터페이스

인터페이스란?
- 오직 추상메소드와 상수만을 맴버변수로 가질 수 있는 것
추상클래스와 인터페이스는 세트메뉴라고 생각하면된다. 추상클래스의 일종으로 추상클래스보다 추상화의 정도가 높다

interface 인터페이스 이름{}

interface B{}
interface C{}
class D{}

//인터페이스는 다중상속이 가능하다
interface E extends B,C{}
//class를 상속받고 B,C의 인터페이스를 구현
class A extends D implements B,C{}

final 키워드

final 키워드란?
- 엔터티를 단 한번만 할당하겠다는 뜻. 크게 3가지의 용도로 나뉜다.

1) final 변수

- 로컬 원시 변수에 final을 선언하면 단 한번만 초기화가 가능하고 변경할 수 없습니다.

public class Final{
    public static void main(String[] args) {
        final int x = 4;
    }
}

- 객체변수에 final을 선언하면 객체변수에는 새로운 다른 객체를 할당할 수 없습니다. 그러나 객체의 속성을 변경할 수 없다는 뜻은 아닙니다.

public class Final{
    int x;
    public static void main(String[] args) {
        final Final x_final = new Final();
        //x_final=new Final();        //Compile Error 발생
        x_final.x=4;
    }
}

2) final 메소드

- 메소드에 final키워드를 붙이면 오버라이딩을 할 수 없음을 뜻합니다.

class Animal{
    public final void Print(){
        System.out.println("I'm Animal");
    }
}
public class Dog extends Animal{
    @Override
    public void Print(){                //Compile Error 발생
        System.out.println("I'm Dog");
    }
    public static void main(String[] args) {
        Dog dog= new Dog();
    }
}

3) final 클래스

- 클래스에 final을 선언하면 상속 자체가 불가합니다.

final class Animal{

}
public class Dog extends Animal{            //Compile Error 발생
    public static void main(String[] args) {
        Dog dog= new Dog();
    }
}

Object 클래스

Object 클래스란?
- Object 클래스란 모든 클래스의 조상이다.출처

많은 API들이 있지만 중요하게 사용되는 API를 한번 알아보자

toString : 객체를 문자로 표현하는 메소드

class Animal{
    String str = "I'm Animal";
}
public class Dog extends Animal{
    public static void main(String[] args) {
        Dog dog= new Dog();
        String str = dog.toString();
        System.out.println(str);
    }
}

com.example.demo.Dog@79fc0f2f 출력이 된다.(@뒤에는 각자 다르다)
이 값은 인스턴스 dog의 주소값을 나타낸다. 한번 재정의 해보자

class Animal{
    String str = "I'm Animal";
    @Override
    public String toString(){
        return str;
    }
}
public class Dog extends Animal{
    public static void main(String[] args) {
        Dog dog= new Dog();
        String str = dog.toString();
        System.out.println(str);
    }
}

I'm Animal이 출력됨을 확인 할수 있다.

equals : 객체와 객체가 같은 것인지를 비교하는 API이다

두 객체의 논리적인 값을 비교한다.

  • 논리적인 값이란 객체가 가지고 있는 값을 의미함
  • 참조 타입의 경우 참조하는 객체의 주소값
  • 참조 타입의 실제 데이터를 비교하기 위해서는 equals()를 재정의 해야한다.

이게 무슨말이냐면 보통 equals를 설명할때 기본적으로 예제를 보여주는 것은 String형이다.
==equals를 비교하면서 설명을 하는데 객체에 관한 equals는 잘 보지를 못한 것 같다.

class Animal{
    String name;
    Animal(String name){
        this.name=name;
    }
    public boolean equals(Object obj) {
        Animal _obj = (Animal) obj;
        return name == _obj.name;
    }
}
public class Dog{
    public static void main(String[] args) {
        Animal dog= new Animal("Animal");
        Animal cat = new Animal("Animal");
        System.out.println(dog.equals(cat));
    }
}

위의 소스에서 euqals 메소드를 제거하면 false가 출력된다. 왜냐하면 dogcat은 참조타입이며 때문에 equals는 주소값을 비교하므로 당연히 다른 주소값을 가지게 되어 false가 출력이 된다.
그러나 우리가 의도한 바는 두 객체의 주소값 비교가 아닌 두 객체가 가지고 있는 name의 데이터값이 같은지 확인하고 싶은 것이다. 때문에 equals를 재정의 해주어 이를 구현할 수 있다.

clone : 어떤 객체가 있을 때 그 객체와 똑같은 객체를 복제해주는 기능이 clone 메소드의 역할이다

이 메소드를 사용하여 객체를 복제하려면 반드시 Cloneable 인터페이스를 구현해야한다.

Cloneable의 인터페이스이다.

public interface Cloneable {
}
  • clone사용시 Cloneable이 구현되어 있지 않다면 예외처리가 발생한다.
    • CloneNotSupportedException
    • checked 예외이기 때문에 try,catch 문으로 감쌓아 주어야 한다.

'백기선님과 함께 하는 자바 스터디' 카테고리의 다른 글

8주차 과제: 인터페이스  (0) 2021.06.22
7주차 과제: 패키지  (0) 2021.06.19
5주차 과제 : 추가사항  (0) 2021.06.06
5주차 과제: 클래스  (0) 2021.06.06
4주차 과제: 제어문  (0) 2021.05.30