-
[Java] 자바 기본 API - Object ClassCSE/Java 2016. 1. 14. 11:11
자바 기본 API는 여러 절로 구성되어 있습니다.
StringTokenizer, StringBuffer, StringBuilder Class
Regular Expression & Pattern Class
Object Class
클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 암시적으로 java.lang.Object 클래스를 상속하게 됩니다. 따라서 자바의 모든 클래스는 Object 클래스의 자식이거나 자손 클래스입니다. Object는 자바의 최상위 부모 클래스에 해당합니다.
객체 비교(equals())
다음은 Object의 equals() 메소드입니다.
12public boolean equals(Object obj) { ... }cs equals() 메소드의 매개 타입은 Object인데, 이것은 모든 객체가 파라미터로 대입될 수 있음을 말합니다. 그 이유는 Object가 최상위 타입이므로 모든 객체는 Object 타입으로 자동 타입 변환될 수 있기 때문입니다. Object 클래스의 equals() 메소드는 비교 연산자인 == 과 동일한 결과를 리턴합니다. 두 객체가 동일한 객체라면 true를 리턴하고 그렇지 않으면 false를 리턴합니다.
123456Object obj1 = new Object();Object obj2 = new Object();boolean result = obj1.equals(obj2);boolean sameResult = (obj1 == obj2);cs equals() 메소드는 두 객체가 논리적으로 동등하다면 true를 리턴하는 메소드입니다. equals() 메소드를 재정의할 때에는 파라미터가 기준 객체와 동일한 타입의 객체인지 먼저 확인해야 합니다. Object 타입의 매개 변수는 모든 객체가 파라미터로 제공될 수 있기 때문에 instanceof 연산자로 기준 객체와 동일한 타입인지 제일 먼저 확인해야 합니다. 만약 비교 객체가 다른 타입이라면 equals() 메소드는 false를 리턴해야 합니다. 비교 객체가 동일한 타입이라면 기준 객체 타입으로 강제 타입 변환해서 필드값이 동일한지 검사하면 됩니다.
다음 예제는 Member 클래스에서 equals() 메소드를 정의한 것입니다. Member 타입이면서 id 필드값이 같을 경우는 true를 리턴하고, 그 이외의 경우는 false를 리턴합니다.
* Member.java
1234567891011121314151617181920212223242526272829303132333435363738394041package apiref;public class Member {private String id;private String name;@Overridepublic boolean equals(Object obj) {if (obj instanceof Member) {Member mem = (Member) obj;if (id.equals(mem.getId())) {return true;}}return false;}public Member(String id) {this.id = id;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}}cs * MemberExam.java
123456789101112131415161718192021222324package apiref;public class MemberExam {public static void main(String[] args) {Member obj1 = new Member("Jack");Member obj2 = new Member("Rich");Member obj3 = new Member("Jack");if (obj1.equals(obj2)) {System.out.println("obj1 과 obj2 는 동등합니다.");} else {System.out.println("obj1 과 obj2 는 동등하지 않습니다.");}if (obj1.equals(obj3)) {System.out.println("obj1 과 obj3 는 동등합니다.");} else {System.out.println("obj1 과 obj3 는 동등하지 않습니다.");}}}cs 객체 해시코드(hashCode())
객체 해시코드란 객체를 식별할 하나의 정수 값을 말합니다. Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체마다 다른 값을 가지고 있습니다. 논리적 동등 비교 시 hashCode()를 오버라이딩하여 사용하는 HashSet, HashMap, HashTable 등이 hashCode()를 통해서 동등 비교합니다.
우선 hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 봅니다. 해시코드 값이 다르면 다른 객체로 판단하고, 해시코드 값이 같으면 equals() 메소드로 다시 비교합니다. 그렇기 때문에 hashCode() 메소드가 true가 나와도 equals()의 리턴값이 다르면 다른 객체가 됩니다.
다음 예제를 보면 Key 클래스는 equals() 메소드를 재정의해서 number 필드값이 같으면 true를 리턴하도록 했습니다. 그러나 hashCode() 메소드는 재정의하지 않았기 때문에 Object의 hashCode() 메소드가 사용됩니다.
* Key.java
1234567891011121314151617181920212223package apiref;public class Key {public int number;public Key(int number) {this.number = number;}@Overridepublic boolean equals(Object obj) {if (obj instanceof Key) {Key compareKey = (Key) obj;if (this.number == compareKey.number) {return true;}}return false;}}cs 이런 경우 HashMap의 식별키로 Key 객체를 사용하면 지정된 값을 가져오지 못합니다. 아래 예제를 보도록 합시다.
* KeyExam.java
123456789101112131415package apiref;import java.util.HashMap;public class KeyExam {public static void main(String[] args) {HashMap<Key, String> hashMap = new HashMap<Key, String>();hashMap.put(new Key(1), "Jack Rich");System.out.println(hashMap.get(new Key(1)));}}cs 위 예제는 null 값을 출력하게 됩니다. number 값이 같더라도 hashCode() 메소드에서 리턴하는 해시코드가 다르기 때문에 다른 식별키로 인식하기 때문입니다. 아래 예제처럼 hashCode() 메소드를 오버라이딩하여 사용하면 결과는 달라집니다.
* Key.java
12345678910111213141516171819202122232425262728package apiref;public class Key {public int number;public Key(int number) {this.number = number;}@Overridepublic boolean equals(Object obj) {if (obj instanceof Key) {Key compareKey = (Key) obj;if (this.number == compareKey.number) {return true;}}return false;}@Overridepublic int hashCode() {return number;}}cs 객체 문자 정보(toString())
Object 클래스의 toString () 메소드는 객체의 문자 정보를 리턴합니다. 객체의 문자 정보란 객체를 문자열로 표현한 값을 말합니다. 기본적으로 Object 클래스의 toString() 메소드는 "클래스명@16진수해시코드"로 구성된 문자 정보를 리턴합니다.
Object의 toString() 메소드의 리턴값은 자바 app에서 쓸데없는 정보이므로 toString()을 오버라이딩하여 유익하게 리턴할 수 있습니다. 다음 예제를 통해 직접 알아보도록 하겠습니다.
* SmartPhone.java
123456789101112131415161718192021package apiref;public class SmartPhone {private String company;private String os;private String version;public SmartPhone(String company, String os, String version) {super();this.company = company;this.os = os;this.version = version;}@Overridepublic String toString() {return company + ", " + os + ": " + version;}}cs * SmartPhoneExam.java
12345678910111213141516package apiref;public class SmartPhoneExam {public static void main(String[] args) {SmartPhone Nexus5 = new SmartPhone("Google", "Android", "Marsh");SmartPhone GalaxyS = new SmartPhone("Samsung", "Android", "Lollipop");SmartPhone iPhone = new SmartPhone("Apple", "iOS", "9.3");String strObj = Nexus5.toString();System.out.println(strObj);System.out.println(GalaxyS);System.out.println(iPhone);}}cs 객체 복제(clone())
객체 복제는 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 생성하는 것을 말합니다. 객체를 복제하는 이유는 원본 객체를 안전하게 보호하기 위해서입니다. 복제된 객체의 데이터가 훼손되더라도 원본 객체는 아무런 영향을 받지 않기 때문에 데이터를 안전하게 보호할 수 있게 됩니다.
객체를 복제하는 방법은 얕은 복제와 깊은 복제가 있습니다.
얕은 복제(thin clone)
얕은 복제란 단순히 필드값을 복사해서 객체를 복제하는 것을 말합니다. 필드값만 복제하기 때문에 필드가 기본 타입일 경우 값 복사가 일어나고, 필드가 참조 타입일 경우에는 객체의 번지가 복사됩니다.
Object의 clone() 메소드는 자신과 동일한 필드값을 가진 얕은 복제된 객체를 리턴합니다. 이 메소드로 객체를 복제하려면 원본 객체는 반드시 java.lang.Clonable 인터페이스를 구현하고 있어야 합니다. 메소드 선언이 없음에도 불구하고 Cloneable 인터페이스를 명시적으로 구현하는 이유는 클래스 설계자가 복제를 허용한다는 의도적인 표시를 하기 위해서입니다.
다음 예제를 보면 Member 클래스가 Cloneable 인터페이스를 구현했기 때문에 getMember() 메소드에서 clone() 메소드로 자신을 복제한 후, 복제한 객체를 외부로 리턴할 수 있습니다.
* Member.java
1234567891011121314151617181920212223242526272829package apiref;public class Member implements Cloneable {public String id;public String name;public String password;public int age;public boolean adult;public Member(String id, String name, String password, int age, boolean adult) {this.id = id;this.name = name;this.password = password;this.age = age;this.adult = adult;}public Member getMember() {Member cloned = null;try {cloned = (Member) clone();} catch (CloneNotSupportedException e) {}return cloned;}}cs * MemberExam.java
1234567891011121314151617181920package apiref;public class MemberExam {public static void main(String[] args) {Member original = new Member("Jack", "Spring", "1234", 22, true);Member cloned = original.getMember();cloned.password = "56rr";System.out.println("원본 필드의 패스워드]");System.out.println(original.password);System.out.println("복제 필드의 패스워드]");System.out.println(cloned.password);}}cs 깊은 복제(deep clone)
얕은 복제의 경우 참조 타입 필드는 번지만 복제되기 때문에 원본 객체의 필드와 복제 객체의 필드는 같은 객체를 참조하게 됩니다. 만약 복제 객체에서 참조 객체를 변경하면 원본 객체도 변경된 객체를 가지게 됩니다.
깊은 복제란 참조하고 있는 객체도 복제하는 것을 말합니다. 다음 그림은 원본 객체를 깊은 복제했을 경우 참조하는 배열 객체도 복제된다는 것을 보여줍니다.
깊은 복제를 하려면 Object의 clone() 메소드를 재정의해서 참조 객체를 복제하는 코드를 직접 작성해야 합니다. 다음 예제를 보면 Member 클래스에 int[] 배열과 Car 타입의 필드가 있습니다. 이 필드들은 모두 참조 타입이므로 깊은 복제 대상이 됩니다. Member 클래스는 Object의 clone() 메소드를 재정의해서 int[] 배열과 Car 객체를 복제합니다.
* Member.java
123456789101112131415161718192021222324252627282930313233343536373839package apiref;import java.util.Arrays;public class Member implements Cloneable {public String name;public int age;public int[] scores;public Car car;public Member(String name, int age, int[] scores, Car car) {this.name = name;this.age = age;this.scores = scores;this.car = car;}@Overridepublic Object clone() throws CloneNotSupportedException {Member cloned = (Member) super.clone();cloned.scores = Arrays.copyOf(this.scores, this.scores.length);cloned.car = new Car(this.car.model);return cloned;}public Member getMember() {Member cloned = null;try {cloned = (Member) clone();} catch (CloneNotSupportedException e) {}return cloned;}}cs * Car.java
1234567891011package apiref;public class Car {public String model;public Car(String model) {this.model = model;}}cs * MemberExam.java
1234567891011121314151617181920212223242526272829303132333435package apiref;public class MemberExam {public static void main(String[] args) {Member original = new Member("Andy", 29, new int[] { 100, 90 }, new Car("Audi"));Member cloned = original.getMember();cloned.scores[0] = 300;cloned.car = new Car("BMW");System.out.println("[원본 객체 스코어와 자동차]");System.out.print("Score: ");for (int i = 0; i < original.scores.length; i++) {System.out.print(original.scores[i] + " ");}System.out.println();System.out.println("Car: " + original.car.model);System.out.println("[복제 객체 스코어와 자동차]");System.out.print("Score: ");for (int i = 0; i < cloned.scores.length; i++) {System.out.print(cloned.scores[i] + " ");}System.out.println();System.out.println("Car: " + cloned.car.model);}}cs 객체 소멸자(finalize())
참조하지 않는 배열이나 객체는 Garbage Collector가 힙 영역에서 자동적으로 소멸시킵니다. Garbage Collector는 객체를 소멸하기 직전에 마지막으로 객체의 소멸자(finalize())를 실행기킵니다. 소멸자는 Object의 finalize() 메소드를 말하는데, 기본적으로 실행 내용이 없습니다. 만약 객체가 소멸되기 전에 마지막으로 사용했던 자원(데이터 연결, 파일 등)을 닫고 싶거나, 중요한 데이터를 저장하고 싶다면 Object의 finalize() 를 재정의할 수 있습니다. 다음은 finalize() 메소드를 재정의한 클래스 입니다. finalize() 메소드가 실행되면 번호를 출력하게 해서 어떤 객체가 소멸되는지 확인할 수 있도록 했습니다.
* Counter.java
12345678910111213141516package apiref;public class Counter {private int no;public Counter(int no) {this.no = no;}@Overrideprotected void finalize() throws Throwable {System.out.println(no + "번 객체의 finalize()가 실행됨");}}cs * FinalizeExam.java
1234567891011121314151617package apiref;public class FinalizeExam {public static void main(String[] args) {Counter counter = null;for (int i = 1; i <= 50; i++) {counter = new Counter(i);counter = null;System.gc();}}}cs * 이 포스트은 서적 '이것이 자바다' 를 참고하여 작성한 포스트입니다.'CSE > Java' 카테고리의 다른 글
[Java] 자바 기본 API - Class Class (0) 2016.03.28 [Java] 자바 기본 API - System Class (0) 2016.03.28 [Java] 자바 기본 API - Objects Class (0) 2016.01.14 [Java] 자바 기본 API - Intro (0) 2016.01.14 [Java] JavaFX - 스레드 동시성 (0) 2016.01.13 [Java] JavaFX - 메뉴바, 툴바, 다이얼로그 (0) 2016.01.03