...
Object 클래스
모든 자바의 클래스는 Object 클래스로 부터 시작된다.
즉, Object 클래스가 모든 클래스의 조상 혹은 base 라고 할 수 있다.
사실 우리가 클래스 파일을 만들어 클래스명을 작성하면 자동적으로 Object 클래스가 extends 가 된다.
우리가 클래스를 만들때 굳이 Object 클래스를 상속시키지 않아도 자동으로 상속해줘서 Object 클래스가 지원하는 메서드를 자유롭게 사용이 가능한 이유이다.
만일 다른 클래스를 상속시키면 당연히 클래스는 한개밖에 상속할수 없기에 extends Object는 사라지지만, 부모 클래스에서 Object를 상속하고 있기에 결국 모든 클래스는 Object 클래스를 상속 받고 있는 형태가 된다.
// extends Object 를 안써줘도 컴파일러가 알아서 상속시켜주고 컴파일 해준다.
class MyObject {
// 아무것도 안쓴다.
}
public class Main {
public static void main(String args[]) {
MyObject o = new MyObject();
// MyObject 클래스에 아래의 메소드를 명시하지 않아도 동작하는 이유는 기본적으로 Object 클래스를 상속하고 있기에 부모의 메서드를 갖다 쓰는 것이다.
o.toString();
o.hashCode();
}
}
Object 클래스 메서드 종류
Object 클래스의 역할은 주로 운영체제와 자바가상머신의 사이의 관리를 담당한다.
이러한 Object 클래스는 필드를 가지지 않으며, 총 11개의 메소드만으로 구성되어 있다.
Object 클래스의 메서드 | 설 명 |
protected Object clone( ) | 객체 자신의 복사본을 반환한다. |
public boolean equals(Object obj) | 객체 자신과 객체 obj가 같은 객체인지 알려준다 (같으면 true) |
protected void finalize( ) | 객체가 소멸될 때 가비지 컬렉터에 의해 자동적으로 호출되어 객체가 소멸되기 직전에 데이터들을 반납한다. 그래서 이 메서드를 오버라이딩 해서 데이터를 청소해 주어야 겠지만 하지만 요즘은 다른방법으로 청소하여 안쓰인다 (deprecated) |
public Class getClass( ) | 객체 자신의 클래스 정보(설계도)를 담고있는 Class 인스턴스를 반환 다른 곳에서 해당 객체의 정보를 가지고 객체를 재생성 해야 할때 이용됨 |
public int hashCode( ) | 객체 자신의 해시코드를 반환 ※ 해시코드는 메모리 주소를 int형으로 변환한 값 |
public String toString( ) | 객체 자신의 정보를 문자열로 반환 |
public void notify( ) | (쓰레드용 메서드) 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다. |
public void notifyAll( ) | (쓰레드용 메서드) 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다. |
public void wait( ) public void wait(long timout) public void wait(long timeout, int nanos) |
(쓰레드용 메서드) 른 쓰레드가 notify() 나 notifyAll() 을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간(timeout, nanos)동안 기다리게 한다.(timeout은 1/1000초, nanos는 1/(10^9)초) |
이중에서 따로 재정의하여 빈번하게 쓰이는 메서드는 clone() , equals() , hashCode() , toString() 4가지 정도가 되겠다.
Objects 클래스
Object 클래스가 아닌, Objects 클래스임을 조심하자.
Objects 클래스는 java.util 패키지에 있는 또다른 클래스이다. (Object 클래스는 java.lang 패키지에 포함)
Array 배열 클래스가 있고 또 Arrays 클래스가 있듯이 똑같이 생각하면 된다.
Objects 클래스는 객체 비교, 해시 코드 생성, null 여부, 객체 문자열 리턴 등의 연산을 수행하는 정적 메소드들로 구성되어 있으며, 개발자가 가져가 쓰기 편하게 하기 위해서 구현되었다고 보면 된다.
Object 클래스 메서드 재정의
이제부터 위에서 소개한 몇몇 Object 클래스의 메서드에 대해 깊이 익혀보는 시간을 가져볼 것이다.
자바에선 자동으로 Object 클래스를 extends 하기 때문에 문제 없이 Object의 메서드를 사용할수 있는데 굳이 오버라이딩을 시켜 사용하는 가에 대해 아마 의아함을 가질 것이다.
사실 자바에서 미리 만들어 제공하는 ArrayList, Map, String 클래스만 사용한다면 굳이 Object 클래스 메서드에 대해서 깊이 공부할 필요는 없을지도 모른다.
하지만 Object 클래스를 활용해야 할때는 자신만의 데이터 자료형을 구성할때 필요하다.
예를들어 유저 프로필 객체를 저장하고 사용하기위해 멤버로 name, age, phone 등 다양한 정보를 가지고 있는 User 클래스를 만들고, 이 클래스를 이용해 데이터를 다루게 될때, 반드시 Object 클래스의 메서드를 재정의하여 활용해야 하는 경우가 생기기 때문이다.
toString 메소드
- 기본적으로 Object 클래스의
toString()메소드는 해당 인스턴스에 대한 정보와 주소(해시코드)를 문자열로 반환한다.Object@251a69d7 - 따라서 객체의 이름이나 주소값이 아닌 객체의 고유 정보를 출력하고 싶을 때 toString 메서드를 재정의하여 반환값을 다르게 설정해주면 된다.
- 기본적으로 객체를 출력(println) 할때 변수에
toString()을 호출하지 않아도 자동으로 붙여 호출한다. - 이 메서드는 인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의되어 있는 것이다.
- 이때 반환되는 문자열은 클래스 이름과 함께 구분자로 @가 사용되며, 그 뒤로 16진수 해시 코드(hash code)가 추가된다. 해시 코드 값은 인스턴스의 주소를 해싱하여 변환한 값으로, 고유 숫자로서 인스턴스마다 모두 다르게 반환된다.
- 실제로
toString()메서드 내부를 본다면 다음과 같이 구현되어있다.
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// @Overriding : 오버라이딩은 메서드 시그니처가 일치해야 한다. 블록 안의 내용만 재정의 하는 것이다.
public String toString() {
return String.format("이름 : %s, 나이 : %d세", this.name, this.age);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("홍길동", 54);
// p1 객체를 출력하면 이름과 나이가 출력
System.out.println(p1); // 이름 : 홍길동, 나이 : 54세
}
}
equals / hashCode 메서드
- 위의
toString()메서드 다음으로 가장 많이 오버라이딩 되어 쓰이는 놈들이다. equals메서드는String타입의 데이터 비교에서 한번 본 것 처럼, 두 객체 간의 동등 비교할때 쓰인다.- 다만 디폴트로는 객체의 주소값으로 동등 비교를 하기 때문에, 객체의 동등 비교 기준을 변경하기 위해서는
equals메서드를 오버라이딩 하여 동등 비교 로직을 재정의 할 필요가 있다. - 또한
hashCode도 디폴트로는 객체의 주소값을 해시코드로 반환해주는데, 객체의 주소가 아닌 다른 데이터의 주소를 반환하기 위해선 역시 오버라이딩 하여 재정의 해주어야 한다.
class Person {
public String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return Objects.equals(name, p.name);
}
@Override
public int hashCode() {
return Objects.hash(name); // name 필드의 해시코드를 반환한다.
}
}
public class ClassTest {
public static void main(String[] args) throws Exception {
Person p1 = new Person("홍길동");
Person p2 = new Person("홍길동");
// 두 객체의 해시 코드
System.out.println(p1.hashCode()); // 54150093
System.out.println(p2.hashCode()); // 54150093
// 해시코드가 달라도, equals를 재정의 했기 때문에 동등함
System.out.println(p1.equals(p2)); // true
// SET를 생성하고 두 객체 데이터를 추가한다.
Set<Person> people = new HashSet<>();
people.add(p1);
people.add(p2);
// 그리고 SET의 길이를 출력한다.
System.out.println(people.size()); // 1
}
}
clone 메소드
- 객체를 깊은 복사할때 사용되는 메소드이다.
- Object 클래스의
clone()메소드는 기본으로 protected 접근 권한을 갖고 있기 때문에, 반드시 메소드를 public 접근제어자로 오버라이딩하여 어디서나 복제가 가능하도록 해야 한다.
// 객체 복사 메소드를 사용하기 위해서는 Cloneable 인터페이스를 구현해서 clone을 재정의 해야함
class User implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) {
try {
// 얕은 복사(shallow copy)
User user = new User();
user.setName("Edward");
User copy = user;
System.out.println(user.hashCode()); // 622488023
System.out.println(copy.hashCode()); // 622488023
System.out.println(user.equals(copy)); // true - 둘이 동인할 힙데이터를 바라보고 있기 때문에
// 깊은 복사(deep copy)
User user2 = new User();
user2.setName("Edward");
User copy2 = (User) user2.clone();
System.out.println(user2.hashCode()); // 1933863327
System.out.println(copy2.hashCode()); // 112810359
System.out.println(user2.equals(copy2)); // false - 둘은 복사되어 생김새만 같지 다른 힙데이터를 바라보고 있기 때문에
} catch(CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
getClass 메소드
- 어떠한 클래스 파일의 클래스 정보를 갖고 있는 Class 클래스 객체를 반환하는 메서드이다.
- 여기서 Class 클래스란, 클래스 이름이 Class인 클래스를 말하는 것이다.
- 이 Class 클래스 객체를 이용하여 Reflection API 기법을 통해, 구체적인 클래스 타입을 모르더라도 그 클래스의 정보(메소드, 타입, 변수, ...)에 접근할수 있게 된다.
public class Main {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, ClassNotFoundException {
String str = "Class클래스 테스트"; // String 클래스
Class<? extends String> cls = str.getClass(); // String 객체로부터 클래스 정보를 얻는다
// 클래스의 이름만 호출한다.
System.out.println("1. " + cls.getSimpleName()); // 1. String
// 패키지의 이름을 호출한다.
System.out.println("2. " + cls.getPackage()); // 2. package java.lang
// 패키지와 이름을 호출한다.
System.out.println("3. " + cls.getName()); // 3. java.lang.String
// 클래스와 패키지 이름을 호출한다.
System.out.println("4. " + cls.toString()); // 4. class java.lang.String
// 제어자부터 패키지 이름 모두다 호출한다.
System.out.println("5. " + cls.toGenericString()); // 5. public final class java.lang.String
}
}
# 참고자료
https://www.youtube.com/watch?v=Mc6OaicCZVA
https://www.youtube.com/watch?v=NI6QZy6juc8
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.