주제 : 김영한님의 자바 중급 1편 총 정리
내용 : Object 클래스에 대해 공부
자바가 기본적으로 제공하는 라이브러리 중에 가장 기본이 되는 것이 바로 java.lang 패키지이다. 여기서 lang은 Language의 줄임말이다.
java.lang 패키지의 대표적인 클래스들
Object: 모든 자바 객체의 부모 클래스String: 문자열Integer,Long,Double: 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것Class: 클래스 메타 정보System: 시스템과 관련된 기본 기능들을 제공
import 생략 가능
java.lang패키지는 모든 자바 애플리케이션에 자동으로 임포트된다. 따라서, 임포트 구문을 사용하지 않아도 된다.//import java.lang.System; //없어도 된다. public class LangMain{ public static void main(String[] args){ System.out.println("hello java"); } }
자바에서 모든 클래스의 최상위 부모 클래스는 항상 Object 클래스이다.
package lang.object; //부모가 없으면 묵시적으로 Object 클래스를 상속받는다. public class Parent { public void parentMethod() { System.out.println("Parent.parentMethod"); } }앞의 코드는 다음 코드와 같다 : extends Object 추가
package lang.object; //부모가 없으면 묵시적으로 Object 클래스를 상속받는다. public class Parent extends Object{ public void parentMethod() { System.out.println("Parent.parentMethod"); } }
- 클래스에 상속 받을 부모 클래스가 없으면 묵시적으로
Object클래스를 상속 받는다.
- 쉽게 이야기해서 자바가
extends Object코드를 넣어준다.
부모 클래스를 상속 받은 코드
public class Child extends Parent{ public void childMethod(){ System.out.println("Child.childMehotd"); } }public class ObjectMain { public static void main(String[] args) { Child child = new Child(); child.childMethod(); child.parentMethod(); // toString()은 Object 클래스의 메서드 String string = child.toString(); System.out.println(string); } }
- 예제에서
toString()은Object클래스의 메서드이다. 이 메서드는 객체의 정보를 제공한다.
실행 결과
Child.childMethod
Parent.parentMethod
lang.object.Child@X001
1.child.toString()을 호출한다.
2. 먼저 본인의 타입인Child에서toString()을 찾는다. 없으므로 부모 타입으로 올라가서 찾는다.
3. 부모 타입인Parent에서 찾는다. 없으므로 부모 타입으로 올라가서 찾는다.
4. 부모 타입인Object에서 찾는다.Object에toString()이 있으므로 이 메서드를 호출한다.
자바에서 Object 클래스가 최상위 부모 클래스인 이유
모든 클래스가Object클래스를 상속 받는 이유는 다음과 같다.
1. 공통 기능 제공
Object가 제공하는 기능은 다음과 같다.
- 객체의 정보를 제공하는
toString()- 객체의 같음을 비교하는
equals()- 객체의 클래스 정보를 제공하는
getClass()- 기타 여러가지 기능
2. 다형성의 기본 구현
Object는 모든 클래스의 부모 클래스이다. 따라서, 모든 객체를 참조할 수 있다.
Object를 활용한 다형성의 한계
Object는 모든 객체를 대상으로 다형적 참조를 할 수 있다.
- 쉽게 이야기해서
Object는 모든 객체의 부모이므로 모든 객체를 담을 수 있다.
Object를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다.
Object가 세상의 모든 메서드를 알고 있는 것이 아니다.Object에서sound()를 찾을 수 없다. 호출하려면 다운 캐스팅을 해주어야 한다.
public class Car { public void move(){ System.out.println("자동차 이름"); } } public class Dog { public void sound(){ System.out.println("멍멍"); } }public class ObjectPolyExample1 { public static void main(String[] args) { Dog dog = new Dog(); Car car = new Car(); action(dog); action(dog); } private static void action(Object obj){ //obj.sound(); //obj.move(); if (obj instanceof Dog dog){ dog.sound(); } else if (obj instanceof Car car) { car.move(); } } }
Object는 모든 타입의 부모다.
부모는 자식을 담을 수 있으므로 앞의 코드를 다음과 같이 변경해도 된다.Object dog = new Dog(); //Dog를 Object로 수정 가능 Object car = new Car(); //Car를 Object로 수정 가능
Object는 모든 타입의 객체를 담을 수 있다. 따라서, Object[]을 만들면 세상의 모든 객체를 담을 수 있는 배열을 만들 수 있다.
public class ObjectPolyExample2 { public static void main(String[] args) { Dog dog = new Dog(); Car car = new Car(); Object object = new Object(); //Object 인스턴스도 만들 수 있다. Object[] objects = {dog, car, object}; size(objects); } private static void size(Object[] obejcts){ System.out.printlne("전달된 객체의 수는:" + objects.length); } }
Object가 없다면?
만약Object와 같은 개념이 없다면 어떻게 될까?
void action(Object obj)과 같이 모든 객체를 받을 수 있는 메서드를 만들 수 없다.Object[] objects처럼 모든 객체를 저장할 수 있는 배열을 만들 수 없다.
Object.toString() 메서드는 객체의 정보를 문자열 형태로 제공한다.
println()과 toString()
그런데, toString() 의 결과를 출력한 코드와 object 를 println() 에 직접 출력한 코드의 결과가 완전히 같다.
public class ToStringMain1 { public static void main(String[] args) { Object object = new Object(); String string = object.toString(); //toString() 반환값 출력 System.out.println(string); //object 직접 출력 System.out.println(object); } }
System.out.println()메서드는 사실 내부에서toString()을 호출한다.
Object타입(자식 포함)이println()에 인수로 전달되면 내부에서obj.toString()메서드를 호출해서 결과를 출력한다.public void println(Object x) { String s = String.valueOf(x); }따라서
println()을 사용할 때,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 + '}'; } }
Dog는toString()을 재정의했다.toString()메서드는 IDE의 도움을 받아서 작성하는 것이 매우 편리하다. generator 단축키:⌘N(macOS) /Alt+Insert(Windows/Linux)
OCP 원칙
Open:
- 새로운 클래스를 추가하고,
toString()을 오버라이딩해서 기능을 확장할 수 있다.Closed:
- 새로운 클래스를 추가해도
Object와toString()을 사용하는 클라이언트 코드인ObjectPrinter는 변경하지 않아도 된다.- 다형적 참조, 메서드 오버라이딩, 그리고 클라이언트 코드가 구체적인
Car,Dog에 의존하는 것이 아니라 추상적인Object에 의존하면서 OCP 원칙을 지킬 수 있었다.- 덕분에 새로운 클래스를 추가하고
toString()메서드를 새롭게 오버라이딩해서 기능을 확장할 수 있다.- 그리고 이러한 변화에도 불구하고 클라이언트 코드인
ObjectPrinter는 변경할 필요가 없다.
자바 언어는 객체지향 언어답게 언어 스스로도 객체지향의 특징을 매우 잘 활용한다.
toString( ) 메서드와 같이, 자바 언어가 기본으로 제공하는 다양한 메서드들은 개발자가 필요에 따라 오버라이딩해서 사용할 수 있도록 설계되어 있다.
Object는 동등성 비교를 위한equals()메서드를 제공한다.자바는 두 객체가 같다라는 표현을 2가지로 분리해서 제공한다.
- 동일성(Identity):
==연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인 (완전 같음)- 동등성(Equality):
equals()메서드를 사용하여 두 객체가 논리적으로 동등한지 확인단어 정리
"동일"은 완전히 같음을 의미한다. 반면 "동등"은 같은 가치나 수준을 의미하지만 그 형태나 외관 등이 완전히 같지는 않을 수 있다.쉽게 이야기해서 동일성은 물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인하는 것이고, 동등성은 논리적으로 같은지 확인하는 것이다.
예를 들어 같은 회원 번호를 가진 회원 객체가 2개 있다고 가정해보자
User a = new User("id-100") //참조 x001 User b = new User("id-100") //참조 x002이 경우 물리적으로 다른 메모리에 있는 다른 객체이지만, 회원 번호를 기준으로 생각해보면 논리적으로 같은 회원으로 볼 수 있다. 따라서, 동일성은 다르지만 동등성은 같다.
예제를 통해서 동일성과 동등성을 비교해보자.
UserV1 예제
public class UserV1 { private String id; public UserV1(String id){ this.id = id; } } 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()는 ==으로 동일성 비교를 제공한다.
동등성 비교 Object.equals() 메서드
public boolean equals(Object obj){ return (this == obj); }
equals실행 순서 예시user1.equals(user2) return (user1 == user2) //Object.equals 메서드 안 return (x001 == x002) //Object.equals 메서드 안 return false false따라서 동등성 비교를 사용하고 싶으면
equals()메서드를 오버라이딩해야 한다. 그렇지 않으면Object는 동일성 비교를 기본으로 제공한다.
UserV2는 id(고객번호)가 같으면 논리적으로 같은 객체로 정의하겠다.
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); } }
Object의equals()메서드를 재정의했다.UserV2의 동등성은id(고객번호)로 비교한다.equals()는Object타입을 매개변수로 사용한다. 따라서 객체의 특정 값을 사용하려면 다운캐스팅이 필요하다.- 여기서는 현재 인스턴스(
this)에 있는id문자열과 비교 대상으로 넘어온 객체의id문자열을 비교한다.UserV2에 있는id는String이다. 문자열 비교는==이 아니라equals()를 사용해야 한다.
- 앞서
UserV2에서 구현한equals()는 이해를 돕기 위해 매우 간단히 만든 버전이고, 실제로 정확하게 동작하려면 다음과 같이 구현해야 한다. 정확한equals()메서드를 구현하는 것은 생각보다 쉽지 않다.- IntelliJ를 포함한 대부분의 IDE는 정확한
equals()코드를 자동으로 만들어준다.
팁: 멤버변수만 선언하면 생성자, toString, equals 다 alt+insert가 처리해준다.
문제 설명
- 다음 코드와 실행 결과를 참고해서
Rectangle클래스를 만들어라Rectangle클래스에 IDE의 기능을 사용해서toString(),equals()메서드를 실행 결과에 맞도록 재정의해라.
rect1과rect2는 넓이(width)와 높이(height)를 가진다. 넓이와 높이가 모두 같다면 동등성 비교에 성공해야 한다