Java 문법 (14) - Object 클래스
Java 문법 (14) - Object 클래스
java.lang 패키지
자바가 기본으로 제공하는 라이브러리 중에 가장 기본이 되는 것이 java.lang
패키지 입니다. 자바 언어를 이루는 가장 기본이 되는 클래스들을 보관하는 패키지입니다. java.lang
패키지는 모든 자바 애플리케이션에 자동으로 임포트 되기 때문에 생략해도 됩니다.
java.lang 패캐지의 대표적인 클래스들
- Object: 모든 자바 객체의 부모 클래스
- String: 문자열
- Integer, Long, Double: 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것
- Class: 클래스 메타 정보
- System: 시스템과 관련된 기본 기능들을 제공
Object 클래스
Object 예시
자바에서 모든 클래스의 최상위 부모 클래스는 항상 Object
클래스입니다. 클래스에 상속 받을 부모 클래스가 없으면 묵시적으로 Object 클래스를 상속받게 됩니다.
1
2
3
4
5
6
7
8
9
// Parent.java
// 부모가 없으면 묵시적으로 Object 클래스를 상속받는다.
// extends Object가 생략되어 있음
public class Parent {
public void parentMethod(){
System.out.println("Parent.parentMethod");
}
}
1
2
3
4
5
6
7
8
9
// Child.java
// extends로 상속받는게 있으면 Object를 직접 상속받지 않음
public class Child extends Parent {
public void childMethod(){
System.out.println("Child.childMethod");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// ObjectMain.java
public class ObjectMain {
public static void main(String[] args) {
Child child = new Child();
child.parentMethod();
child.childMethod();
// toString()은 Object 클래스의 메서드
String string = child.toString();
System.out.println(string);
}
}
Child는 Parent를 상속받고 Parent는 상속받는 코드가 없기 때문에 자동으로 Object를 상속받게 됩니다. Child -> Parent -> Object
Child의 객체 child는 자신의 메서드인 childMethod()
를 직접 호출 가능하고, 부모의 메서드 parentMethod()
도 호출 가능하며, 조부모인 Object의 toString()
메서드 또한 호출 가능합니다.
Object가 최상위 부모 클래스인 이유
- 공통 기능 제공
- 객체의 정보를 제공 toString()
- 객체의 같음을 비교 equals()
- 객체의 클래스 정보를 제공 getClass()
위와 같은 기능들은 모든 객체에게 필요한 기본 기능입니다. 이런 기능을 객체를 만들 때 마다 항상 새로운 메서드를 정의해야 한다면 상당히 번거로울 것입니다. 만든다고 해도 개발자마다 서로 다른 이름의 메서드를 만들게 될 것이고, 그렇다면 일관성이 없어져 호환성도 떨어집니다.
다형성의 기본 구현
Object는 모든 클래스의 부모 클래스이기 때문에 모든 객체를 참조할 수 있습니다. 이를 통해 Object 클래스는 다형성을 지원하는 기본적인 메커니즘을 제공합니다. 모든 자바 객체는 Object 타입으로 처리될 수 있고, 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 해줍니다. 예를들어 타입이 다른 객체들을 어딘가 한 곳에 보관해야 한다면 Object 배열에 보관할 수 있습니다.
Object 다형성 예제
1
2
3
4
5
6
//Car.java
public class Car {
public void move(){
System.out.println("자동차 이동");
}
}
1
2
3
4
5
6
// Dog.java
public class Dog {
public void sound(){
System.out.println("멍멍");
}
}
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
ObjectPolyExample.java
public class ObjectPolyExample {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
// Object는 모든 객체의 부모이기 때문에 담을 수 있다.
action(dog);
action(car);
// 오브젝트 배열에 자바의 모든 객체를 담을 수 있다
Object[] objects = {dog, car, object};
size(objects);
}
private static void action(Object obj){
// obj.move(); 컴파일 오류 Object는 move()가 없음
// obj.sound(); 컴파일 오류 Object는 sound()가 없음
// 객체에 맞는 다운 캐스팅이 필요
if (obj instanceof Dog dog) {
dog.sound();
} else if (obj instanceof Car car) {
car.move();
}
}
// Obbject 타입만 사용하는 메서드이기 때문에 자바를 사용하는 곳이라면 어디든지 사용될 수 있다.
private static void size(Object[] objects) {
System.out.println("전달된 객체의 수는: " + objects.length);
}
}
서로 전혀 관계가 없는 Car와 Dog클래스입니다. 둘 다 부모 클래스가 없기 때문에 Object를 자동으로 상속 받습니다.
- Object는 모든 객체를 담을 수 있어서 함수 및 배열에 다른 타입의 객체들을 전부 담을 수 있습니다.
- 하지만 Object는 자식의 메서드를 알고 있지 않기 때문에, 자식 메서드를 호출하려면 다운 캐스팅을 해야합니다.
toString()
Object.toString()
메서드는 객체의 정보를 문자열 형태로 제공합니다. 그래서 디버깅과 로깅에 유용하게 사용되는 메서드입니다. 이 메서드는 Object 클래스에 정의되므로 모든 클래스에서 상속받아 사용할 수 있습니다.
1
2
3
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString() 메서드는 객체의 이름과 객체의 참조값(해시코드)를 16진수로 return합니다. System.out.println 과 출력 결과가 완전히 같은데, 이는 println 내부에서 toString을 호출하기 때문입니다.
toString() 오버라이딩
Object.toString()
메서드가 클래스 정보와 참조값을 제공하지만 이 정보만으로 객체의 상태를 적절히 표현하지는 못합니다. 그래서 보통 toString()을 오버라이딩해서 적절한 정보를 제공하는 것이 일반적입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Dog.java
public class Dog {
private String dogName;
private int age;
public Dog(String dogName, int age) {
this.dogName = dogName;
this.age = age;
}
// toString 오버라이딩
@Override
public String toString() {
return "Dog{" +
"dogName='" + dogName + '\'' +
", age=" + age +
'}';
}
}
1
2
3
4
5
6
7
8
9
//Car.java
public class Car {
// toString 오버라이딩 하지 않음
private String carName;
public Car(String carName) {
this.carName = carName;
}
}
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
// ToStringMain2.java
public class ToStringMain2 {
public static void main(String[] args) {
Car car = new Car("ModelY");
Dog dog = new Dog("멍멍이1", 2);
Dog dog2 = new Dog("멍멍이2", 5);
System.out.println("1. 단순 toString 호출");
System.out.println(car.toString()); // lang.object.tostring.Car@452b3a41
System.out.println(dog.toString()); //Dog{dogName='멍멍이1', age=2}
System.out.println(dog2.toString()); //Dog{dogName='멍멍이2', age=5}
System.out.println("2. println 내부에서 toString 호출");
System.out.println(car); //lang.object.tostring.Car@452b3a41
System.out.println(dog); //Dog{dogName='멍멍이1', age=2}
System.out.println(dog2); //Dog{dogName='멍멍이2', age=5}
// 오버라이딩을 하면 객체의 참조값을 출력할 수 없다. 다음 코드를 사용하면 참조값 출력 가능하다
// System.identityHashcode()
System.out.println(System.identityHashCode(dog));
String refValue = Integer.toHexString(System.identityHashCode(dog));
System.out.println(refValue);
System.out.println(Integer.toBinaryString(System.identityHashCode(dog)));
}
}
Dog에서 toString()을 오버라이딩한 예제입니다. toString()과 println의 결과가 똑같은데, 오버라이딩 된 함수가 잘 호출되는 것을 알 수 있습니다.
toStirng()을 오버라이딩 하게되면 원래 보여주던 참조값을 더 이상 출력할 수 없게됩니다. 참조값이 필요한 경우에는 System.identityHashcode()
코드를 사용해서 참조값을 가져올 수 있습니다.
equals()
동일성과 동등성
- 동일성(Identity):
==
연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인 - 동등성(Equality):
equals()
메서드를 사용하여 두 객체가 논리적으로 동등한지 확인 쉽게 생각하면 동일성은 완전히 같음, 자바에서는 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인하는 것이고, 동등성은 같은 가치나 수준, 보통 사람이 생각하는 논리적인 기준에 맞추어 비교합니다.
1
2
User a = new User("id-100")
User b = new User("id-100")
a와 b 유저가 있을 때, 서로 다른 메모리에 존재하지만, 회원 번호 기준으로 생각해보면 논리적으로 같은 회원입니다. 이런 경우 동일성은 다르지만, 동등성은 같은 경우가 됩니다.
Object.equals()
Object가 기본으로 제공하는 equals()는 ==
으로 동일성 비교를 합니다. 따라서 동등성 비교를 사용하고 싶다면 equals() 메서드를 오버라이딩 해야합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// User.java
public class User {
private String id;
public User(String id) {
this.id = id;
}
// equals 오버라이딩
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
User user = (User) object;
return Objects.equals(id, user.id);
}
}
1
2
3
4
5
6
7
8
9
10
11
// EqualsMain.java
public class EqualsMain {
public static void main(String[] args) {
User user1 = new User("id-100");
User user2 = new User("id-100");
System.out.println("identity = " + (user1 == user2)); // false
// 오버라이딩 된 equalds() 사용
System.out.println("equality = " + user1.equals(user2)); // true
}
}
이렇게 equals를 오버라이딩해서 사용하면 user1, user2의 동일성과 동등성을 비교할 수 있습니다.
equals() 메서드를 구현할 때 지켜야 하는 규칙
- 반사성: 객체는 자기 자신과 동등해야한다. (x.equals(x)는 항상 true)
- 대칭성: 두 객체가 서로에 대해 동일하다고 판단한다면, 이는 양방향으로 동일해야 한다. (x.equals(y)가 true이면, y.equals(x)도 true)
- 추이성: 만약 한 객체가 두 번째 객체와 동일하고, 두 번째 객체가 세 번째 객체와 동일하다면, 첫 번째 객체와 세번째 객체는 동일하다.
- 일관성: 두 객체의 상태가 변경되지 않는 한, equals() 메소드는 항상 같은 값을 반환해햐 한다.
- null에 대한 비교: 모든 객체는 null과 비교했을 때 false를 반환해야 한다.