슬기로운 개발생활

온라인 자바 스터디 #6 - 상속, super, 오버라이딩, 추상클래스, final, Object

by coco3o
반응형

목표

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

학습할 것 (필수)

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

자바 상속의 특징

상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.

 

객체지향에서 상속을 사용했을 때 가져다주는 장점은

1. 코드를 재활용함으로써 간소화된 클래스 구조

2. 클래스의 기능 테스트에 대한 생산성 및 정확성 증가

3. 클래스 수정/추가에 대한 유연성 및 확장성 증가

 

상속을 구현하는 방법은 아주 간단하다. 상속받고자 하는 클래스의 이름을 키워드 'extends'와 함께 써 주기만 하면 됨

 

단일 상속
class Parent {}
class Child extends Parent {}
  • 한 클래스가 하나의 클래스만을 상속받는다.
다단계 상속
class A {}

class B extends A {}

class C extends B {}
  • 클래스가 기본 클래스가 아닌 기본 클래스의 파생 클래스를 상속받는다.
계층적 상속
class A {}

class B extends A {}

class C extends A {}

class D extends A {}
  • 여러 하위 클래스가 한 클래스를 상속받는다.

 

간단한 예제

class Parent {
 	int age = 50;
    
    public void parentC() {
    System.out.println("Parent Class");
    }
 }
 
 class Child extends Parent {
 	public void childC() {
    System.out.println("age = " + age);
    }
 }
 
 public class Main {
	public static void main(String[] args) {
    Child c = new Child();
    c.parentC();
    c.childC();
    }
}    
 
 코드 실행 결과
 Parent Class
 age = 50
  • Child 클래스에는 변수 age가 없지만 Parent 클래스로부터 상속 받았기 때문에 50이라는 결과가 발생한다. (변수 및 메소드가 보이진 않지만 변수가 존재하는 것이다.)
  • Child 클래스에는 parentC() 메소드가 없지만, Parent 클래스로부터 상속 받았기 때문에 출력이 가능하다.
  • Child에 멤버변수 및 메소드를 추가할 경우 Parent 클래스에서는 사용할 수 없다.

특징

  • 다중 상속이 불가능 하다.
    • child는 Parent 클래스로부터 상속 받았기 때문에, 다른 클래스로부터는 상속 받을 수 없다.
class Mother {}
class Father {}
class Child exnteds Mother, Father {} // x
  • 자식(Child) 클래스는 부모(Parent) 클래스에 존재하는 멤버변수 및 메소드를 모두 상속 받는다.
  • 부모 클래스의 변화는 자식 클래스에 영향을 바로 미친다.
  • 반대로 자식 클래스의 변화는 부모 클래스에게 영향을 주지 못한다.

super() 키워드

super & super()

 

1. super

 

super는 자식 클래스가 부모 클래스로부터 상속받은 멤버를 참조할 때 사용하는 참조 변수다.

멤버변수와 지역변수의 이름이 같을 때 this를 사용하듯이 부모 클래스와 자식 클래스 멤버의 이름이 같을 경우 super를 사용한다.

this와 super는 인스턴스의 주소값을 저장하는데 static 메소드(클래스메소드)와 관련이 없다.

class Parent { int x = 10; }

class Child extends Parent { 
	int x = 20;
    
    void childMethod() { 
    System.out.println("x = " + x);
    System.out.println("this.x = " + this.x);
    System.out.println("super.x = " + super.x);
    }
}

class Main {
	public static void main(String[] args) {
    Child c = new Child();
    c.childMethod();
    }
}

실행 결과
x = 20
this.x = 20
super.x = 10

 

2. super()

 

this()와 마찬가지로 super() 역시 생성자이다.

this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만,

super()는 부모 클래스의 생성자를 호출하는데 사용된다.

 

Object 클래스를 제외한 모든 클래스의 생성자의 첫줄에는 반드시 this() 또는 super()를 호출해줘야 한다.

이렇게 하지 않으면 컴파일러가 자동으로 super()를 생성자의 첫줄에 호출한다.

class Point {
    int x = 10;
    int y = 20;
 
    Point(int x, int y) {
        // 생성자의 첫줄에 다른 생성자를 호출하지 않았기 때문에,
        // 컴파일러가 이 부분에 super()를 호출한다.
        // 부모 클래스이므로 Object 클래스의 super()가 호출한다.
        this.x = x;
        this.y = y;
    }
}
 
class Point3D extends Point {
    int z = 30;
 
    Point3D() {  
        this(100, 200, 300);    // 자신의 클래스의 또다른 생성자 호출 ( Point3D(int x, int y, int z) )
    }
 
    Point3D(int x, int y, int z) {  
        super(x, y);    // 부모 클래스 생성자 호출( Point(int x, int y) )
        this.z = z;
    }  
}

class Main {
    public static void main(String[] args) {
        Point3D point3d = new Point3D();    // Point3D() 생성자로 초기화 및 인스턴스 생성
        System.out.println("point3d.x= " + point3d.x);
        System.out.println("point3d.y= " + point3d.y);
        System.out.println("point3d.z= " + point3d.z);
    }
}

실행 결과
point3d.x = 100
point3d.y = 200
point3d.z = 300

Reference : Java의 정석 Chapter 7 객체지향 프로그래밍 II


메소드 오버라이딩

오버로딩? 오버라이딩?
  • 오버로딩(overloading) 기존에 없는 새로운 메소드를 정의하는 것(new)
  • 오버라이딩(overriding) 부모 클래스로부터 상속받은 메소드의 내용을 변경하는 것(change, modify)
오버라이딩의 조건

자식 클래스에서 오버라이딩하는 메소드는 부모클래스의 메소드와

  • 이름이 같아야 한다.
  • 매개변수가 같아야 한다.
  • 리턴타입이 같아야 한다.
class Parent { 
	protected void job() {
    	System.out.println("developer");
    }
}

class Child extends Parent {
	
    @Override
    public void job() { 
        System.out.println("Student");
    }
}

class Main { 
	public static void main(String[] args) {
    Parent p = new Parent();
    Child c = new Child();
    
    p.job();
    c.job();
    }
}

실행 결과
developer
Student

요약하자면 선언부가 서로 일치해야한다는 것이다.

다만, 접근 제어자와 예외는 제한된 조건 하에서만 다르게 변경할 수 있다.

  • 접근 제어자는 부모 클래스의 메소드보다 좁은 범위로 변경 할 수 없다.
    • 만일 부모 클래스의 메소드의 접근 제어자가 protected라면, 이를 오버라이딩 하는 자식 클래스의 메소드는 접근제어자가 protected나 public이어야 한다.
접근 제어자의 접근범위 ( 큰 -> 작은)
public > protected > (default) > private
  • 오버라이딩 하는 자식 클래스의 메소드는 부모 클래스의 메소드보다 많은 수의 예외를 선언할 수 없다.
//예외 선언의 올바른 예
class Parent { 
	void parentMethod() throws IOException, SQLException { }
}

class Child extends Parent {
	void parentMethod() throws IOException { }
}
  • 인스턴스메소드를 static메소드로 또는 그 반대로 변경할 수 없다.

오버로딩과 오버라이딩은 명백히 서로 다르다. 절대 햇갈리지 말자.


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

Method Dispatch는 어떤 메소드를 호출할지 결정하여 실제로 실행시키는 과정을 말한다.

대표적으로 Static(정적) Dispatch와 Dynamic(동적) Dispatch가 있다.

동적 디스패치는 메소드 오버라이딩이 되어있는 경우 실행시점에 어떤 메소드를 실행할 지 결정하는 것이다.

정적 디스패치는 그와 다르게 컴파일 시점에 어떤 메소드를 실행할지 결정한다.

 

Static Dispatch

정적 디스패치는 컴파일 시점에 어떤 메소드를 실행할지 결정한다.

public class Dispatch { 
	static class Service { 
    	void run(int number) { 
        System.out.println("run(" + number + ")");
        }
        
        void run(String msg) { 
        System.out.println("run(" + msg + ")");
        }
    }
    
    public static void main(String[] args) { 
    	new Service().run(1);
        new Service().run("static Dispatch");
    }
}

실행 결과
run(1)
run(static Dispatch)

 

Dynamic Dispatch

동적 디스패치는 메소드 오버라이딩이 되어있을 때 런타임시점에 어떤 메소드를 실행할지 결정한다.

public class Dispatch { 
	static abstract class Service {
    	abstract void run();
    }
    
    static class MyService1 extends Service { 
    @Override
    void run() { 
	    System.out.println("run1");
   	  }
    }
    
    static class MyService2 extends Service { 
    @Override
    void run() { 
        System.out.println("run2");
   	  }
    }
    
    public static void main(String[] args) { 
    	Service svc = new MyService1();
        
        svc.run(); //svc에 할당되어 있는 Object가 뭔지 보고 결정
	}
}

실행결과
run1

 

 

Double Dispatch

더블 디스패치는 다이나믹 디스패치를 두 번 하는 것이다.

 

아래에 두 개의 코드가 있다.

import java.util.Arrays;
import java.util.List;

public class Dispatch {
    interface Post { void postOn(SNS sns); }

    static class Text implements Post {
        @Override
        public void postOn(SNS sns) {
            if(sns instanceof Facebook) {
                System.out.println("text - facebook");
            }
            if(sns instanceof Instagram) {
                System.out.println("text - Instagram");
            }
        }
    }

    static class Picture implements Post {
        @Override
        public void postOn(SNS sns) {

            if(sns instanceof Facebook) {
                System.out.println("picture - facebook");
            }
            if(sns instanceof Instagram) {
                System.out.println("picture - Instagram");
            }
        }
    }

    interface SNS { }
    static class Facebook implements  SNS { };
    static class Instagram implements SNS { };

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

        posts.forEach(p->sns.forEach(s->p.postOn(s)));
    }
}

 

import java.util.Arrays;
import java.util.List;

public class Dispatch {
    interface Post { void postOn(SNS sns); }

    static class Text implements Post {
        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    static class Picture implements Post {
        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    interface SNS {
        void post(Text post);
        void post(Picture post);
    }
    static class Facebook implements  SNS {
        @Override
        public void post(Text post) { System.out.println("text - Facebook"); }
        @Override
        public void post(Picture post) { System.out.println("picture - Facebook"); }
    };
    static class Instagram implements SNS {
        @Override
        public void post(Text post) { System.out.println("text - Instagram"); }
        @Override
        public void post(Picture post) { System.out.println("picture - Instagram"); }
    };

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

        posts.forEach(p->sns.forEach(s->p.postOn(s)));
    }
}

예제코드 실행결과(결과는 동일)

여기서 새로운 SNS가 생겼다고 가정해보자.

import java.util.Arrays;
import java.util.List;

public class Dispatch {
    interface Post { void postOn(SNS sns); }

    static class Text implements Post { 
        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    static class Picture implements Post { 
        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    interface SNS {
        void post(Text post);
        void post(Picture post);
    }
    static class Facebook implements  SNS {
        @Override
        public void post(Text post) { System.out.println("text - Facebook"); }
        @Override
        public void post(Picture post) { System.out.println("picture - Facebook"); }
    };
    static class Instagram implements SNS {
        @Override
        public void post(Text post) { System.out.println("text - Instagram"); }
        @Override
        public void post(Picture post) { System.out.println("picture - Instagram"); }
    };
    static class KakaoStory implements SNS {
        @Override
        public void post(Text post) { System.out.println("text - KakaoStory"); }
        @Override
        public void post(Picture post) { System.out.println("picture - KakaoStory"); }
    };

    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(), new Instagram(), new KakaoStory());

        posts.forEach(p->sns.forEach(s->p.postOn(s)));
    }
}

KakaoStory 클래스를 생성하고, Text, Picture 클래스는 건들이지 않았다.

예제코드 실행결과

위처럼 새로 추가하는 것이 자유롭고, 추가하는 시점에서 기존에 의존하고 있는 코드에 직접적인 영향을 주지 않는다.

반면, 위의 if문의 instanceof를 사용할 때 새로운 SNS가 생겼다고 하면, if문쪽에 코드를 수정하지 않으면 에러가 발생하거나, 코드가 정상적으로 동작하지 않는다. 이런 부분에서 굉장히 큰 장점이 있다.

 

Reference : www.youtube.com/watch?v=s-tXAHub6vg


추상 클래스

추상클래스는 일종의 미완성의 클래스이다.

보통 클래스는 new 로 인스턴스를 생성할 수 있지만, 추상클래스는 그것이 불가능하다.

오직 상속을 통해서 자손 클래스에 의해서만 완성되는 클래스이다.

 

그럼 이런 미완성 클래스가 무슨 용도가 있을까? 

단독으로는 아무 역할을 못한다. 그러나 새로운 클래스를 작성하는데 밑바탕이 되는 역할을 할 수 있다.

새로운 클래스를 작성할 때 아무것도 없는 상태에서 시작하기 보다는

미완성이지만 어느 정도 틀을 갖춘 상황에서 시작하는 것이 나을 것이다.

 

자동차로 예를 들면 용도나 크기에 따라 종류가 나뉜다. 하지만 기본적인 부분은 모두 동일하다.

핸들, 바퀴, 엔진 등과 같은 것들이다. 여기서 공통된 부분들로 틀을 만든 것이 추상클래스이다. 

공통된 부분을 추상클래스로 만들어두고 그 외 필요한 부분은 각 클래스에서 구현하면 효율적으로 클래스를 구현할 수 있다.

 

추상 메소드

추상 메소드는 특이한 형태의 메소드를 갖는다.

메소드의 몸통(구현부)가 없는 미완성의 메소드이다.

이와 같이 되어있는 이유는 메소드의 내용은 상속받는 클래스에 따라 달라질 수 있기 때문에

추상클래스에서는 선언부만 작성해 두는 것이다. 

그리고 몸통(구현부)은 상속받은 클래스에서 목적에 맞게 구현하도록 비워 두는 것이다.

 

추상클래스의 문법
abstract class 클래스명 {
	
    abstract 리턴타입 메소드이름();
    // . . .
}

간단한 예)

public abstract class Animal {

    abstract void eat();
    abstract void sleep();
}
class Tiger extends Animal {
    @Override
    void eat() { System.out.println("사슴을 먹습니다."); }
    @Override
    void sleep() { System.out.println("잠을 잡니다."); }
    
    void jump() { System.out.println("높이 점프 합니다."); }
}

class Eagle extends Animal {
    @Override
    void eat() { System.out.println("호랑이가 먹다 남은 사슴을 먹습니다."); }
    @Override
    void sleep() { System.out.println("잠을 잡니다."); }
    
    void flying() { System.out.println("하늘을 납니다."); }

    public static void main(String[] args) {
        Tiger t = new Tiger();
        t.eat();
        t.sleep();
        t.jump();
        System.out.println("------------------------");
        Eagle e = new Eagle();
        e.eat();
        e.sleep();
        e.flying();


    }
}

추상클래스인 Animal을 상속받는 Tiger와 Eagle이 있다.

Animal의 추상메소드인 eat()과 sleep()는 상속받는 클래스에서 반드시 오버라이딩하여 구현해야 한다.


final 키워드

클래스나 변수를 정의할 때 final 키워드를 사용할 수 있다.

final을 붙이게 되면 그 값은 변하지 않는다.

 

final 키워드는 총 3가지에 적용할 수 있다.

  • final 변수
    • 원시 타입
    • 객체 타입
    • 클래스 필드
    • 메소드 인자
  • final 메소드
  • final 클래스

 

final 변수

  • 원시 타입

로컬 원시 변수에 final로 선언하면 초기화된 변수는 변경할 수 없는 상수값이 된다.

public class Main { 
    final int MAX = 1;
    MAX = 3; // error
}

 

  • 객체 타입

객체 변수에 final로 선언하면 그 변수에 다른 참조 값을 지정할 수 없다.

원시 타입과 동일하게 변경할 수 없다. 

단, 객체의 속성은 변경 가능

public class Main { 
	final Pet pet = new Pet();
    pet = new Pet(); //다른 객체로 변경할 수 없음
    pet.setWeight(3); //객체 필드는 변경 가능
}

 

  • 메소드 인자

메소드 인자에 final를 붙이면, 메소드 안에서 변수 값을 변경할 수 없다.

public class Pet { 
int weight;
public void setWeight(final int weight) { 
weight = 1; // final 인자는 메소드 안에서 변경할 수 없음
}
}

 

  • 멤버 변수

클래스의 멤버 변수에 final로 선언하면 상수값이 되거나 한번만 쓰이게 된다.

final로 선언시 초기화되는 시점은 생성자의 메소드가 끝나기 전에 초기화가 된다.

하지만, static이냐 아니냐에 따라서 초기화 시점이 달라진다.

  • static final 멤버 변수 (static final int MAX = 1)
    • 값과 함께 선언시
    • 정적 초기화 블럭에서 (static initialization block)
  • instance final 멤버 변수 (final int MAX = 1)
    • 값과 함께 선언시
    • 인스턴스 초기화 블럭에서 (instance initialization block)
    • 생성자 메소드에서
인스턴스 초기화 블럭 정적 초기화 블럭
객체 생성할때마다 블럭이 실행됨
부모 생성자 이후에 실행됨
생성자보다 먼저 실행됨
클래스 로드시 한 번만 블럭이 실행됨

final 메소드

다음과 같이 클래스의 메소드에도 final을 붙일 수 있다.

class AAA { 
	final String hello = "hello world";
	final String getHello() { 
	return hello;
	}
}

final 메소드는 오버라이딩이 안된다.

class BBB extends AAA { 
    @Override
    String getHello() { // error
    return "nice to meet you";
    }
}

위와 같이 AAA 클래스를 상속받는 BBB 클래스에선 getHello()메소드를 재정의할 수 없다.


final 클래스

클래스를 정의할 때 다음과 같이 final 키워드를 사용할 수 있다.

final class AAA {
	final String hello;
    AAA() {
     hello = "hello world";
     }
}     

클래스에 final을 선언하면 다른 클래스가 상속할 수 없는 클래스가 된다.

final class AAA {
	final String hello;
    AAA() {
     hello = "hello world";
     }
}     

class BBB extends AAA { } // error

references : blog.advenoh.pe.kr/java/%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-final%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4/

codechacha.com/ko/java-final-keyword/


Object 클래스

-java.lang.Object
-모든 클래스의 최상위 클래스

Object 클래스는 자바에서 제공하는 중요한 클래스 중 하나이다.

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

따라서 모든 클래스는 Object 클래스의 메소드를 사용할 수 있고 일부 메소드를 override해서 사용할 수도 있다.

단, 당연히 final 메소드는 override할 수 없다.

 

Object 클래스가 들어있는 java.lang 패키지는 컴파일러에 의해 자동으로 import된다.

그래서 java.lang 패키지에 있는 클래스는 따로 import 하지 않아도 사용할 수 있다.

주로 clone(), equals(), hashCode(), toString()을 사용한다.

1. toString()

-기본 동작 : 객체의 해시코드 출력
-목적 : 객체의 정보를 문자열 형태로 표현하고자 할 때
public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
		
        Student std = new Student("홍길동", 30);
        System.out.println(std);
    }
}

실행결과

위와같이 toString()을 하지 않고 인스턴스를 출력하면 '클래스이름@해시코드'가 출력된다.

이번엔 toString()을 사용해서 해보았다.

실행결과

 


2. equals()

-기본 동작 : '==' 연산 결과 반환
-목적 : 물리적으로 다른 메모리에 위치하는 객체여도 논리적으로 동일함을 구현하기 위해

equals()를 사용해 두 객체의 동일함을 논리적으로 비교 할 수 있다.

public class User {
    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static void main(String[] args) {
        User user1 = new User(10, "홍길동");
        User user2 = new User(10, "홍길동");
        System.out.println(user1.equals(user2));
    }
}

실행 결과

false

Process finished with exit code 0

equals()는 '==' 연산이기 때문에 서로 다른 인스턴스를 가리키는 참조변수를

equals()로 비교하면 false를 결과로 리턴된다.

이런 경우 equals()를 override하여 논리적인 동일성을 갖도록 할 수 있따.

 

public class User {

    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {

        if (obj instanceof User) {
            return this.getId() == ((User)obj).getId();
        } else {
            return false;
        }
    }

    public static void main(String[] args) {

        User user1 = new User(10, "홍길동");
        User user2 = new User(10, "홍길동");

        System.out.println(user1.equals(user2));
    }
}

실행 결과

true

Process finished with exit code 0

equals()의 반환값이 true로 두 객체가 같다는 의미는 같은 해시코드값을 갖는다는 것이다.


3. hashCode()

-기본 동작 : JVM이 부여한 코드값. 인스턴스가 저장된 가상머신의 주소를 10진수로 반환
-목적 : 두 개의 서로 다른 메모리에 위치한 객체가 동일성을 갖기 위해

일반적으로 equals()와 hashCode()는 함께 사용한다.

public class User {

    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {

        if (obj instanceof User) {
            return this.getId() == ((User)obj).getId();
        } else {
            return false;
        }
    }

    public static void main(String[] args) {

        User user1 = new User(10, "홍길동");
        User user2 = new User(10, "홍길동");

        System.out.println("user1.equals(user2): " + user1.equals(user2));
        System.out.println("user1.hashCode(): " + user1.hashCode());
        System.out.println("user2.hashCode(): " + user2.hashCode());
    }
}

실행 결과

user1.equals(user2): true
user1.hashCode(): 901506536
user2.hashCode(): 747464370

Process finished with exit code 0

지금은 user1과 user2가 서로 다른 해시코드를 반환한다.

public class User {

    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {

        if (obj instanceof User) {
            return this.getId() == ((User)obj).getId();
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return getId();
    }

    public static void main(String[] args) {

        User user1 = new User(10, "홍길동");
        User user2 = new User(10, "홍길동");

        System.out.println("user1.equals(user2): " + user1.equals(user2));
        System.out.println("user1.hashCode(): " + user1.hashCode());
        System.out.println("user2.hashCode(): " + user2.hashCode());
        System.out.println("System.identityHashCode(user1): " + System.identityHashCode(user1));
        System.out.println("System.identityHashCode(user2): " + System.identityHashCode(user2));
    }
}

실행 결과

user1.equals(user2): true
user1.hashCode(): 10
user2.hashCode(): 10
System.identityHashCode(user1): 901506536
System.identityHashCode(user2): 747464370

Process finished with exit code 0

4. clone()

- 객체의 복사본을 만듦
- Prototype으로부터 같은 속성값을 가진 복사본을 생성
- 복제할 객체는 Cloneable 인터페이스를 명시해야함 (정보은닉에 위배될 가능성이 있기 때문)
- 복제된 객체는 Object로 반환되므로 명시적인 캐스팅 필요
- 메모리가 복제되면서 CloneNotSupportedException이 발생할 수 있으므로 예외처리 필요

clone()메소드는 private 필드도 복제할 수 있기 때문에 정보은닉에 위배될 수 있다.

따라서 Cloneable 인터페이스가 명시되어 있는 클래스만 clone()을 사용한다.

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
	
    public String toString() {
    return "name : " + name + " age : " + age;
    }
    
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException {
		
        Student std = new Student("홍길동", 30);
        System.out.println(std);
        
        Student std2 = (Student) std.clone();
        System.out.println(std2);
    }
}

실행 결과

name : 홍길동 age : 30
Exception in thread "main" java.lang.CloneNotSupportedException: Student
	at java.base/java.lang.Object.clone(Native Method)
	at Student.clone(Student.java:16)
	at Student.main(Student.java:23)

Process finished with exit code 1

CloneNotSupportedException이 발생한다.

이유는 Student 클래스에 Cloneable 인터페이스가 명시되어있지 않기 때문.

아래와 같이 수정을 한다.

public class Student implements Cloneable{

실행 결과

name : 홍길동 age : 30
name : 홍길동 age : 30

Process finished with exit code 0

Cloneable 인터페이스는 빈 인터페이스이다.

따라서 Cloneable 인터페이스를 implements해도 구현해야할 메소드는 없다.

 

reference :

atoz-develop.tistory.com/entry/%EC%9E%90%EB%B0%94-Object-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A0%95%EB%A6%AC-toString-equals-hashCode-clone

반응형

블로그의 정보

슬기로운 개발생활

coco3o

활동하기