728x90
기본 클래스란
- JDK 에서 제공하는 많은 클래스(기본 클래스)를 활용하면 프로그램을 더욱더 효율적으로 구현할 수 있습니다.
- 프로그램을 제작하는 것은 누군가가 만들어준 도구들을 활용하여 만드는 것인 만큼 주요한 파트라고 할 수 있습니다.
- 모든 내용들을 외우고 있을 필요 까지는 없으나, 더 학습이 필요한 경우 JavaDoc 을 활용하는 것이 가장 바람직합니다.
java.lang 패키지
- 우리는 지금까지 많은 예시들을 구현해보면서 왔는데요.
- String, Integer 와 같은 클래스를 사용해왔습니다.
- 이런 클래스들은 어디서 온 것일까요?
- 이들 클래스는 java.lang 패키지에 속해 있습니다.
- String 클래스의 전체 이름은 java.lang.String 이 되는 것이죠.
- 이와 같이 java.lang 패키지에는 기본적으로 많이 사용하는 클래스들이 포함되어 있습니다.
- 그런데 이상한 점이 하나 있지 않나요?
- 우리 프로젝트에 포함되어 있지 않은 외부 클래스인 String 과 같은 것들을 사용할 때 우리는 여태까지 import 문을 사용하지 않아왔지 않나요?
- java.lang 패키지는 컴파일할 때 import java.lang.*; 문장이 자동으로 추가되어 java.lang 패키지 하위 클래스를 모두 사용할 수 있습니다.
- 즉 프로그래머가 import 문을 직접 쓰지 않아도 java.lang 패키지의 모든 하위 클래스를 참조할 수 있습니다.
- 우선, 모든 자바 클래스의 최상위 클래스인 java.lang.Object 부터 알아보겠습니다.
최상위 클래스 Object

- Object 클래스는 모든 자바 클래스의 최상위 클래스입니다.
- 다시 말하면 모든 클래스는 Object 클래스로부터 상속을 받습니다.
- 역시나 이상한 점이 보이죠?
- 우리는 여태까지 Object 를 상속받는다는 extends Object 라는 코드를 작성한 적이 없습니다.
- extends Object 는 컴파일 과정에서 자동으로 쓰이게 됩니다.
- 이클립스의 편집 창에 Object 라고 쓰고 F1 키를 누르면 JavaDoc 내용이 보입니다.
- 주로 사용하는 Object 메서드를 살펴보겠습니다.
| 메서드 | 설명 |
| String toString( ) | 객체의 정보를 문자열로 반환합니다. 재정의하여 객체에 대한 설명이나 멤버변수를 반환하도록 사용합니다. |
| boolean equals(Objectobj) | 객체 자신과 Object 가 동일한지 여부를 반환합니다. 재정의하여 논리적으로 동일한 인스턴스임을 정의할 수 있습니다. |
| int hasCode( ) | 객체의 해시코드(메모리에 저장된 인스턴스 주소값)를 반환합니다. |
| Object clone( ) | 객체를 복제하여 새로운 인스턴스를 생성합니다. |
| Class getClass( ) | 객체의 Class(설계 정보)를 반환합니다. |
| void finalize( ) | 인스턴스가 힙 메모리에서 제거될 때 가비지 컬렉터(GC)에 의해 호출되는 메서드입니다. 네트워크 연결 해제, 열려 있는 파일 스트림 해제 등을 구현합니다. |
| void wait( ) | 멀티스레드 프로그램에서 사용하는 메서드입니다. 스레드를 '기다리는 상태'(non runnable)로 만듭니다. |
| void notify( ) | wait( ) 메서드에 의해 기다리고 있는 스레드(non runnable 상태)를 실행 가능한 상태(runnable)로 가져옵니다. |
- 주로 사용하는 Object 메서드를 하나씩 살펴보도록 하겠습니다.
toString( ) 메서드
- Object 클래스에서 기본으로 제공하는 toString( ) 메서드는 객체 정보를 문자열(String)으로 바꾸어 반환합니다.
Object 클래스의 toString( ) 메서드
- Object 클래스의 toString 메소드 원형은 인스턴스 주소값을 반환합니다.
- toString 메서드의 원형은 다음과 같습니다.
getClass( ).getName( ) + '@' + Integer.toHexString(hashCode( ))
- 위 코드를 보면 '클래스 이름@해시코드값' 임을 알 수 있습니다.
- 즉 클래스 이름과 16진수 해시 코드 값이 출력됩니다.
String 과 Integer 클래스의 toString( ) 메서드
- 자연스럽게 Object 클래스를 상속받은 모든 클래스는 toString( ) 메서드를 재정의할 수 있으며, String 이나 Integer 등 여러 클래스에는 toString( ) 메서드가 이미 재정의 되어 있습니다.
- 이런 이유로 toString( ) 메서드가 호출된 경우라도 출력 결과가 '클래스 이름@해시 코드 값' 이 아닌 경우가 있습니다.
- 다음 코드로 보겠습니다.
String str = new String("test");
System.out.println(str);
Integer i1 = new Integer(100);
System.out.println(i1);

- String 과 Integer 클래스로 인스턴스를 생성하여 System.out.println( ) 출력문에 참조 변수로 넣으면 String 클래스는 문자열 값이, Integer 클래스는 정수 값 100이 출력됩니다.
- 값이 이렇게 출력되는 이유는 String 과 Integer 클래스에서 toString( ) 메서드를 재정의했기 때문입니다.
- 이런 toString( ) 메서드는 우리가 프로그램을 개발하면서 필요에 따라서 원하는 대로 재정의하여 사용하게 됩니다.
equals( ) 메서드
- equals( ) 메서드의 원래 기능은 두 인스턴스의 주소 값을 비교하여 boolean 값을 반환해 주는 것입니다.
- 주소 값이 같다면 당연히 같은 인스턴스입니다.
- 그런데 서로 다른 주소 값을 가질 때도 같은 인스턴스라고 정의할 수 있는 경우가 있습니다.
- 물리적 동일성(인스턴스의 메모리 주소가 같음) 뿐 아니라 논리적 동일성(논리적으로 두 인스턴스가 같음)을 구현할 때 equals( ) 메서드를 재정의하여 사용합니다.
- 무슨 말인지 정말 이해하기가 어려운데 예제를 보면서 쉽게 이해해보겠습니다.
Object 클래스의 equals( ) 메서드
- 생성된 두 인스턴스가 '같다'는 것은 무엇을 의미할까요?
- 인스턴스를 가리키는 참조 변수가 두 개 있을 때 이 두 인스턴스가 물리적으로 같다는 것은, 두 인스턴스의 주소 값이 같은 경우를 말합니다.
- 다시 말해 두 변수가 같은 메로리 주소를 가리키고 있다는 뜻입니다.
- 다음 코드를 보겠습니다.
Student studentLee = new Student(100, "이상원");
Student studentLee2 = studentLee;
- Student 클래스를 생성하고, 생성된 인스턴스를 가리키는 참조 변수를 다른 변수에 복사합니다.
- 그러면 두 변수는 스택 메모리에 생성된 상태에서 힙 메모리에 생성된 동일한 인스턴스의 주소 값을 가지게 됩니다.
- 그런데 다음과 같은 경우가 있을 수 있습니다.

- 맨 처음 만들었던 인스턴스가 가지는 변수의 값과 정확히 동일한 값을 가지는 인스턴스를 생성합니다.
- student2 라는 인스턴스는 student1 이라는 인스턴스와 같은 내용을 가지고 있으므로 같은 인스턴스라고 우리는 논리적으로 보이지만 물리적으로는 전혀 다른 힙 메모리를 가리키고 있기 때문에 물리적으로는 다른 인스턴스입니다.
- 이런 경우에는 논리적으로 같은 학생으로 처리하는 것이 맞을 겁니다.
- 예제를 통해서 이해해봅시다.
class Student {
int studentId;
String studentName;
public Student(int studentId, String studentName) {
this.studentId = studentId;
this.studentName = studentName;
}
public String toString( ) {
return studentId + "," + studentName;
}
}
public class StudentTest {
public static void main(String[] args) {
Student studentLee = new Student(100, "이상원");
Student studentLee2 = studentLee;
Student studentSang = new Student(100, "이상원");
// 동일한 주소의 두 인스턴스 비교
if (studentLee == studentLee2) {
System.out.println("studentLee 와 studentLee2 의 주소는 같습니다.");
} else {
System.out.println("studentLee 와 studentLee2 의 주소는 같지 않습니다.");
}
if (studentLee.equals(studentLee2))
System.out.println("studentLee 와 studentLee2 의 주소는 같습니다.");
else
System.out.println("studentLee 와 studentLee2 의 주소는 같지 않습니다.");
// 동일인이지만 인스턴스의 주소가 다른 경우
if (studentLee == studentSang) {
System.out.println("studentLee 와 studentSang 의 주소는 같습니다.");
} else {
System.out.println("studentLee 와 studentSang 의 주소는 같지 않습니다.");
}
if (studentLee.equals(studentSang))
System.out.println("studentLee 와 studentSang 의 주소는 같습니다.");
else
System.out.println("studentLee 와 studentSang 의 주소는 같지 않습니다.");
}
}
- 위의 코드의 출력값은 다음과 같습니다.

- 출력값의 결과가 이렇게 나온 이유가 무엇일까요?
studentLee == studentLee2
- 우선 위 코드에서 좌우항은 참조 변수입니다.
- 참조 변수는 생성된 인스턴스가 힙 메모리 어디에 위치하고 있는지 참조변수값을 가지고 있습니다.
- 따라서 좌 우항 참조 변수들이 같은 참조변수값을 가지고 있기 때문에 == 관계 연산자를 통해 true 가 반환되거나 false 가 반환됩니다.
studentLee.equals(studentLee2)
- 이 코드의 동작을 이해하기 위해서는 Object 의 equals( ) 메서드의 원래 기능은 무엇이었는지 떠올려보면 이해가 갑니다.
- Object 의 equals( ) 메서드의 원래 기능은 두 인스턴스의 주소를 비교하는 것입니다.
- equals( ) 메서드의 원래 코드를 보면 다음과 같습니다.
public boolean equals(Object obj) {
return (this == obj);
}
- 예약어 this 는 자기 자신의 메모리를 가리키고, 매개변수 obj 는 인자로 참조 변수를 받아오니까 참조변수값을 가지고 있습니다. 이 둘을 비교하는 것이니 두 인스턴스의 주소를 비교하는 것이라고 말할 수 있는 것입니다.
- 그런데, 여기서 우리가 생각해야 하는 것이 하나 있습니다.
- studentLee 와 studentSang 참조 변수들은 서로 참조 변수 값은 다르지만 같은 내용을 갖고 있습니다.
- 인스턴스 주소가 다르더라도 학번이 같으면 사실 같은 학생의 정보인 것이죠.
- 따라서 인스턴스의 주소가 달라도 동일한 객체임을 확인할 수 있어야 합니다.
- 즉 두 인스턴스가 있을 때 == 관계 연산자는 단순히 물리적 메모리 주소가 같은지 파악하는데 사용할 수 있습니다.
- 그런데 우리가 필요한 것은 논리적 동일성을 확인하는 것이기 때문에 Object의 equals( ) 메서드를 재정의하여 논리적으로 같은 인스턴스인지 확인하도록 구현할 수 있습니다.
String과 Integer 클래스의 equals( ) 메서드
- JDK 에서 제공하는 String 클래스와 Integer 클래스에는 equals( ) 메서드가 이미 재정의되어 있습니다.
- 재정의된 equals( ) 메서드를 사용하는 예제를 보면서 이해해보겠습니다.
public class StringEquals {
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2); // 관계연산자를 활용하여 메모리 주소 값이 같은지 비교
System.out.println(str1.equals(str2)); // String 클래스에서 재정의된 equals 를 활용하여 문자열 값이 같은지 비교.
Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
System.out.println(i1 ==i2); // 관계연산자를 활용하여 메모리 주소 값이 같은지 비교
System.out.println(i1.equals(i2)); // Integer 클래스에서 재정의된 equals 를 활용하여 인스턴스 정수값이 같은지 비교.
}
}
- String 과 Integer 클래스에서 재정의된 equals( ) 메서드는 object 클래스에서 정의된 equals( ) 메서드와는 다르게 메모리 주소 값을 비교하는 것이 아니라 문자열과 정수값이 실제로 같은지 논리적 동일성을 확인합니다.
Student 클래스에서 equals( ) 메서드 직접 재정의하기
- 우리가 만든 Student 클래스에서 학생들의 논리적 동일성을 확인하기 위해서 equals( ) 메서드를 어떤 식으로 재정의해야할까요?
- 학교에서 논리적으로 같은 학생이라고 말할 수 있는 것은 학번이 같은 경우를 의미합니다.
- 즉 우리는 학생의 학번이 같은 경우 논리적으로 같은 학생이라고 판단할 수 있는 equals( ) 메서드를 재정의할 수 있습니다.
public class Student {
// ...
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student std = (Student)obj;
if (this.studentId == std.studentId)
return true;
else return false;
}
return false;
}
}
- 재정의한 equals 메서드를 활용하면 물리적 데이터 동일성을 확인하지 않고, 논리적 동일성을 확인하는 것을 볼 수 있습니다.
hashCode( ) 메서드
- 해시(hash)는 정보를 저장하거나 검색할 때 사용하는 자료 구조입니다.
- 정보를 어디에 저장할 것인지, 어디서 가져올 것인지 해시 함수를 사용하여 구현합니다.
- 해시 함수(hashCode)는 객체의 특정 정보(키 값)를 매개변수 값으로 넣으면 그 객체가 저장되어야 할 위치나 저장된 해시 테이블 주소(위치)를 반환합니다.
- 따라서 객체 정보를 알면 해당 객체의 위치를 빠르게 검색할 수 있습니다.
- 이 해시 함수의 기본 형태는 다음과 같습니다
index = hash(key)
저장위치 = 해시함수(객체 정보)
- 이런 해시 함수는 개발하는 프로그램 특성에 따라 다르게 구현됩니다.
- 자바에서는 인스턴스를 힙 메모리에 생성하여 관리할 때 해시 알고리즘을 사용합니다.
- 무슨 말인지 이해하기가 어려운데요.
- Object 클래스의 toString( ) 메서드 원형을 다시 살펴보겠습니다.
getClass( ).getName( ) + '@' + Integer.toHexString(hashCode( ))
- 이것이 의미하는 바는 우리가 참조 변수의 값을 출력했을 때 본 16진수 숫자 값이 '해시 코드 값'이고, 이 값은 자바 가상 머신이 힙 메모리에 저장한 '인스턴스의 주소 값'이라는 의미입니다.
- 즉 자바에서는 두 인스턴스가 같다면 hashCode( ) 메서드에서 반환하는 해시 코드 값이 같아야 합니다. (왜냐하면 힙 메모리에 저장한 인스턴스 주소 값이 바로 hasCode 메서드의 반환 값이었으니까)
- 이해하기가 매우 어렵습니다. 추가적으로 찾아본 내용에 의하면 다음과 같습니다. (틀린 내용이 있다면 후에 수정하러 오겠습니다.)
기본적으로 equals method 는 두 객체가 논리적(컨텐츠가)으로 같은지 판단합니다.
그런데, equals 메서드가 작동할 때 hashCode( ) 메서드가 객체를 위한 특별한 숫자값을 주기 위해 함께 연관되어
작동합니다.
여기서 말하는 특별한 숫자값이라는 것은 해시(hash)의 기본 역할인 특정 객체가 저장된 해시 테이블주소(위치)를 말합니다.
그렇기 때문에 equals 메서드를 필요에 따라 수정했는데, 그에 맞추어서 hashCode( ) 메서드를 수정하지
않으면 hash-based collections 인 상황 속에서는 두 객체가 논리적으로 같은 것이라고 판단되더라도
다른 hash code 를 가지기 때문에 틀린 위치에 저장될 수 있습니다.
이 말은 결국 내가 찾고자 하는 객체를 나중에 다시 못 찾게 될 수 있음을 의미합니다.
- 결국 논리적으로 같은 두 객체가 있다면 equals 메서드를 재정의하는 것뿐만 아니라 두 객체가 같은 해시 코드 값을 반환하도록 hashCode( ) 메서드를 재정의해야한다는 것을 의미합니다.
- 정리하자면, equals( ) 메서드를 재정의했다면 hashCode( ) 메서드도 재정의해야 합니다.
String 과 Integer 클래스의 hashCode( ) 메서드
- String 클래스와 Integer 클래스의 equals( ) 메서드는 재정의되어 있다고 했습니다.
- 그말은 무조건적으로 hashCode( ) 메서드도 함께 재정의되어 있다는 것을 의미합니다.
- 예제를 통해서 살펴보겠습니다.
public class HashCodeTest {
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
System.out.println(i1.hashCode());
System.out.println(i2.hashCode());
}
}

- 이 코드와 출력값이 의미하는 바는 다음과 같습니다.
- String 클래스의 인스턴스인 두 객체가 같은 문자열을 가진 경우에 (다른 말로 하자면 String 클래스에서 재정의된 equals 메서드를 통한 비교 결과 값이 true 인 경우에) hashCode( ) 메서드는 동일한 해시 코드 값을 반환합니다.
- Integer 클래스의 hashCode( ) 메서드는 정수 값을 그대로 반환하도록 재정의되어 있습니다.
Student 클래스에서 hashCode( ) 메서드 재정의하기
- 앞선 내용에서 서로 다른 인스턴스로 생성된 두 학새잉 논리적으로 같은 학생이라는 것을 파악하기 위해 equals( ) 메서드를 재정의하였습니다.
- 이제 equals( ) 메서드를 수정하면 무엇도 같이 수정해야하죠?
- 네, 바로 해시 코드 메서드 hashCode( ) 메서드도 재정의해야합니다.
public class Student {
// ...
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student std = (Student)obj;
if (this.studentId == std.studentId)
return true;
else return false;
}
return false;
}
@Override
public int hashCode() {
return studentId;
}
}
- 여기서 궁금한 점이 한 가지 있습니다.
- Student 클래스에서 hashCode( ) 메서드를 재정의할 때 어떤 값을 반환하도록 만드는 것이 가장 합리적일까요?
- 논리적으로 같은 학생인지 비교하는 equals( ) 를 재정의할 때 학번이 같으면 true 를 반환하였습니다.
- 그렇기 때문에 hashCode( ) 메서드를 재정의할 때는 equals( ) 메서드에서 논리적으로 같다는 것을 구현할 때 사용한 멤버 변수를 활용하는 것이 좋습니다.
- 따라서 Student 클래스에서는 equals( ) 메서드에서 논리적 동일성을 구현하기 위해 사용한 멤버 변수인 StudentId 를 hashCode( ) 메서드가 반환하는 것이 가장 합리적입니다.
clone( ) 메서드
- clone( ) 메서드는 다음과 같은 상황에서 유용하게 사용됩니다.
- 1. 객체 원본을 유지해 놓고 복사본을 사용하고 싶을 때
- 2. 기본 틀의 복사본을 사용해 동일한 인스턴스를 만들어 복잡한 생성 과정을 간단히 하고 싶을 때
- clone( ) 메서드는 Object 에 다음과 같이 선언되어 있습니다.
protected Object clone()
- clone( ) 메서드는 객체를 복제해 또 다른 객체를 반환해 주는 메서드입니다.
- 예제를 통해서 객체가 복제되는 과정을 이해해보겠습니다.
class Point {
int x;
int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "x = " + x + "," + "y = " + y;
}
}
class Circle implements Cloneable { // 객체를 복제해도 된다는 의미로 Cloneable 인터페이스를 구현함
Point point;
int radius;
public Circle(int x, int y, int radius) {
super();
this.radius = radius;
point = new Point(x, y);
}
@Override
public String toString() {
return "원점은 " + point + "이고," + "반지름은 " + radius + "입니다.";
}
@Override
protected Object clone() throws CloneNotSupportedException { // clone( ) 메서드를 사용할 때 발생할 수 있는 오류를 예외 처리함
return super.clone();
}
}
public class ObjectCloneTest {
public static void main(String[] args) throws CloneNotSupportedException { // clone( ) 메서드를 사용할 때 발생할 수 있는 오류를 예외 처리함
Circle circle = new Circle(10, 20, 30);
Circle copyCircle = (Circle)circle.clone();
System.out.println(circle);
System.out.println(copyCircle);
System.out.println(System.identityHashCode(circle));
System.out.println(System.identityHashCode(copyCircle));
}
}

- 위 코드에서 유의해서 보아야할 것이 몇 가지 있습니다.
- 1. clone( ) 메서드를 해당 클래스 인스턴스에서 사용가능하도록 만들기 위해서는 객체를 복제해도 된다는 의미로 클래스에 Cloneable 인터페이스를 구현해야합니다. (Cleneable 인터페이스를 명시하지 않으면 clone() 메서드를 호출할 때 CloneNotSupportedExceoption 이 발생합니다)
- 2. 위 코드에서 clone( ) 메서드는 Object 의 clone( ) 메서드를 그대로 사용하고 있습니다. (Objdect 의 clone 메서드는 클래스의 인스턴스를 새로 복제하여 생성해 줍니다.)
- 복제된 값과 기존의 인스턴스를 출력하여 비교해보면 멤버 변수가 동일한 인스턴스가 다른 메모리에 새로 생성되는 것을 볼 수 있습니다.