java.lang 패키지
자바의 기존 제공 라이브러리(클래스 모음) 중 가장 기본이 되는 패키지(lang : Language의 줄임말)
java.lang 패키지의 대표 클래스
- Object : 모든 자바 객체의 부모 클래스
- String : 문자열
- Integer, Long, Double : 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것
- Class : 클래스 메타 정보
- System : 시스템과 관련된 기본 기능들을 제공
import 생략 가능
java.lang 패키지는 모든 자바 애플리케이션에 자동으로 임포트되므로 임포트 구문 사용을 하지 않아도 됨
Object 클래스
자바의 모든 클래스의 최상위 부모 클래스는 항상 Object 클래스임
클래스에 상속 받을 부모 클래스가 없을 시 묵시적으로 Object 클래스를 상속받고, 명시적으로 지정하면 Object를 상속받지 않음
- 묵시적(Implicit) : 개발자가 코드에 직접 기술하지 않아도 시스템 또는 컴파일러에 의해 자동으로 수행되는 것
- 명시적(Explicit) : 개발자가 코드에 직접 기술해서 작동하는 것
자바에서 Object 클래스가 최상위 부모 클래스인 이유?
모든 클래스가 Object 클래스를 상속 받는 이유
- 공통 기능 제공
- 다형성의 기본 구현
공통 기능 제공
객체 정보를 제공하고, 객체가 다른 객체와 같은지 비교하고 어떤 클래스로 만들어졌는지 확인하는 기능은 모든 객체에게 필요한 기본 기능이다.
Object는 모든 객체에 필요한 공통 기능을 제공하고, 최상위 부모 클래스이기 때문에 모든 객체는 공통 기능을 편리하게 제공(상속) 받을 수 있다.
Object가 제공하는 기능
- 객체의 정보를 제공하는 toString()
- 객체의 같음을 비교하는 equals()
- 객체의 클래스 정보를 제공하는 getClass()
- 기타 여러 가지 기능
다형성의 기본 구현
부모는 자식을 담을 수 있다. Object는 모든 클래스의 부모 클래스이기 때문에 모든 객체를 참조할 수 있다.
Object 클래스는 다형성을 제공하는 기본적인 메커니즘을 제공하며, 모든 자바 객체는 Object 타입으로 처리될 수 있으며, 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 해준다.
Object 다형성
Dog와 Car는 서로 아무 관련 없는 클래스이며, 둘 다 부모가 없으므로 Object를 자동으로 상속 받는다.
예제
package lang.object.poly;
public class ObjectPolyExample1 {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
action(dog);
action(car);
}
private static void action(Object obj) {
//obj.sound(); // 컴파일 오류, Object는 sound()가 없다.
//obj.move(); // 컴파일 오류, Object는 move()가 없다.
// 객체에 맞는 다운캐스팅 필요
if (obj instanceof Dog dog) {
dog.sound();
} else if (obj instanceof Car car) {
car.move();
}
}
}
Object 다형성의 장점
action(Object obj) 메서드는 Object 타입의 매개변수를 사용하고, Object는 모든 객체의 부모이기 때문에 어떤 객체든지 인자로 전달할 수 있다.
Object 다형성의 한계
action() 메서드에서 obj.sound() 호출 시 오류가 발생하는데, 매개 변수인 obj는 Object 타입이라 sound() 메서드가 없기 때문이다.
action에서 Dog 인스턴스의 sound()를 호출하려면 다운 캐스팅이 필요하다.
Object를 활용한 다형성의 한계
- Object는 모든 객체를 대상으로 다형적 참조를 할 수 있다.
- Object를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운 캐스팅 과정이 필요하다.
다형성을 제대로 활용하기 위해서는 다형적 참조 + 메서드 오버라이딩을 함께 사용해야 되는데, Object를 사용한 다형성에는 한계가 있다.
Object는 모든 객체를 대상으로 다형적 참조를 할 수 있지만 다른 객체의 메서드가 정의되어 있지 않아서 메서드 오버라이딩을 활용할 수 없다(각 객체의 기능을 호출하기 위해서는 다운 캐스팅을 해야 함).
결과적으로 Object는 다형적 참조는 가능하지만, 메서드 오버라이딩이 안되기 때문에 다형성을 활용하는데 한계가 있다.
toString()
Object.toString() 메서드는 객체의 정보를 문자열 형태로 제공하여 디버깅과 로깅에 유용하게 사용된다.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- Object가 제공하는 toString() 메서드는 기본적으로 패키지를 포함한 객체의 이름과 객체의 참조값(해시 코드)를 16진수로 제공한다.
println() 메서드는 내부에서 toString()을 호출하기 때문에 Object 타입을 println()을 사용해서 출력하는 것과 toString()을 사용하는 것과 출력 결과가 같다.
toString() 오버라이딩
Object.toString() 메서드는 클래스 정보와 참조값을 제공하지만 이 정보만으로는 객체의 상태를 적절히 나타내지 못한다. 따라서 toString()을 재정의(오버라이딩)해서 보다 유용한 정보를 제공하는 것이 일반적임
예제
public class Car {
private String carName;
public Car(String carName) {
this.carName = carName;
}
}
public class Dog {
private String dogName;
private int age;
public Dog(String dogName, int age) {
this.dogName = dogName;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"dogName='" + dogName + '\'' +
", age=" + age +
'}';
}
}
public class ObjectPrinter {
public static void print(Object obj) {
String string = "객체 정보 출력: " + obj.toString();
System.out.println(string);
}
}
package lang.object.tostring;
public class ToStringMain2 {
public static void main(String[] args) {
Car car = new Car("Model Y");
Dog dog1 = new Dog("멍멍이1", 2);
Dog dog2 = new Dog("멍멍이2", 5);
System.out.println("1. 단순 toString 호출");
System.out.println(car.toString());
System.out.println(dog1.toString());
System.out.println(dog2.toString());
System.out.println("2. println 내부에서 toString 호출");
System.out.println(car);
System.out.println(dog1);
System.out.println(dog2);
System.out.println("3. Object 다형성 활용");
ObjectPrinter.print(car);
ObjectPrinter.print(dog1);
ObjectPrinter.print(dog2);
}
}
출력 결과
1. 단순 toString 호출
lang.object.tostring.Car@3feba861
Dog{dogName='멍멍이1', age=2}
Dog{dogName='멍멍이2', age=5}
2. println 내부에서 toString 호출
lang.object.tostring.Car@3feba861
Dog{dogName='멍멍이1', age=2}
Dog{dogName='멍멍이2', age=5}
3. Object 다형성 활용
객체 정보 출력: lang.object.tostring.Car@3feba861
객체 정보 출력: Dog{dogName='멍멍이1', age=2}
객체 정보 출력: Dog{dogName='멍멍이2', age=5}
Car 인스턴스는 Object가 제공하는 기본 toString() 메서드를 사용하지만 Dog 인스턴스는 toString()을 재정의 해 객체의 상태를 명확하게 확인할 수 있다.
ObjectPrinter.print(Object obj) 분석 - Car 인스턴스
- Object obj 의 인수로 car(Car)가 전달 된다.
- 메서드 내부에서 obj.toString() 을 호출한다.
- obj 는 Object타입이다. 따라서 Object에 있는 toString() 을 찾는다.
- 이때 자식에 재정의(오버라이딩)된 메서드가 있는지 찾아본다. 재정의된 메서드가 없다.
- Object.toString() 을 실행한다.
ObjectPrinter.print(Object obj) 분석 - Dog 인스턴스
- Object obj의 인수로 dog(Dog)가 전달 된다.
- 메서드 내부에서 obj.toString() 을 호출한다.
- obj는 Object타입이다. 따라서 Object에 있는 toString() 을 찾는다.
- 이때 자식에 재정의(오버라이딩)된 메서드가 있는지 찾아본다. Dog에 재정의된 메서드가 있다.
- Dog.toString() 을 실행한다
객체의 참조값 직접 출력
String refValue = Integer.toHexString(System.identityHashCode(dog1));
System.out.println(refValue);
equals()
자바는 두 객체가 같다라는 표현을 2가지로 분리해서 제공함
- 동일성(Identity) : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
- 동등성(equals) : equals() 메서드를 사용하여 두 객체가 논리적으로 동등한지 확인
"동일"은 완전히 같음을 의미한다. 반면 "동등"은 같은 가치나 수준을 의미하지만 그 형태나 외관 등이 완전히 같지 않을 수 있다.
동일성은 물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인하는 것, 동등성은 논리적으로 같은지 확인하는 것
- 동일성은 물리적(자바 머신 기준, 메모리의 참조 기준), 동등성은 보통 사람이 생각하는 논리적인 기준
예제
package lang.object.equals;
public class EqualsMainV1 {
public static void main(String[] args) {
UserV1 user1 = new UserV1("id-100");
UserV1 user2 = new UserV1("id-100");
System.out.println("identity = " + (user1 == user2));
System.out.println("equality = " + (user1.equals(user2)));
}
}
출력결과
identity = false
equality = false
// Object 기본 제공 equals 메서드
public boolean equals(Object obj) {
return (this == obj);
}
Object가 기본으로 제공하는 equals()는 == 으로 동일성 비교를 제공하므로, 동등성 비교를 사용하려면 equals() 메서드를 재정의해야 한다.
equals 오버라이딩 예제
package lang.object.equals;
public class UserV2 {
private String id;
public UserV2(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
UserV2 user = (UserV2) obj;
return id.equals(user.id);
}
}
package lang.object.equals;
public class EqualsMainV2 {
public static void main(String[] args) {
UserV2 user1 = new UserV2("id-100");
UserV2 user2 = new UserV2("id-100");
System.out.println("identity = " + (user1 == user2));
System.out.println("equality = " + (user1.equals(user2)));
}
}
출력결과
identity = false
equality = true
- 동일성(Identity): 객체의 참조가 다르므로 동일성은 다르다.
- 동등성(Equality): user1, user2는 서로 다른 객체이지만 둘 다 같은 id(고객번호)를 가지고 있으므로 동등하다.
정확한 equals() 메서드 구현
equals() 가 정확하게 동작하려면 다음과 같은 구현이 필요하며, 여러 규칙을 지켜야 한다.
IDE 자동 생성 equals 메서드
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
UserV2 userV2 = (UserV2) object;
return Objects.equals(id, userV2.id);
}
equals() 메서드를 구현할 때 지켜야 하는 규칙
- 반사성(Reflexivity): 객체는 자기 자신과 동등해야 한다. (x.equals(x)는 항상 true).
- 대칭성(Symmetry): 두 객체가 서로에 대해 동일하다고 판단하면, 이는 양방향으로 동일해야 한다.(x.equals(y)가 true이면 y.equals(x)도 true).
- 추이성(Transitivity): 만약 한 객체가 두 번째 객체와 동일하고, 두 번째 객체가 세 번째 객체와 동일하다면, 첫 번째 객체는 세 번째 객체와도 동일해야 한다.
- 일관성(Consistency): 두 객체의 상태가 변경되지 않는 한, equals() 메소드는 항상 동일한 값을 반환해야 한다.
- null에 대한 비교: 모든 객체는 null과 비교했을 때 false를 반환해야 한다.
정리
- 동등성 비교가 항상 필요하지 않으며, 동등성 비교가 필요한 경우에만 equals()를 재정의하면 된다.
- equals()와 hashCode()는 보통 함께 사용된다.
'항해 99 > Java' 카테고리의 다른 글
String 클래스 (0) | 2024.06.05 |
---|---|
Java - 불변 객체 (0) | 2024.05.31 |
정렬 알고리즘 (0) | 2024.05.09 |
Array & LinkedList (0) | 2024.05.03 |
Java - Garbage Collector, Java Map (1) | 2024.04.08 |