본문 바로가기
개발자 꿈나무의 하루/01_Boot Camp

(네이버클라우드 부트캠프) 21일차 - Java프로그래밍 기초(Object 클래스)

by kk_naks 2024. 6. 24.

1. Java최상위 클래스

  1.1 Object클래스

- Object 클래스는 자바의 최상위 클래스이다.
- 모든 클래스는 Object 클래스와 "링크"된다. 

 

  1.2 instanceof 연산자

- instaceof연산자는 레퍼런스가 가리키는 인스턴스가 지정한 클래스의 인스턴스이거나 super 클래스의 인스턴스 있지 확인하는 연산자이다.

package com.eomcs.basic.ex01;
//클래스를 정의할 때 수퍼 클래스를 지정하지 않으면 
//컴파일러는 자동으로 Object를 상속 받는다.
public class Exam0110 /*extends Object*/ {
  static class My /*extends Object*/ {
  }
  public static void main(String[] args) {
    // instanceof 연산자
    // => 레퍼런스가 가리키는 인스턴스가 지정한 클래스를 인스턴스 이거나 또는 조상으로 갖는지 검사한다.
    Object obj = new My();
    // Object의 레퍼런스에 My 인스턴스 주소를 저장할 수 있다는 것은
    // My 클래스가 Object 크래스의 서브 클래스임을 증명하는 것이다.
    System.out.println(obj instanceof My); //true
    System.out.println(obj instanceof String); //false
    System.out.println(obj instanceof Object); //true
  }
}

// Object 클래스의 주요 메서드
// 1) toString()
//    => 클래스이름과 해시코드를 리턴한다.
// 2) equals()
//    => 같은 인스턴스인지 검사한다. 
// 3) hashCode()
//    => 인스턴스를 식별하는 값을 리턴한다.
// 4) getClass()
//    => 인스턴스의 클래스 정보를 리턴한다.
// 5) clone()
//    => 인스턴스를 복제한 후 그 복제 인스턴스를 리턴한다.
// 6) finalize()
//    => 가비지 컬렉터에 의해 메모리에서 해제되기 직전에 호출된다.
//

 

2. Object class의 주요 메서드

  2.1 toString() 메서드

- toString()메서드는 클래스 정보 반환한다.
- 형식은 "패키지명.클래스@16진수해시값" 이다.

package com.eomcs.basic.ex01;
public class Exam0120 {
  static class My {
  }
  public static void main(String[] args) {
    My obj = new My();
    System.out.println(obj.toString());
    //com.eomcs.basic.ex01.Exam0120$My@7ad041f3
	//.toString을 생략가능
    System.out.println(obj);
    //com.eomcs.basic.ex01.Exam0120$My@7ad041f3

    My obj2 = new My();
    My obj3 = new My();

    System.out.println(obj2 == obj3); //false

    System.out.println(obj2.toString());
    //com.eomcs.basic.ex01.Exam0120$My@251a69d7

    System.out.println(obj3.toString());
    //com.eomcs.basic.ex01.Exam0120$My@7344699f
  }

}

 

  2.1 toString() 메서드 응용(@override활용)

package com.eomcs.basic.ex01;
public class Exam0121 {

  static class My {
    String name;
    int age;
    // 개발을 하다 보면 인스턴스의 현재 값을 간단히 확인하고 싶을 경우가 있다.
    // 그럴 경우 toString()을 오버라이딩 하라!
    
    //override를 한다고 컴파일러에게 알려주면 오타로 인한 버그를 줄일수 있다.
    @Override
    public String toString() {
      return "My [name=" + name + ", age=" + age + "]";
    }
  }

  public static void main(String[] args) {

    My obj1 = new My();

    obj1.name = "홍길동";
    obj1.age = 20;

    System.out.println(obj1.toString());
    //My [name=홍길동, age=20]

    System.out.println(obj1);
    //My [name=홍길동, age=20]
  }
}

 

  2.2 equals() 메서드

public class Exam0130 {

  static class My {
    String name;
    int age;
  }

  public static void main(String[] args) {
    My obj1 = new My();
    obj1.name = "홍길동";
    obj1.age = 20;

    My obj2 = new My();
    obj2.name = "홍길동";
    obj2.age = 20;
  }
}

 

     - 기본형

- 기본적으로 equals() 메서드로 두 인스턴스를 비교하면 false가 리턴된다.
    System.out.println(obj1 == obj2); //false
    System.out.println(obj1.equals(obj2)); //false

 

     - Override 활용하기

- 두 인스턴스를 비교하기 위해 기존equals를 override를 한다. 
- 같은 클래스를 확인하기 위해 getClass()메서드를 사용한다.
- 대표적으로 String.equals()가 Override를 활용한 경우이다.
static class My {
    String name;
    int age;

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null) {
        return false;
      }
      if (getClass() != obj.getClass()) {
        return false;
      }
      My other = (My) obj;
      //파라미터가 type이 Object type이기 때문에 형변환을 적용한다.
      return age == other.age && Objects.equals(name, other.name);
    }
  }
    System.out.println(obj1 == obj2); //false
    System.out.println(obj1.equals(obj2)); //true

 

  2.3 hashCode() 메서드

- hashCode는  데이터를 구분하기위한 고유번호값이다. 
- 데이터 동일성 검증에 사용된다. 

 

     - 기본형

package com.eomcs.basic.ex01;
public class Exam0140 {
  static class My {
    String name;
    int age;
  }

  public static void main(String[] args) {
    My obj1 = new My();
    obj1.name = "홍길동";
    obj1.age = 20;

    My obj2 = new My();
    obj2.name = "홍길동";
    obj2.age = 20;
    System.out.println(Integer.toHexString(obj1.hashCode())); //7ad041f3
    System.out.println(Integer.toHexString(obj2.hashCode())); //251a69d7
    }
}

 

     - Override 활용하기

  static class My {
    String name;
    int age;

    @Override
    public int hashCode() {
      // String 클래스의 hashCode() 메서드는
      // 같은 문자열에 대해 같은 해시값을 리턴한다.

      String str = String.format("%s,%d", this.name, this.age);
      return str.hashCode();
    }
  }
- 같은 String에 대해서는 동일한 해쉬값을 리턴한다. 

 

    System.out.println(Integer.toHexString(obj1.hashCode())); //994aa0fc
    System.out.println(Integer.toHexString(obj2.hashCode())); //994aa0fc

 

3. hashCode() 와 equals()활용

  3.1 HashSet 

static class Student {
    String name;
    int age;
    boolean working;

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

  public static void main(String[] args) {

    Student s1 = new Student("홍길동", 20, false);
    Student s2 = new Student("홍길동", 20, false);
    Student s3 = new Student("임꺽정", 21, true);
    Student s4 = new Student("유관순", 22, true);
}
- 다음과 같이 Student 클래스와  s1 - s4까지 학생 정보가 정의 되어있다.
- HashSet은 중복값을 제거한 하나의 집합을 나타낸다. 

 

    HashSet<Student> set = new HashSet<Student>();
    set.add(s1);
    set.add(s2);
    set.add(s3);
    set.add(s4);

    // 해시셋에 보관된 객체를 꺼낸다.
    Object[] list = set.toArray();
    for (Object obj : list) {
      Student student = (Student) obj;
      System.out.printf("%s, %d, %s\n",
          student.name, student.age, student.working ? "재직중" : "실업중");
  /* 결과값
    홍길동, 20, 실업중
    임꺽정, 21, 재직중
    홍길동, 20, 실업중
    유관순, 22, 재직중
    */
- 결과 값이 홍길동이 중복되면 안되지만 중복값을 출력한다. 
- HashSet()은 중복을 검사할때, equals()와 hashCode()가 모두 true일때 중복으로 간주한다. 
- Override()를 통해 equals()와 hashCode()를 수정한다.
  static class Student {
 	// 기본구조는 동일
    
    @Override
    public int hashCode() {
      return Objects.hash(age, name, working);
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      Student other = (Student) obj;
      return age == other.age && Objects.equals(name, other.name) && working == other.working;
    }
  }
    HashSet<Student> set = new HashSet<Student>();
    set.add(s1);
    set.add(s2);
    set.add(s3);
    set.add(s4);

    // 해시셋에 보관된 객체를 꺼낸다.
    Object[] list = set.toArray();
    for (Object obj : list) {
      Student student = (Student) obj;
      System.out.printf("%s, %d, %s\n",
          student.name, student.age, student.working ? "재직중" : "실업중");
  /* 결과값
    홍길동, 20, 실업중
    임꺽정, 21, 재직중
    유관순, 22, 재직중
    */

  3.2 HashMap

package com.eomcs.basic.ex01;
import java.util.HashMap;
public class Exam0152 {
  static class MyKey {
    String contents;
    public MyKey(String contents) {
      this.contents = contents;
    }

    @Override
    public String toString() {
      return "MyKey [contents=" + contents + "]";
    }
  }

  public static void main(String[] args) {
    HashMap<MyKey,Student> map = new HashMap<>();

    MyKey k1 = new MyKey("ok");
    MyKey k2 = new MyKey("no");
    MyKey k3 = new MyKey("haha");
    MyKey k4 = new MyKey("ohora");
    MyKey k5 = new MyKey("hul");

    map.put(k1, new Student("홍길동", 20, false));
    map.put(k2, new Student("임꺽정", 30, true));
    map.put(k3, new Student("유관순", 17, true));
    map.put(k4, new Student("안중근", 24, true));
    map.put(k5, new Student("윤봉길", 22, false));

    // HashMap
    // => 값을 저장할 때 key 객체의 해시코드를 이용하여 저장할 위치(인덱스)를 계산한다.
    // => 따라서 해시코드가 같다면 같은 key로 간주한다.
    //

    // 값을 저장할 때 사용한 key 객체로 값을 찾아 꺼낸다.
    System.out.println(map.get(k3));

    // key를 사용하여 값을 꺼내보자.
    MyKey k6 = new MyKey("haha");

    System.out.println(map.get(k6)); // 엥? 값을 꺼낼 수가 없다.

    System.out.println(k3 == k6); // 인스턴스는 다르다.
    System.out.printf("k3(%s), k6(%s)\n", k3, k6);
    System.out.println(k3.hashCode()); // hash code는 다르다.
    System.out.println(k6.hashCode()); // hash code는 다르다.
    System.out.println(k3.equals(k6)); // equals()의 비교 결과도 다르다.

  }

}

 

- 두 키 객체 k3와 k6가 내용물이 같다 하더라도, (둘다 "haha"이다.) 
- hashCode()의 리턴 값이 다르고, equals() 비교 결과도 false 라면  HashMap 클래스에서는 서로 다른 key로 간주한다.
- Override()를 통해 equals()와 hashCode()를 수정한다.
    @Override
    public int hashCode() {
      return Objects.hash(contents);
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      MyKey2 other = (MyKey2) obj;
      return Objects.equals(contents, other.contents);

 

4. getClass()메서드 활용

  4.1 getClass()

package com.eomcs.basic.ex01;
public class Exam0160 {
  static class My {
  }

  public static void main(String[] args) {
    My obj1 = new My();
    // 레퍼런스를 통해서 인스턴스의 클래스 정보를 알아낼 수 있다.
    Class classInfo = obj1.getClass();
    // 클래스 정보로부터 다양한 값을 꺼낼 수 있다.
    System.out.println(classInfo.getName()); // 패키지명 + 바깥 클래스명 + 클래스명
    System.out.println(classInfo.getSimpleName()); // 클래스명

	//com.eomcs.basic.ex01.Exam0160$My
	//My
  }

 

  4.2 getClass() : Primitive type과 String 타입의 클래스 정보

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

    // Primitive Type은 Object의 서브 클래스가 아니기 때문에 getClass()를 호출할 수 없다.
    // 대신 static 변수인 class 를 사용하여 Class 정보를 리턴 받을 수 있다.
    // 
    Class classInfo = byte.class;
    System.out.println(classInfo.getName()); //byte
    System.out.println(short.class.getName()); //short
    System.out.println(char.class.getName()); //char
    System.out.println(int.class.getName());//int
    System.out.println(long.class.getName()); //long
    System.out.println(float.class.getName()); //float
    System.out.println(double.class.getName()); //double
    System.out.println(boolean.class.getName()); //boolean

    System.out.println(String.class.getName());// java.lang.String

  }
}

 

  4.3 getClass() : Primitive[] type과 String[] 배열 타입의 클래스 정보

// Object 클래스 - getClass() 와 배열
package com.eomcs.basic.ex01;

public class Exam0162 {

  public static void main(String[] args) {

    // 배열의 클래스 정보
    String[] obj2 = new String[10];
    Class classInfo = obj2.getClass();
    System.out.println(classInfo.getName()); //[Ljava.lang.String;

    System.out.println(new byte[10].getClass().getName()); //[B
    System.out.println(new short[10].getClass().getName()); //[S
    System.out.println(new char[10].getClass().getName()); //[C
    System.out.println(new int[10].getClass().getName()); //[I
    System.out.println(new long[10].getClass().getName()); //[J
    System.out.println(new float[10].getClass().getName()); //[F
    System.out.println(new double[10].getClass().getName()); //[D
    System.out.println(new boolean[10].getClass().getName()); //[Z

  }
}

 

 

  4.3 getComponentType()

- 배열 안의 원소에 대한 type을 확인하기 위해서는 getComponentType()를 활용한다. 
public class Exam0163 {
  public static void main(String[] args) {
    // 배열의 클래스 정보
    String[] obj2 = new String[10];
    Class classInfo = obj2.getClass();
    System.out.println(classInfo.getName()); //[Ljava.lang.String;

    // 배열 항목의 타입 정보를 가져온다.
    Class compTypeInfo = classInfo.getComponentType();
    System.out.println(compTypeInfo.getName()); //java.lang.String

    // 배열안의 원소에 대한 타입 정보는 getComponetType()을 통해 얻는다.
    System.out.println(obj2.getClass().getComponentType().getName()); //java.lang.String
	// [Ljava.lang.String -> java.lang.String -> java.lang.String
    System.out.println(new int[10].getClass().getComponentType().getName()); //int
    // [I -> int -> int
  }
}

 

5. clone()메서드 활용

  5.1 protectd의 기능

- protected 는 subclass에서 접근가능하지만, 자신의 멤버에 속한 경우에 가능하다. 

public class Exam0170 {
  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    public Score() {}

    public Score(String name, int kor, int eng, int math) {
      this.name = name;
      this.kor = kor;
      this.eng = eng;
      this.math = math;
      this.sum = this.kor + this.eng + this.math;
      this.aver = this.sum / 3f;
    }

    @Override
    public String toString() {
      return "Score [name=" + name + ", kor=" + kor + ", eng=" + eng + ", math=" + math + ", sum="
          + sum + ", aver=" + aver + "]";
    }
  }

  public static void main(String[] args) {

    Score s1 = new Score("홍길동", 100, 100, 100);
    Score s3 = s1.clone(); // 컴파일 오류!
    //
    // Object에서 상속 받은 clone()은 protected 이다.
    // 따라서 같은 패키지에 소속된 클래스이거나 상속 받은 서브 클래스가 아니면 호출할 수 없다.
    // 비록 Object의 서브 클래스라 할지라도 남의 인스턴스로 protected 멤버를 사용할 수 없다.
    // 자신이 상속 받은 protected 멤버인 경우에만 접근할 수 있다.

    // 해결책:
    // => Object에서 상속 받은 clone()을 오버라이딩 하라!
    // => Exam0171.java 를 살펴보라!

  }
}

 

  5.2 clone() 오버라이딩

static class Score implements Cloneable {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    public Score() {}

    public Score(String name, int kor, int eng, int math) {
      this.name = name;
      this.kor = kor;
      this.eng = eng;
      this.math = math;
      this.sum = this.kor + this.eng + this.math;
      this.aver = this.sum / 3f;
    }

    @Override
    public String toString() {
      return "Score [name=" + name + ", kor=" + kor + ", eng=" + eng + ", math=" + math + ", sum="
          + sum + ", aver=" + aver + "]";
    }

    @Override
    public Score clone() throws CloneNotSupportedException {
      // 복제를 위한 코드를 따로 작성할 필요가 없다.
      // JVM이 알아서 해준다.
      // 그냥 상속 받은 메서드를 오버라이딩하고, 접근 권한을 public 으로 확대한다.
      // 원래의 clone() 메서드를 실행한 다음에
      // 리턴 타입을 해당 클래스로 형변환 한다.
      return (Score) super.clone();
    }
  }

  5.3 clone() : deep copy 

    @Override
    public Car clone() throws CloneNotSupportedException {
      // deep copy
      // => 포함하고 있는 객체에 대한 복제를 수행하려면 다음과 같이 
      //    개발자가 직접 포함하는 객체를 복제하는 코드를 작성해야 한다.
      // 
      Car copy = (Car) super.clone();
      copy.engine = this.engine.clone();
      return copy;
    }