ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java java.lang - 1, Object 클래스
    Java 2024. 2. 25. 15:33
    728x90
    반응형

    java.lang 패키지는 자바프로그래밍에서 가장 기본이 되는 클래스들을 포함하고 있다.

    자주 사용되는 클래스 몇가지만 배워보자.

    Object 클래스

    멤버변수는 없고 오직 11개의 메서드만 가지고 있다.
        

    equals(Object obj)

    매개변수로 객체의 참조변수를 받아 비교하여 그 결과를 boolean값으로 알려 주는 역할을 한다.

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


    위 코드에서 알 수 있듯이 두 객체의 같고 다름을 참조변수의 값으로 판단한다.
    그렇기 때문에 서로 다른 두 객체를 equals로 비교하면 항상 false를 얻는다.

    참고            객체를 생성할 때, 메모리의 비어있는 공간을 찾아 생성하므로 서로 다른 두 객체가 같은 주소를 갖는 일은 있을 수 없다.
                        두 개 이상의 참조변수가 같은 주소값을 갖는 것(한 객체를 참조하는 것)은 가능하다.

    Object클래스로부터 상속받은 equals는 결국 두 참조변수가 같은 객체를 참조하는지,
    즉, 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능밖에 없다.

    실제 값을 비교하고 싶으면 오버라이딩 해야 한다.

    String 클래스는 오버라이딩을 통해 String 인스턴스가 갖는 문자열 값을 비교하게 되어있다.
    그러나 StringBuffer 클래스는 오버라이딩되어 있지 않다.

        

    hashCode()

    해싱(hashing)기법에 사용되는 해시함수(hash function)를 구현한 것    
    해싱은 데이터 관리기법 중 하나로, 다량의 데이터를 저장하고 검색하는데 유용함

    해시함수는 찾고자하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드(hashCode)를 반환한다

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

    클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야 할 때는,
    hashCode메서드도 오버라이딩 해야한다.

    String 클래스는 문자열 내용이 같으면 동일한 해시코드를 반환하도록 hashCode메서드가 오버라이딩 되어 있다.
    따라서 문자열 내용이 같은 str1과 str2에 대해 hashCode를 호출하면 항상 동일한 해시코드값을 얻는다.
        
        

    toString()

    public String toString(){
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }


    클래스를 작성할 때 오버라이딩 하지 않는다면, 위 내용이 그대로 사용된다.
    즉, toString을 호출하면 클래스이름에 16진수의 해시코드를 얻게 될 것이다.

    String 클래스의 toString()은 String 인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩되어 있다.
            
    Date 클래스의 경우, Date 인스턴스가 갖고 있는 날짜와 시간을 문자열로 반환하도록 오버라이딩되어 있다.

    Object 클래스의 toString이 public이므로, 이를 오버라이딩 하는 자손클래스의 toString도 public이다.

        

    clone()

    자신을 복제하여 새로운 인스턴스를 생성

    어떤 인스턴스에 대해 작업을 할 때, 
    기존 인스턴스는 보존하고 clone()을 이용해 새로운 인스턴스를 생성하여 작업하면,
    작업 이전의 값이 보존되므로 작업에 실패해서 원상태로 되돌리거나 변경되기 전의 값을 참고하는데 도움이 된다.

    Object클래스의 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에, 
    참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.

    예를 들어 배열의 경우, 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 복제된 인스턴스의 작업이 원래 인스턴스에 영향을 미친다.
    이런 경우 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야 한다.

    clone()을 사용하려면 먼저 복제할 클래스가 Cloneable인터페이스를 구현해야 하고,
    clone()을 오버라이딩하면서 접근 제어자를 protected에서 public으로 변경한다.
    그래야 상속관계가 없는 다른 클래스에서 clone을 호출할 수 있다.

    class Point implements Cloneable{
        int x, y;
    
        Point(int x, int y){
            this.x = x;
            this.y = y;
        }
    
        public String toString(){
            return x + "" + y;
        }
    
        public Object clone(){
            Object obj = null;
            try{
                obj = super.clone();
            } catch (CloneNotSupportedException e){}
            return obj;
        }
    }
    
    class CloneEx1{
        public static void main(String args[]){
            Point original = new Point(3, 5);
            Point copy = (Point)original.clone();
            sysout(original);
            sysout(copy);
        }
    }


    결과    3, 5
               3, 5

    참고            Object 클래스의 clone은 Cloneable을 구현하지 않은 클래스에서 호출되면 예외를 발생시킨다

    Cloneable 인터페이스를 구현한 클래스의 인스턴스만 clone을 통한 복제가 가능한데,
    인스턴스 데이터를 보호하기 위함이다.
    Cloneable 인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미이다.

        

    공변 반환타입

    오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것
    jdk1.5부터 추가
            
    예전에는 오버라이딩할 때 조상에 선언된 메서드의 반환타입을 그대로 사용해야 했다.

    public Point clone(){           // 반환 타입을 Object에서 Point로 변경
        Object obj = null;
        try{
            obj = super.clone();
        } catch (CloneNotSupportedException e){}
        return (Point)obj;
    }
    
    번거로운 형변환이 줄어든다
    
    Point copy = (Point)original.clone();
        >>
    Point copy = original.clone();


    일반적으로 배열을 복사할 때, 
    같은 길이의 새로운 배열을 생성한 다음에 System.arraycopy()를 이용해서 복사하지만,
    clone()을 이용해서 간단하게 복사할 수도 있다.

    int[] arr = {1, 2, 3, 4, 5};
    int[] arrClone = arr.clone();
        ==
    int[] arr = {1, 2, 3, 4, 5};
    int[] arrClone = new int[arr.length];                   // 배열을 생성
    System.arraycopy(arr, 0, arrClone, 0, arr.length);      // 내용을 복사


    Object 클래스에는 protected로 정의된 clone()을 배열에서는 public으로 오버라이딩했기 때문에 직접 호출이 가능하다.

        

    얕은 복사와 깊은 복사

    clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐,
    객체가 참조하고 있는 객체까지 복제하지는 않는다.

    기본형 배열을 복제하는 경우는 문제가 없지만,
    객체배열을 clone()으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어렵다.

    이런 복제를 얕은 복사(shallow copy)라고 한다.
    얕은 복사에서는 원본을 변경하면 복사본도 영향을 받는다.

    원본이 참조하고 있는 객체까지 복제하는 것을 깊은 복사(deep copy)라고 하며,
    원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 주지 않는다.           

    Circle c1 = new Circle(new Point(1, 1), 2.0);
    Circle c2 = c1.clone();                         // 얕은 복사


    c1과 c2는 같은 Point인스턴스를 가리키게 되므로 완전한 복제라고 볼 수 없다.

            

    얕은 복사

    public Circle shallowCopy(){
        Object obj = null;
        try{
            obj = super.clone();
        } catch (CloneNotSupportedException e){}
        return (Circle)obj;
    }


            

    깊은 복사

    public Circle deepCopy(){
        Object obj = null;
        try{
            obj = super.clone();
        } catch (CloneNotSupportedException e){}
        Circle c = (Circle)obj;
        c.p = new Point(this.p.x, this.p.y);
        return c;
    }


    deepCopy는 shallowCopy에 두 줄을 추가하여, 복제된 객체가 새로운 Point인스턴스를 참조하도록 했다.
    원본이 참조하고 있는 객체까지 복사한 것이다.
        
        

    getClass()

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

    Class객체는 이름이 'Class'인 클래스의 객체이다.

    public final class Class implements ... {       // Class 클래스
    
    }


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

    클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

    먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고,
    있으면 객체의 참조를 반환하고 없으면 클래스 패스(classpath)에 지정된 경로를 따라 클래스 파일을 찾는다.

    못 찾으면 ClassNotFoundException 발생, 찾으면 해당 클래스 파일을 읽어서 Class객체로 변환

    파일 형태로 저장되어 있는 클래스를 읽어서 Class클래스에 정의된 형식으로 변환하는 것이다.
    즉, 클래스 파일을 읽어서 사용하기 편한 형태로 저장해놓은 것이 클래스 객체이다.

    참고            클래스 파일을 메모리에 로드하고 변환하는 일은 클래스 로더가 한다.

        

    Class객체를 얻는 방법

    클래스의 정보가 필요할 때, 먼저 Class객체에 대한 참조를 얻어와야 하는데,
    해당 Class객체에 대한 참조를 얻는 방법은 여러가지가 있다.

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


    forName은 특정 클래스 파일,
    예를 들어 데이터베이스 드라이버를 메모리에 올릴 때 주로 사용

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

    Card c = new Card();                // new 연산자를 이용해 객체 생성
    Card c = Card.class.newInstance();  // Class객체를 이용해 객체 생성


    참고            newInstance()는 InstantiationException이 발생할 수 있으므로 예외처리 필요

    728x90
    반응형
Designed by Tistory.