[6주차] 상속

janjanee·2022년 8월 1일
0
post-thumbnail

2021.01.12 작성글 이전

6. 상속

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

6-1. 자바 상속의 특징

상속이란?

상속이란? 기존의 클래스를 재사용하여 새로운 클래스를 작성

상속을 통해 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.

class Child extends Parent {
  ...
}

상속을 구현하는 방법은 새로 작성하는 클래스의 이름 뒤에 상속받으려는 클래스 이름을 'extends'와 함께
사용하면 된다.

Child와 Parent 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상 클래스'
상속 받는 클래스를 '자손 클래스' 라 한다.

조상 클래스 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스 자손 클래스 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스

class Parent {
  int age;
}

class Child extends Parent { 
  ...
}

위의 Parent 클래스에 age라는 변수를 멤버변수로 추가하면, 자손 클래스는 조상의 멤버를 모두 상속받기 때문에
Child 클래스는 자동으로 age라는 멤버변수를 갖게된다.

class Parent {
  int age;
}

class Child extends Parent {
  void play() {
    System.out.println("놀자!");
  }
}

반대로 자손 클래스인 Child에 play() 메소드를 추가하자.

이때, Child 클래스에 새로운 코드가 추가되어도 조상인 Parent 클래스는 아무런 영향도 받지 않는다.
조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받지만, 자손 클래스가 변경되는 것은 조상 클래스에
아무런 영향을 주지 못한다.

  • 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
  • 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
class Parent {
  int age;
}

class Child extends Parent { }
class GrandChild extends Child { }

GrandChild라는 클래스는 Child 클래스를 상속받는다. 여기서 Child는 GrandChild의 직접 조상이며
Parent는 GrandChild의 간접 조상이다.

age 변수는 모든 자손 클래스에 추가되므로, GrandChild 클래스에도 age 변수가 추가된다.

클래스간의 관계 - 포함관계

상속이외에도 클래스를 재사용하는 또 다른 방법이 있는데, 클래스 간에 '포함(Composite') 관계를 맺어주는 것이다.

class Circle {
  int x;
  int y;
  int r;
}

class Point {
  int x;
  int y;
}

원을 표현하는 Circle 클래스와 점을 다루는 Point 클래스가 있다고 하자.

Circle 클래스를 Point클래스를 포함하여 사용하면 다음과 같이 작성할 수 있다.

class Circle {
  Point p = new Point();
  int r;
}

클래스간의 관계 결정하기

상속관계 '~은 ~이다.(is-a)' 포함관계 '~은 ~을 가지고 있다.(has-a)'

원(Circle)은 점(Point)이다. 원(Circle)은 점(Point)을 가지고 있다.

원의 경우 점을 가지고 있으므로 상속 보다 포함이 더 맞을 수 있겠다.

하지만 모든 경우가 이렇게 딱 떨어지는 것은 아니지만, 적어도 가장 기본적인 원칙에 대한 감은 잡을 수 있다.

단일 상속

자바에서는 오직 단일 상속만을 허용한다. 둘 이상의 클래스로부터 상속을 받을 수 없다.

6-2. super 키워드

super

super? 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용하는 참조변수

조상의 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같다.

static 메소드(클래스메소드)는 인스턴스와 관련이 없다. 따라서 this와 마찬가지로 super 역시
static메소드에서는 사용할 수 없고 인스턴스 메소드에서만 사용할 수 있다.

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

class Parent {
    int x = 10;
}

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

결과는 x, this.x, super.x 모두 같은 변수를 의미하므로 모두 10이 출력된다.

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

class Parent {
    int x = 10;
}

class Child extends Parent {
    int x = 20;

    void method() {
        System.out.println("x=" + x);
        System.out.println("this.x=" + this.x);
        System.out.println("super.x=" + super.x);
    }
}

이 예제의 결과는 어떨까? 멤버변수 x가 조상, 자손 클래스 모두에 존재한다.
따라서 super.x와 this.x는 서로 다른 값을 참조하게 된다.
x와 this.x는 20이고, super.x는 10의 결과가 나온다.

class Point {
  int x;
  int y;

  String getLocation() {
    return "x :" + x + ", y :" + y;
  }
}

class Point3D extends Point {
  int z;

  String getLocation() {    // 오버라이딩
    // return "x :" + x + ", y :" + y + ", z:" + z;
    return super.getLocation() + ",z :" + z;  // 조상의 메소드 호출
  }
}

변수만이 아니라 메소드 역시 super를 써서 호출 할 수 있다.
특히 조상 클래스의 메소드를 자손 클래스에서 오버라이딩한 경우 super를 사용한다.

super() - 조상 클래스의 생성자

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

super() ? 조상 클래스의 생성자를 호출하는데 사용

생성자의 첫 줄에서 조상클래스의 생성자를 호출해야하는 이유는 자손 클래스의 멤버가 조상 클래스의
멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다.

모든 클래스의 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다.
그렇지 않으면, 컴파일러가 생성자 첫 줄에 'super();' 를 자동적으로 추가한다.

public class PointTest {
    public static void main(String[] args) {
        Point3D p3 = new Point3D(1,2,3);
        System.out.println(p3.getLocation());
    }
}

class Point {
    int x, y;

    Point(int x, int y) {
        this.x  = x;
        this.y = y;
    }

    String getLocation () {
        return "x :" + x + ", y:" + y;
    }
}

class Point3D extends Point {
    int z;

    Point3D(int x, int y, int z) {
        super(x, y);
        this.z = z;
    }

    String getLocation () {
        return super.getLocation() + ", z :" + z;
    }
}

Point3D 클래스에서 super() 키워드를 사용하여 조상 클래스의 멤버변수를 조상의 생성자에 의해 초기화되도록 설정한다.

6-3. 메소드 오버라이딩

오버라이딩? 조상 클래스로부터 상속받은 메소드의 내용을 변경하는 것

상속받은 메소드를 그대로 사용하기도 하지만, 자손 클래스에 맞게 변경해야하는 경우 오버라이딩 한다.

class Point {
  int x;
  int y;

  String getLocation() {
    return "x :" + x + ", y :" + y;
  }
}

class Point3D extends Point {
  int z;

  String getLocation() {    // 오버라이딩
    return "x :" + x + ", y :" + y + ", z:" + z;
  }
}

위의 Point 클래스는 좌표를 문자열로 반환하는 getLocation 메소드를 갖고있다.
Point를 상속받은 Point3D 클래스에도 동일하게 좌표를 문자열로 반환하는 getLocation() 메소드를 호출하고 싶다.

이때, Point3D 클래스는 z 좌표까지 갖고있으므로 z 좌표도 나타내는 문자열을 포함하여 반환받고 싶을 것이다.
Point 클래스의 getLocation을 오버라이딩 하여 z의 좌표까지 문자열로 반환하는 메소드 오버라이딩을 할 수 있다.

오버라이딩의 조건

자손 클래스에서 오버라이딩하는 메소드는 조상 클래스의 메소드와

  • 이름이 같아야 한다.
  • 매개변수가 같아야 한다.
  • 반환타입이 같아야 한다.

선언부가 서로 일치해야 한다. 다만 접근 제어자와 예외는 제한된 조건 하에서만 다르게 변경할 수 있다.

조상 클래스의 메소드를 자손 클래스에서 오버라이딩할 때
1. 접근 제어자를 조상 클래스의 메소드보다 좁은 범위로 변경할 수 없다.
2. 예외는 조상 클래스의 메소드보다 많이 선언할 수 없다.(단순 개수가 아님)
3. 인스턴스 메소드를 static메소드로 또는 그 반대로 변경할 수 없다.

만약, 조상 클래스에 정의된 static 메소드를 자손 클래스에서 똑같은 이름의 static 메소드로 정의할 수 있을까?
-> 가능하다. 그러나 이것이 오버라이딩은 아니다. static 멤버들은 자신들이 정의된 클래스에 묶여있다.

오버로딩 vs 오버라이딩

두 개념이 처음에는 헷갈릴 수 있는데 다시 짚고 넘어가자.

오버로딩(overloading) 기존에 없는 새로운 메소드를 정의하는 것(new)
오버라이딩(overriding) 상속받은 메소드의 내용을 변경하는 것(change, modify)

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

메소드 디스패치? 어떤 메소드를 호출할 지 결정하여 실행시키는 과정을 의미한다.

정적 메소드 디스패치 (Static Method Dispatch)

public class StaticDispatch {

    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(10);      // run(10)
        new Service().run("hello"); // run(hello)
    }

}

run(10)과 run("hello") 를 호출 할 때 우리는 Service 클래스의 각각의 파라미터에 맞는 메소드가
호출 될 것이라고 예상한다.
컴파일러 역시 이 메소드를 호출할 것을 알고있으며, 컴파일 타임에 어떤 메소드를 호출할 지 정확히
알고 있는 상태를 정적 메소드 디스패치(Static Method Dispatch) 라 한다.

메소드 오버로딩은 정적 메소드 디스패치로 동작한다.

동적 메소드 디스패치 (Dynamic Method Dispatch)

public class DynamicDispatch {

    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) {
        List<Service> svc = Arrays.asList(new MyService1(), new MyService2());
        svc.forEach(Service::run);
    }

}

추상클래스 Service가 있고 이것을 상속받은 MyService1, MyService2 클래스가 있다.
각 클래스들은 추상 메소드인 run()을 재정의 하고있다.

main 메소드를 살펴보자.

List<Servce> svc = Arrays.asList(new MyService1(), new MyService2());
svc.forEach(Service::run);

Service 타입을 받는 리스트에 MyService1 클래스와 MyService2 클래스를 넣었다. 그리고 forEach를 돌면서 run 메소드를 호출하고 있다.

이 때, 컴파일러는 run()이 호출 될때 Service의 run()과 MyService1의 run()과 MyService2의 run() 중
어느 메소드를 호출할 지 아직 모른다.

이는 런타임 시점에 객체의 receiver parameter (this)를 통해 각 객체의 this.run()을 호출하게 된다.
즉, 컴파일 시점에 컴파일러가 어떤 메소드를 호출해야할 지 모르며 런타임 시점에 알 수 있는 것
동적 메소드 디스패치(Dynamic Method Dispatch) 라고 한다.

6-5. 다형성

다형성이란?

다형성? 여러 가지 형태를 가질 수 있는 능력

자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현하였다.

구체적으로 표현하면 아래와 같다.

조상클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하는 것

Tv 클래스와 Tv를 상속받는 CaptionTv가 있다고 가정하자.

Tv t = new Tv();
CaptionTv c = new CaptionTv();

보통 여태까지 이런식으로 써왔다. 인스턴스 타입과 일치하는 타입의 참조변수만을 사용했다.

Tv와 CaptionTv가 서로 상속관계이므로, 다음과 같이 사용할 수 있다.

Tv t = new CaptionTv();
CaptionTv c = new CaptionTv();

이 경우, t는 실제 인스턴스 타입이 CaptionTv라 할지라도, t로 CaptionTv의 모든 멤버를 사용할 수 없다.
t는 Tv 멤버들만 사용할 수 있다.

t, c 둘 다 같은 타입인 CaptionTv의 인스턴스 이지만 참조변수의 타입에 따라 사용할 수 있는
멤버의 개수가 달라진다.

반대로 아래와 같이 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다.

CaptionTv c = new Tv(); // Error

참조변수의 형변환

자손타입 -> 조상타입(Up-casting) : 형변환 생략가능 조상타입 -> 자손타입(Down-casting) : 형변환 생략불가

Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2  null;

car = fe;   // 업캐스팅(생략가능)
fe2 = (FireEngine)car;  // 다운캐스팅(생략불가)

형변환은 참조변수의 타입을 변환하는 것이지, 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은
인스턴스에 아무런 영향을 미치지 않는다.

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는
인스턴스의 자손타입으로 형변환은 허용되지 않는다.

즉, 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는것이 중요!

참조변수와 인스턴스의 연결

public class BindingTest {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        System.out.println("p.x="+p.x);
        p.method();

        System.out.println("c.x="+c.x);
        c.method();
    }
}

class Parent {
    int x = 100;

    void method() {
        System.out.println("Parent Method");
    }
}

class Child extends Parent {
    int x = 200;

    void method() {
        System.out.println("Child Method");
    }
}

// result
p.x = 100
Child Method
c.x = 200
Child Method

조상 클래스와 자손 클래스에 중복으로 정의된 멤버변수인 경우 참조변수의 타입에 맞는 멤버변수가 사용된다.
메소드의 경우는 항상 실제 인스턴스의 메소드가 호출된다.

매개변수의 다형성

public void print(Object obj) {
  write(String.valueOf(obj));
}

public static String valueOf(Object obj) {
  return (obj == null) ? "null" : obj.toString();
}

print(Object o)는 매개변수로 Object타입의 변수가 선언되어있다.
Object 클래스는 모든 클래스의 조상이므로 이 메소드의 매개변수로 어떤 타입의 인스턴스도 가능하다.

6-6. 추상 클래스

abstract는 '미완성'의 의미를 가지고 있다.
선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메소드를 선언하는데 사용된다.

abstract가 사용될 수 있는 곳 - 클래스, 메소드

대상의미
클래스클래스 내에 추상 메소드가 선언되어 있음을 의미
메소드선언부만 작성하고 구현부는 작성하지 않은 추상 메소드임을 알림

추상 클래스는 아직 미완성된 메소드가 존재하는 '미완성 설계도' 이다.
미완성 설계도로는 제품을 만들어낼 수 없다. 마찬가지 추상 클래스로는 인스턴스를 생성할 수 없다.

추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.

abstract class 클래스명 {
  ...
}

추상클래스는 키워드 'abstract'를 붙이기만 하면 된다.

추상 메소드(abstract method)

추상 메소드? 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨둔 것

왜 구현부는 작성하지 않고 남겨둔 것일까?
메소드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문이다.
추상 클래스를 상속받는 자손 클래스는 조상의 추상메소드를 상황에 맞게 적절히 구현해야 한다.

/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.*/
abstract 리턴타입 메서드이름();

추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상 메소드를 모두 구현해주어야한다. 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 한다.

추상클래스 작성

추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업 구체화 : 상속을 통해 클래스를 구현, 확장하는 작업

public abstract class Player {
    boolean pause;
    int currentPos;

    Player() {
        pause = false;
        currentPos = 0;
    }

    /** 지정된 위치에서 재생을 시작하는 기능 수행*/
    abstract void play(int pos);

    /** 재생을 즉시 멈추는 기능을 수행*/
    abstract void stop();

    void play() {
        play(currentPos);
    }

    void pause() {
        if (pause) {
            pause = false;
            play(currentPos);
        } else {
            pause = true;
            stop();
        }
    }
}
public class CDPlayer extends Player {
    int currentTrack;

    void nextTrack() {
        currentTrack ++;
    }

    void preTrack() {
        if(currentTrack > 1) {
            currentTrack --;
        }
    }

    @Override
    void play(int pos) {
        // 조상의 추상메소드 구현
    }

    @Override
    void stop() {
        // 조상의 추상메소드 구현
    }
}

추상클래스 Player를 상속받아 CDPlayer에서 추상메소드 2개를 구현한다.
사실 Player 클래스의 play와 stop을 추상메소드로 하는 대신, 아무 내용 없는 메서드로 작성할 수도 있다.
굳이 abstract를 붙여서 추상메소드로 선언하는 이유는?

자손클래스에서 추상메소드를 반드시 구현하도록 강요하기 위해서다.

6-7. final 키워드

final 키워드는 '마지막의', '변경될 수 없는'의 의미를 가지고 있으며 거의 모든 대상에 사용될 수 있다.

final이 사용되는 곳 -> 클래스, 메소드, 멤버변수, 지역변수

대상의미
클래스변경될 수 없는 클래스. 확장될 수 없는 클래스가 된다.(상속 금지)
메소드변경될 수 없는 메소드, final로 지정된 메소드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가된다.
지역변수변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가된다.

대표적인 final 클래스로는 String, Math가 있다.

final class FinalTest {       // 조상이 될 수 없는 클래스
  final int MAX_SIZE = 10;    // 값을 변경할 수 없는 멤버변수 (상수)

  final void getMaxSize() {     // 오버라이딩 할 수 없는 메소드
    final int LV = MAX_SIZE;    // 값을 변경할 수 없는 지역변수 (상수)
    return MAX_SIZE;
  }
}

생성자를 이용한 final 멤버 변수의 초기화

public class FinalCardTest {
    public static void main(String[] args) {
        Card c = new Card("HEART", 10);
        System.out.println(c.KIND);
        System.out.println(c.NUMBER);
        System.out.println(c);
    }
}

class Card {
    final int NUMBER;
    final String KIND;

    static int width = 100;
    static int height = 250;

    Card(String kind, int num) {
        KIND = kind;
        NUMBER = num;
    }

    public String toString () {
        return KIND + " " + NUMBER;
    }
}

final이 붙은 변수는 상수이므로 일반적으로 선언과 함께 초기화를 하지만, 인스턴스 변수의 경우
생성자에서 초기화 되도록 할 수 있다. 이것이 불가능하다면 클래스에 선언된 final이 붙은 인스턴스 변수는 모든 인스턴스에서 같은 값을 가져야만 할 것이다.

6-8. Object 클래스

Object 클래스? 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다.

class Tv {
  ...
}

class Tv extends Object {
  ...
}

위쪽 코드를 컴파일하면 아래 코드와 같이 컴파일러가 자동으로 'extends Object'를 추가하여
Tv 클래스가 Obejct클래스로부터 상속받도록 한다.

모든 상속계층도의 최상위에는 Object 클래스가 위치한다. 그래서 자바의 모든 클래스들은 Object 클래스의
멤버들을 상속받기 때문에 Object 클래스에 정의된 멤버들을 사용할 수 있다.

toString()이나 equals(Object o)와 같은 메소드를 따로 정의하지 않고도 사용할 수 있었던 이유가 바로
Object 클래스를 상속받았기 때문이다.

다음은 Object 클래스의 주요 메소드들을 살펴보자.

메소드설명
protected Object clone()객체 자신의 복사본을 반환
public boolean equals(Object obj)객체 자신과 객체 obj가 같은 객체인지 알려준다.
protected void finalize()객체가 소멸될 때 까지 가비지 컬렉터에 의해 자동적으로 호출된다.
public Class getClass()객체 자신의 클래스 정보를 담고 있는 Class 인스턴스를 반환한다.
public int hashCode()객체 자신의 해시코드를 반환한다.
public String toString()객체 자신의 정보를 문자열로 반환한다.
public void notify()객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다.
public void notifyAll()객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.
public void wait() public void wait(long timeout) public void wait(long timeout, int nanos)다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간동안 기다리게 한다.

Object 클래스는 멤버변수는 없고 오직 11개의 메소드만 가지고 있다. 이 메소드들은
모든 인스턴스가 가져야할 기본적인 것들이다.

equals(Object obj)

public boolean equals(Object obj) {
  return (this==obj);
}

두 객체의 같고 다름을 참조변수의 값으로 판단한다.

public class EqualsEx {
    public static void main(String[] args) {
        Person p1 = new Person(1);
        Person p2 = new Person(1);

        System.out.println(p1.equals(p2));
    }
}

class Person{
    long id;

    Person(long id){
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if(o instanceof Person)
            return id == ((Person)o).id;
        else
            return false;
    }
}

// 결과
true

equals를 재정의 하지 않았다면 결과는 false였을 것이다. p1과 p2의 참조값이 다르기 때문이다.
그러나 equals 메소드를 재정의 하여 각 인스턴스의 id값을 비교하기 때문에 'true' 값이 나온다.

hashCode()

이 메소드는 해싱 기법에 사용되는 해시 함수를 구현한 것이다.

해시코드가 같은 두 객체가 존재하는 것이 가능하지만, Object클래스에 정의된
hashCode 메소드는 객체의 주소값을 해시코드를 만들어 반환하기 때문에 32bit JVM에서는
서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없다. 그러나 64 bit JVM에서는 8 byte 주소값으로
해시코드(4 byte)를 만들기 때문에 중복될 수도 있다.

인스턴스의 같고 다름을 판단하려면 equals 메소드 뿐만 아니라 hashCode메소드도 적절히 오버라이딩 해야한다.

public class HashCodeEx {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println(str1.equals(str2));
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());

        System.out.println(System.identityHashCode(str1));
        System.out.println(System.identityHashCode(str2));
    }
}

// 결과
true
99162322
99162322
999966131
1989780873

Stirng 클래스 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode 메소드가
오버라이딩 되어 있기 때문에, str1, str2가 동일한 값을 얻는다.

System.identityHashCode()의 경우 Object처럼 객체의 주소값으로 해시코드를 생성하기 때문에
모든 객체에 대해 항상 다른 해시코드 값을 반환할 것을 보장한다.

toString()

인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의되었다.

Tv t = new Tv("LG");
System.out.println(t);

LG Tv를 생성 후, 출력을 하도록 했다. 위의 결과는 Tv@139a55 이런식으로 16진수의 해시코드를 얻게된다.

하지만, 우리가 원하는 결과는 Tv의 제조사 정보를 보고 싶은 것이다.

class Tv {
  String manufacturer;
  ...

  public String toString() {
    return "제조사:" + manufacturer;
  }
}

위와 같이 toString() 메소드를 재정의하면 우리가 원하는 결과인 "제조사: LG" 라는 문자열이 반환된다.

getClass()

자신이 속한 클래스의 Class객체를 반환하는 메서드이다.

public final class Class implements ... {
  ... 
}

Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
클래스 파일이 '클래스 로더'에 의해서 메모리에 올라갈 때, 자동으로 생성된다.

클래스 로더는 실행 시 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.
먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인 후, 있으면 객체의 참조를 반환하고
없으면 클래스 패스(classpath)에 지정된 경로를 따라서 클래스 파일을 찾는다.

못 찾으면 -> ClassNotFountException이 발생
찾으면 -> 해당 클래스 파일을 읽어서 Class 객체로 변환

  • Class 객체를 얻는 방법
Class cObj = new Card().getClass(); // 생성된 객체로 읻기
Class cObj = Card.class;            // 클래스 리터럴(*.class)로 얻기
Class cObj = Class.forName("Card)   //  클래스 이름으로 얻기

Class 객체를 이용하면 클래스에 정의된 멤버의 이름이나 개수 등, 클래스에 대한 모든 정보를
얻을 수 있기 때문에 Class 객체를 통해서 객체를 생성하고 메서드를 호출하는 등 동적인 코드를 작성할 수 있다.

위와 관련된 자세한 내용은 '리플렉션'으로 찾아보면 더 많은 정보를 알 수 있다.

References

profile
얍얍 개발 펀치

0개의 댓글