본문 바로가기
카테고리 없음

기본 API1 - Object

by 낭만 코딩 2024. 9. 21.

1. java.lang 패키지

2. Object 클래스

3. equals() 메서드

4. hashCode() 메서드

5. toString() 메서드

 

 

 

 

1. java.lang 패키지

자바에서 제공하는 API 중 가장 많이 사용되는 클래스들로 자바 프로그램의 기본적인 클래스를 가지고 있는 패키지입니다. java.lang 패키지에 있는 클래스들은 import 없이 바로 사용할 수 있는 클래스들로 이루어져 있습니다. 앞에서 String클래스나 System클래스를 import 하지 않고 사용할 수 있었던 이유가 바로 이것 때문입니다. java.lang 패키지의 많은 클래스들 중에서 자주 사용하는 클래스들만 배워보도록 알아보도록 하겠습니다.

 

클래스 용도
Object 모든 클래스의 최상위 클래스
System 표준 입출력 관련 클래스
Class 클래스를 메모리에 로드할 때 사용
String 문자열
StringBuffer, StringBuilder 문자열을 저장하거나 처리할 때 사용
Math 수학관련 사용
Wrapper 기본자료형의 처리를 위한 클래스

 

 

2. Object 클래스

Object 클래스는 모든 클래스의 최상위 클래스입니다. 클래스를 정의할 때 다른 클래스를 상속받지 않으면 그 클래스는 Object를 상속받도록 컴파일러가 자동으로 추가해줍니다. 그래서 결국 모든 클래스의 가장 상위에 있는 클래스가 됩니다. 따라서 모든 클래스는 Object의 메서드를 사용할 수 있게 됩니다.

Object 클래스는 멤버 변수는 존재하지 않고, 11개의 메서드만 가지고 있습니다. 11개의 메서드는 아래와 같습니다.

메서드 설명
protected Object clone() 자신 객체의 복사한 객체 리턴
public Boolean equals(Object obj) 같은 객체인지 비교
protected void finalize() 객체가 소멸될 때 가비지 콜렉션에 의해 호출되는 메서드
public Class getClass() 자신 객체의 클래스 정보 리턴
public int hashCode() 자신 객체의 해쉬코드 리턴
public String toString() 자신 객체의 문자열 정보 리턴
public void notify() 자신 객체를 사용하는 스레드 하나를 깨움
public void notifyAll() 자신 객체를 사용하는 모든 스레드를 깨움
public void wait() 다른 스레드가 notify/notifyAll을 실행할때까지 대기
public void wait(long timeout) 다른 스레드가 notify/notifyAll을 실행할 때까지 timeout동안 대기
public void wait(long timeout, int nanos) 다른 스레드가 notify/notifyAll을 실행할 때까지 timeout, nano동안 대기

 

이 중 equals(), hashCode(), toString() 메서드는 재정의해서 사용하는 경우가 많으므로 아래에서 살펴보고, 나머지 스레드에 관련된 notify(), notifyAll(), wait() 메서드는 나중에 스레드 관련 포스트에서 다시 살펴 보도록 하겠습니다.

 

3. equals() 메서드

equals 메서드는 주로 객체를 비교하여 결과값을 boolean 값으로 리턴하는 역할을 합니다. 아래 코드는 실제 Object 클래스에 정의되어 있는 equals() 메서드입니다.

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

 

thisobj는 모두 참조변수로 비교하기 때문에 참조변수가 참조하고 있는 주소값으로 비교하게 됩니다. 예전에 배웠던 참조자료형의 비교연산입니다. 아래 예제로 다시 확인해보겠습니다.

package example;

public class EqualsEx {

	public static void main(String[] args) {
		Obj obj1 = new Obj(100);
		Obj obj2 = new Obj(100);
		
		if (obj1.equals(obj2)) {
			System.out.println("obj1 객체와 obj2 객체는 같음");
		} else {
			System.out.println("obj1 객체와 obj2 객체는 다름");
		}
		
		Obj obj3 = obj1;
		
		if (obj1.equals(obj3)) {
			System.out.println("obj1 객체와 obj3 객체는 같음");
		} else {
			System.out.println("obj1 객체와 obj3 객체는 다름");
		}
		
		ObjOverride objo1 = new ObjOverride(100);
		ObjOverride objo2 = new ObjOverride(100);
		
		if (objo1.equals(objo2)) {
			System.out.println("objo1 객체와 objo2 객체는 같음");
		} else {
			System.out.println("objo1 객체와 objo2 객체는 다름");
		}
	}
}

class Obj {
	int obj_var;
	
	Obj(int obj_var) {
		this.obj_var = obj_var;
	}
}

class ObjOverride {
	int obj_var;
	
	ObjOverride(int obj_var) {
		this.obj_var = obj_var;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof ObjOverride) {
			return true;
		} else {
			return false;
		}
	}
}

 

실행 결과

obj1 객체와 obj2 객체는 다름
obj1 객체와 obj3 객체는 같음
objo1 객체와 objo2 객체는 같음

 

Obj 클래스는와 ObjOverride 클래스는 멤버 변수 obj_var를 갖고 있고, 생성자를 통해 멤버변수에 값을 초기화하고 있습니다. ObjeOverride 클래스는 equals() 메서드를 오버라이드해서 매개변수 objObjOverride의 객체이면 true를 그렇지 않으면 false를 리턴합니다.

EqualsEx 클래스에서는 obj1obj2 객체를 생성자에 같은 매개변수값으로 넣어 각각 객체를 생성하고, equals() 메서드로 비교했는데 객체도 같고, 멤버 변수 값도 같지만, 참조하는 주소값이 다르기 때문에 결과값은 false가 됩니다.

그 아래에서는 obj3변수에 obj1을 대입했는데, 이때 주소값이 대입되기 때문에 obj1obj3은 같은 주소를 참조하는 변수가 됩니다. 그래서 equals() 메서드로 비교한 결과값은 true가 된것이죠.

이번엔 ObjOverride 객체를 두 개 생성했는데, equals() 메서드로 비교한 결과가 위 결과와는 다르게 true가 나왔습니다. ObjOverride 클래스에서 재정의한 equals() 메서드는 instanceof로 매개변수가 ObjOverride 타입의 객체이면 true를 리턴하고 있기 때문입니다.

 

우리가 자주 사용하는 String 클래스도 Object 클래스의 equals() 메서드를 재정의해서 사용하고 있습니다. 앞에서 문자열 값을 비교할 때는 == 를 사용하지 말고 equals() 메서드를 사용해서 비교하는 것이 좋다고 했었죠? String 클래스도 equals() 메서드를 재정의해서 객체를 비교하는 것이 아니라 저장하고 있는 문자열을 비교하도록 되어 있는 것입니다. 참고로 뒤에서 배울 Wrapper 클래스도 equals() 메서드가 재정의 되어 주소가 아닌 저장된 값으로 비교하도록 되어 있습니다.

따라서 메서드명이 같더라도 동일한 기능이라고 단정하지 말고, 해당 내용을 잘 살펴본 후 사용해야 합니다.

package example;

public class EqualsEx2 {

	public static void main(String[] args) {
		
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		if(str1 == str2) {
			System.out.println("str1 객체와 str2 객체는 같음");
		} else {
			System.out.println("str1 객체와 str2 객체는 다름");
		}
		
		if(str1.equals(str2)) {
			System.out.println("str1 문자열과 str2 문자열은 같음");
		} else {
			System.out.println("str1문자열과  str2 문자열은 다름");
		}

	}

}

 

실행 결과

str1 객체와 str2 객체는 다름
str1 문자열과 str2 문자열은 같음

 

 

4. hashCode() 메서드

hashCode 메서드는 JVM이 객체를 식별할 수 있는 정수값을 리턴합니다. 객체의 메모리 주소를 이용해 해쉬코드를 생성해서 리턴하는데, 두 개의 다른 객체인 경우 해쉬코드 값도 다릅니다.

생성된 객체의 해쉬코드는 프로그램이 실행될 때마다 할당되는 메모리 주소가 다르므로, 매번 다른 값을 갖게 되지만, 프로그램의 한번의 실행 중에는 같은 값을 유지합니다.

앞의 예제들과 같이 객체가 같은지 비교해야하는 경우 hashCode() 메서드도 같이 오버라이딩을 해줘야 합니다. 같은 객체인 경우 hashCode() 메서드의 결과값인 해쉬코드도 같아야 하기 때문입니다.

만약 hashCode() 메서드를 오버라이딩하지 않으면, 원래 Object 클래스에 정의된 대로 모든 객체가 서로 다른 해쉬코드값을 갖게 됩니다. 그래서 equals() 메서드와 hashCode() 메서드를 같이 오버라이딩 하는 경우가 많습니다.

package example;

public class HahsCodeEx {

	public static void main(String[] args) {
		
		String str1 = new String( "abc");
		String str2 = new String( "abc");
		System.out.println("str1.hashCode():"+str1.hashCode()) ;
		System.out.println("str2.hashCode():"+str2.hashCode()) ;
		System.out.println("System.identityHashCode(str1):"+
							System.identityHashCode(str1)) ;
		System.out.println("System.identityHashCode(str2):"+
							System.identityHashCode(str2)) ;

	}

}

 

실행 결과

str1.hashCode():96354
str2.hashCode():96354
System.identityHashCode(str1):495053715
System.identityHashCode(str2):1922154895

 

String 클래스의 hashCode() 메서드는 문자열 값이 같으면, 동일한 해쉬코드를 리턴하도록 재정의되어 있기 때문에, 문자열 값이 같으면 str1str2에 대한 hashCode() 메서드를 실행하면 항상 동일한 해쉬코드 값을 리턴합니다.

하지만 System.identityHashCode() 메서드는 Object 클래스의 hashCode() 메서드처럼 객체의 주소값으로 해쉬코드를 생성하므로 모든 객체에 대해 항상 다른 해쉬코드값을 리턴합니다.

 

이클립스에서 프로그램을 실행해보시면 실행할때마다 해쉬코드값이 다르게 출력됩니다. 그래서 str1str2변수가 해쉬코드는 같지만 서로 다른 객체인 것입니다.

package example;

public class HashCodeEx2 {

	public static void main(String[] args) {
		
		Hash v1 = new Hash(20);
		Hash v2 = new Hash(20);
		System.out.println(v1.hashCode());
		System.out.println(v2.hashCode());
		System.out.println ("v1 객체 진짜 해쉬값 :"+System.identityHashCode(v1));
		System.out.println ("v2 객체 진짜 해쉬값 :"+System.identityHashCode(v2));

	}

}

class Hash {
	int value;
	Hash(int value) {
		this.value = value;
	}
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Hash) {
			Hash v = (Hash)obj;
			return value == v.value;
		} else {
			return false;
		}
	}
	@Override
	public int hashCode() {
		return value;
	}
}

 

실행 결과

20
20
v1 객체 진짜 해쉬값 :1922154895
v2 객체 진짜 해쉬값 :883049899

 

 

5. toString() 메서드

Object 클래스의 메서드 중 toString() 메서드는 객체를 문자열화할 때 사용되는 메서드로, 콘솔에 객체를 직접 출력하는 경우 자동으로 toString() 메서드가 실행됩니다.

Object 클래스에 정의된 toString() 메서드의 리턴값은 클래스명@16진수의 해쉬코드 값입니다. 여러분이 생성한 클래스의 특정 정보를 출력하려면 toString() 메서드를 재정의해야 합니다.

아래는 Object 클래스에 정의된 toString() 메서드입니다.

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

 

위 코드에서 getClass() 메서드와 getName(), hashCode() 메서드는 Object 클래스의 메서드이기 때문에 객체 생성 없이 직접 실행할 수 있습니다.

package example;

public class ToStringEx {

	public static void main(String[] args) {
		Fruit f = new Fruit("사과", "빨강");
		System.out.println(f);
	}
}

class Fruit {
	String name;
	String color;
	public Fruit(String name, String color) {
		this.name = name;
		this.color = color;
	}
}

 

실행 결과

example.Fruit@7291c18f

 

System.out.println(f); Fruit 클래스의 객체 f를 출력하는 코드인데, f는 참조변수이므로 실제 실행된 명령문은  System.out.println(f.toString()); 입니다.

Fruit 클래스에는 toString() 메서드가 존재하지 않으므로, 상위 클래스인 Object 클래스의 toString() 메서드가 실행되어 클래스명@16진수 해쉬코드값으로 출력되었습니다. 이제 이 toString() 메서드를 재정의해서 실행해보도록 하겠습니다.

package example;

public class ToStringEx2 {

	public static void main(String[] args) {
		Fruit2 f = new Fruit2("사과", "빨강");
		System.out.println(f);
	}
}

class Fruit2 {
	String name;
	String color;
	public Fruit2(String name, String color) {
		this.name = name;
		this.color = color;
	}
	
	@Override
	public String toString() {
		return "과일 이름 : "+this.name+"\n과일 색상 : "+this.color;
	}
}

 

실행 결과

과일 이름 : 사과
과일 색상 : 빨강

 

Fruit2 클래스의 toString() 메서드는 재정의 되어, System.out.println(f); 라고 출력하면 재정의된 이 toString() 메서드가 실행됩니다. 이 재정의된 메서드의 접근제한자를 보면 public 으로 선언되었는데, Object 클래스의 toString() 메서드의 접근제한자도 public이기 때문입니다. 메서드를 재정의할 때 부모(상위) 클래스의 메서드의 접근제한자보다 같거나 더 넓어야 합니다. 그래서 Object 클래스의 toString() 메서드의 접근제한자 public 보다 더 넓거나 같은 접근제한자는 public 밖에 쓸 수가 없게 됩니다. 만약 이 접근제한자를 protectedprivate로 수정하면 에러가 발생하게 됩니다.

 

추가로 뒤에서 배울 Date 라는 클래스의 toString() 메서드도 출력해보겠습니다.

package example;

import java.util.Date;

public class ToStringDateEx {

	public static void main(String[] args) {
		
		Date now = new Date();
		System.out.println(now);

	}

}

 

실행 결과

Sat Sep 21 23:39:41 KST 2024

 

실행 결과는 현재 실행한 시점의 일자와 시간이 출력되겠죠? 이처럼 Date 클래스도 toString() 메서드가 저 형태로 문자열을 만들어 리턴하고 있다는 것을 알 수 있습니다.