Posts 학원 #24일차: java.lang.Object 메서드
Post
Cancel

학원 #24일차: java.lang.Object 메서드

com.eomcs.corelib.ex01

객체지향

oop

  • Actor: 어플리케이션이 동작하게 만드는 촉발점
  • 요청하지도 않았는데 스스로 동작하는 경우: ex) 고객의 자산정보 자동으로 보냄 (지정된 시간이 있으면 보냄 )
  • 자신의 업무 중에서 사람, 사물, 개념을 application을 통해 관리하고 싶은 것. 그러려면 사람, 사물, 개념에 관련된 데이터를 정의해야 한다. 이렇게 일단 추상화를 하고 부가적인 문법(상속, 다형성, 캡슐화)을 통해서 프로그래밍이 필요하다.

java.lang.Object

클래스를 정의할 때 수퍼 클래스를 지정하지 않으면 컴파일러는 자동으로 Object를 상속 받는다.

Object 클래스의 주요 메서드

자바의 모든 클래스는 Obejct 클래스에 정의된 메서드를 호출할 수 있다.

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

toString()

toString()이란?

1
2
3
4
5
// Object 클래스에 정의된 toString()
public String toString() {
    return getClass().getName() + "@" 
        + Integer.toHexString(hashCode());
}
  • 클래스 정보(클래스이름과 해시코드)를 리턴한다.
    • 패키지명.클래스명@16진수해시값 예) ch15.My1@1e81f4dc
  • 해시값: 인스턴스를 식별할 때 사용하라고 JVM이 임의로 붙인 식별자이다. 인스턴스의 주소가 아니다. 자바는 메모리 주소를 알려주지 않는다.

toString() 오버라이딩

  • Object로 상속 받은 toString()의 리턴 값이 마음에 들지 않는다면 재정의하라.
  • 보통 인스턴스 내부 데이터를 문자열로 리턴하도록 변경한다.
  • 개발자들이 프로그램을 실행하는 중에 인스턴스의 내부 값을 빠르게 확인하고 싶을 때 오버라이딩한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static class My {
    String name;
    int age;
    
    @Override
    public String toString() {
      return "name=" + name + ", age=" + age + "]";
    }
}

public static void main(String[] args){
    My obj = new My();
    obj1.name = "Monica";
    obj1.age = 25;
    
    System.out.println(obj);
    // name=Monica, age=25
}
  • println()에 String이 아닌 객체를 넘기면, println()에서 내부적으로 그 객체에 대해 toString()을 호출하여 그 리턴 값을 출력한다. 따라서 prinln()으로 객체의 값을 출력할 때 굳이 toString()을 번거롭게 호출할 필요가 없다.

equals()

equals()란?

1
2
3
4
// Object 클래스에 정의된 equals()
public boolean equals(Object obj) {
    return this == obj;
}
  • Object에서 상속 받은 equals()==연산자와 마찬가지로 인스턴스가 같은 지를 비교한다.

equals() 오버라이딩

  • 데이터 타입을 정의한 클래스의 경우 보통 toString()과 equals()를 오버라이딩 한다.
  • 인스턴스의 내용물이 같은지 비교하도록 만들고 싶다면 오버라이딩해야 한다.
    • StringWrapper 클래스는 equals()를 오버라이딩하였다.
    • StringBuffer 클래스는 오버라이딩하지 않았다.
1
2
3
4
5
6
7
8
9
10
11
// String
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2); //false
System.out.println(s1.equals(s2)); //true

// StringBuffer
StringBuffer sb1 = new StringBuffer("Hello");
StringBuffer sb2 = new StringBuffer("Hello");
System.out.println(sb1 == sb2); //false
System.out.println(sb1.equals(sb2)); //false

hashCode()

해시값이란?

git-hashcode

  • 데이터를 다른 데이터와 빠르게 비교하기 위해 사용하는 특별한 정수 값
  • 데이터가 같은 지 비교할 때 주로 사용해 “디지털 지문”이라고도 한다.
  • 예시
    • 본인 여부를 확인하는 인증서
    • 파일 위변조 검사
      • git에서 커밋할 때 붙이는 고유번호
      • 파일 다운로드 사이트에서 제공하는 해시값
      • 파일 공유사이트에서 파일 구분할 때 사용하는 해시값. 사용자가 파일 이름을 변경하더라도 데이터만 바꾸지 않는다면 파일의 해시값은 같다.
  • 특정 수학 공식에 따라 값을 계산한다.
    • 해시 알고리즘: SHA, MD(Message Digest), PGP 등

hashCode() 란?

  • 기본적으로 인스턴스마다 고유의 값(해시값)을 리턴한다.

  • 해시값은 인스턴스를 식별할 때 사용하라고 JVM이 임의로 붙인 식별자이다.

  • 같은 값을 갖고 있다 하더라도 인스턴스가 다르다면 해시코드의 값이 다르다.
  • 인스턴스 주소가 아니다.

hashCode() 오버라이딩

  • 인스턴스(메모리)가 다르더라고 같은 데이터를 갖는 경우 같은 것으로 취급하기 위해 hashCode()를 재정의한다.
  • Map에 값을 저장하는 key로 사용할 때 hashCode()를 오버라이딩한다.
  • 보통 값이 같은 지 비교할 때 equals()와 함께 사용되기 때문에 hashCode()를 오버라이딩 할 때 equals도 함께 오버라이딩한다.
  • 방법1: 이클립스의 hashCode() + equals() 오버라이딩 기능 사용
  • 방법2: String 클래스 사용
    • 모든 값을 문자열로 만들어 붙인 뒤 String 클래스에 있는 hashCode()를 사용한다.
    • String클래스에 있는 hashCode()는 문자열이 같은 경우 같은 해시값을 리턴하도록 이미 오버라이딩 되어 있다.
    • 데이터가 같으면 문자열이 같을 것이고, 문자열이 같으면 해시코드의 리턴 값도 같을 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    @Override
    public int hashCode() {
      String value = String.format("%s,%d,%d,%d,%d,%.1f", 
          this.name, this.kor, this.eng, this.math,
          this.sum, this.aver);
      return value.hashCode();
    }
}

응용1: HashSet

HashSet이란?

  • java.util.HashSet

  • 목록을 관리하는 컬렉션 API

  • 중복값을 저장하지 않는 집합의 기능을 수행한다.
  • 저장할 객체에 대해 hash 코드로 중복 여부를 검사한다.
  • 해시 값이 다르면 다른 값으로 취급한다.
  • hash 코드로 값을 저장할 인덱스를 결정하기 때문에 값을 꺼낼 때 저장한 순서대로 꺼낼 수 없다.

hashCode()와 equals() 오버라이딩

  • 인스턴스가 다르더라도 인스턴스의 필드 값이 같을 경우 HashSet에 중복 저장되지 않도록 하기 위해 hashCode()와 equals() 모두 오버라이딩해야 한다.
    • hashCode()는 같은 필드 값을 갖는 경우 같은 해시코드를 리턴하도록 한다.
    • equals()는 필드 값이 같을 경우 true를 리턴하도록 변경한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  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;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + age;
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      result = prime * result + (working ? 1231 : 1237);
      return result;
    }

    @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;
      if (age != other.age)
        return false;
      if (name == null) {
        if (other.name != null)
          return false;
      } else if (!name.equals(other.name))
        return false;
      if (working != other.working)
        return false;
      return true;
    }
    
  }
  
  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);
    
    System.out.println(s1 == s2);
    
    System.out.println(s1.hashCode());
    System.out.println(s2.hashCode());
    System.out.println(s3.hashCode());
    System.out.println(s4.hashCode());
    System.out.println("--------------------");
    
    // 해시셋(집합)에 객체를 보관한다.
    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 ? "재직중" : "실업중");
    }
    

응용2: HashMap의 key

HashMap이란?

  • Map은 값을 저장할 때 key를 이용한다.
    • key: 값을 저장할 위치를 계산할 때 사용한다. key 객체의 hashCode()를 호출하여 그 리턴값을 사용하여 위치를 계산한다. 따라서 key객체의 해시코드가 다르면 위치도 다르다. 해시코드가 같다면 같은 key로 간주한다.
    • map.put(key, value);
  • put(Object key, Object value) 원리
    • put() 메서드는 key로 넘겨받은 객체에 대해 hashCode()를 호출하여 정수값을 얻는다.
    • 그렇게 리턴 받은 정수 값(해시 코드)를 사용하여 value 객체를 저장할 위치를 계산한다.
    • 그런 후 그 위치에 해당하는 배열(배열로 관리한다면)에 저장한다.
  • get(key) 실행 원리
    • key 파라미터로 받은 객체에 대해 hashCode() 호출하여 정수 값을 얻는다.
    • 정수 값을 이용하여 값이 저장된 위치를 찾는다.
    • 원래의 키와 같은지 equals()로 한 번 더 비교한다.
    • 같다면 같은 key로 간주하여 해당 값을 꺼내 리턴한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 오버라이딩 이전

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));

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

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

System.out.println(map.get(k6)); // 엥? 값을 꺼낼 수가 없다.
// 두 키 객체 k3와 k6가 내용물이 같다 하더라도, (둘다 "haha"이다.)
// hashCode()의 리턴 값이 다르고, equals() 비교 결과도 false 라면
// HashMap 클래스에서는 서로 다른 key로 간주한다.

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()의 비교 결과도 다르다.

키의 hashCode()와 equals() 오버라이딩

  • 오버라이딩을 해서 같은 데이터를 가지고 있다면 hashCode()의 리턴 값과 equals()의 비교 결과가 true가 나오게 바꿔줄 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 오버라이딩 이전
System.out.println(map.get(k3));

// 다른 key 객체를 사용하여 값을 꺼내보자.
MyKey2 k6 = new MyKey2("haha");

System.out.println(map.get(k6)); // OK! 값을 정상적으로 꺼낼 수 있다.
// k3와 k6는
// hashCode()의 리턴 값이 같다
// equals() 비교 결과도 true 이기 때문에
// HashMap 클래스에서는 서로 같은 key라고 간주한다.

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()의 비교 결과도 같다.
  • Object 클래스에서 상속받은 hashCode()는 인스턴스 필드의 값이 같은 지 따지지 않고 무조건 인스턴스마다 고유의 해시값을 리턴한다. 또한 equals()는 필드의 값이 같은지 비교하지 않고 같은 인스턴스인지만 따진다.
  • 그러나 wrapper클래스와 String 클래스는 Object 클래스에서 상속받은 hashCode()와 equals()를 오버라이딩 했기 때문에 인스턴스가 다르더라도 인스턴스 필드의 값이 같으면 hashCode()의 리턴 값이 같고 equals()까 true를 리턴한다. 그래서 wrapper 클래스와 String 클래스는 HashMap/HashTable의 key로 사용할 수 있다. 실무에서도 String이나 primitive type(결국 wrapper 객체로 전환되기 때문에)을 key로 자주 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
HashMap<Integer,Student> map = new HashMap<>();

Integer k1 = new Integer(101);
Integer k2 = new Integer(102);
Integer k3 = new Integer(103);

System.out.println(k1 == k2);
System.out.println(k1 == k3);
System.out.println(k2 == k3);

map.put(k1, new Student("홍길동", 20, false));
map.put(k2, new Student("임꺽정", 30, true));
map.put(k3, new Student("유관순", 17, true));

// 다음과 같이 int를 key로 사용할 수 있다.
// => key 값으로 int를 넘겨준다면,
//    컴파일러가 컴파일 할 때 auto-boxing을 수행하여 Integer 객체를 만든다.
//    그리고 그 객체를 넘겨주는 것이다.
map.put(104 /* new Integer(104)*/, new Student("안중근", 24, true));
map.put(105 /* new Integer(105)*/, new Student("윤봉길", 22, false));

// 값을 저장할 때 사용한 key로 다시 값을 꺼내보자!
System.out.println(map.get(k2));

// k2와 같은 정수값을 가지는 key를 새로 생성한다.
Integer k6 = new Integer(102);

// k2와 같은 값을 갖는 k6로 값을 꺼내보자!
System.out.println(map.get(k6));

// 다음과 같이 k2와 k6는 분명히 다른 객체이다.
System.out.println(k2 == k6); 

// 그러나 k2와 k6는 같은 해시코드를 갖는다.
System.out.println(k2.hashCode()); // hash code는 같다.
System.out.println(k6.hashCode()); // hash code는 같다.

// 또한 equals()의 리턴 값도 true이다.
System.out.println(k2.equals(k6)); // equals()의 비교 결과도 같다.

getClass()

getClass()란?

  • reflection API (클래스를 거울처럼 비춰준다)
  • 해당 클래스의 정보를 리턴한다.

API(Application Programming Interface)란? 어플리케이션을 프로그래밍하는 데 사용하는 인터페이스 도구

인터페이스란? 서로 대화하는 도구, 수단 (마우스가 컴퓨터 대화 수단인 블루투스, 사람과 사람 사이의 대화 도구인 언어)

1
2
3
4
5
6
7
8
9
10
11
static class My {}

public static void main(String[] args){
    My obj = new My();
    
    //레퍼런스를 통해서 인스턴스의 클래스 정보를 알아낼 수 있다.
    Class<?> classInfo = obj.getClass();
    
    System.out.println(classInfo.getName()); //com.eomcs.corelib.ex01.My
    System.out.println(classInfo.getSimpleName()); //My
}

getClass()와 배열

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String obj1 = new String();
Class<?> classInfo = obj1.getClass();
System.out.println(classInfo.getName()); // java.lang.String

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

int[] obj3 = new int[10];
classInfo = obj3.getClass();
System.out.println(classInfo.getName()); //[I

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

배열의 항목 이름

1
2
3
4
5
6
7
8
9
String[] obj2 = new String[10];
// 배열 항목의 타입 정보를 가져온다.
Class<?> compTypeInfo = classInfo.getComponentType();
System.out.println(compTypeInfo.getName()); //java.lang.String

// 값을 한 번 밖에 사용하지 않을 것이라면
// 위의 경우처럼 한 번씩 호출하고, 리턴 값을 가지고 또 호출하는 방식으로 값을 꺼내지 않는다.
// 체인(chain) 방식으로 호출한다.
System.out.println(obj2.getClass().getComponentType().getName()); //java.lang.String

Class 클래스의 getName() 메서드

  • public String getName()
  • 배열 인스턴스에 사용된다면 다음과 같은 문자열을 리턴한다.

getName()-array

1
2
3
4
5
6
7
8
9
//getName()
 String.class.getName()
     returns "java.lang.String"
 byte.class.getName()
     returns "byte"
 (new Object[3]).getClass().getName()
     returns "[Ljava.lang.Object;"
 (new int[3][4][5][6][7][8][9]).getClass().getName()
     returns "[[[[[[[I

clone()

clone() 이란?

  • 인스턴스를 복제할 때 호출하는 메서드이다.

  • 새 객체를 만들어 기존 객체의 값을 저장할 수도 있지만, 값이 너무 많다면 비효율적이다.

clone() 오버라이딩

  • clone()의 접근 범위가 protected 라서 같은 패키지의 멤버이거나 서브 클래스가 아니면 호출할 수 없다.
  • Object에서 상속 받은 clone()을 오버라이딩하자!
    • clone()은 JVM이 알아서 해 주기 때문에 복제를 위한 코드를 따로 작성할 필요가 없다.
    • 다른 패키지의 멤버가 호출할 수 있도록 public으로 접근 제어의 범위를 넓힌다.
    • 리턴 타입은 해당 클래스 이름으로 변경한다.
    • clone() 메서드의 사용을 활성화시킨다.
      • 개발자가 클래스 선언부에 이 클래스에 대해 클론을 선언하면 호출하시오, 라는 허락이 없으면 복제 허락 안함 (안전장치 같은것!!!!!!)
      • 인스턴스 복제 기능을 활성화시키려면 Clonenable 인터페이스를 구현해야 한다.
      • Clonenablee 인터페이스에는 메서드가 선언되어 있지 않아서 클래스는 따로 메서드를 구현할 필요가 없다.
      • Clonenable을 구현하는 이유는 JVM에게 이 클래스의 인스턴스를 복제할 수 있음을 표시하기 위함이다. 이 표시가 안 된 클래스는 JVM이 인스턴스를 복제해 주지 않는다. 즉 clone()을 호출하지 않는다. 대신 JVM은 java.lang.cloneNotSupportedException을 발생시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// clone() 메서드 사용 활성화
// JVM아, 이 클래스의 인스턴스는 복제할 수 있어!
static class Score implements Clonenable {
    String name;
    int kor;
    int eng;
    int math;
    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;
    }
    
    // 오버라이딩
    // public 으로 접근 제어 범위 넓히기 + 리턴타입 해당 클래스 이름으로 변경
    @Override
    public Score clone() throws CloneNotSupportedException {
        return (Score) super.clone();
    }
}

public static void main(String[] args){
    Score s1 = new Score("Monica", 100, 100, 100);
	Score s2 = s1.clone();
    
    System.out.println(s1 == s2); //false, 인스턴스는 다르다.
}

shallow copy

  • 해당 객체의 필드 값만 복제한다.
  • 그 인스턴스 변수가 가리키고 있는 객체는 복사하지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class Engine {
    int cc;
    int valve;
}

static class Car implements Cloneable {
    String maker;
    String name;
    Engine engine;
    
    @Override
    public Car clone() throws CloneNotSupportedException {
        return (Car) super.clone();
    }
}

deep copy

  • 객체의 인스턴스 변수가 가리키고 있는 객체까지 복사하는 것(객체들을 따라가면서 다 복사하는 것)을 말한다.
  • 개발자가 직접 clone() 메서드 안에 deep copy를 수행하는 코드를 작성해야 한다.
  • 어떤 사람 복제할 때 그 사람의 집과 가족까지 다 클론해서 원본과 겹치는 것을 없게 만드는 것과 같다.
  • 일반적으로 많이 쓰이지는 않는다.
1
2
3
4
5
6
@Override
public Car clone() throws CloneNotSupportedException {
  Car copy = (Car) super.clone();
  copy.engine = this.engine.clone();
  return copy;
}
This post is licensed under CC BY 4.0 by the author.

학원 #23일차: 다형성 문법(오버로딩, 오버라이딩), 캡슐화 문법

💻 02. HTTP 캐싱

Loading comments from Disqus ...