Programing/Java
Effective Java - 객체 비교, 복제
안중환
2016. 3. 4. 16:29
- 객체 비교
- 1_equals 재정의package kr.co.ioacademy; //iocademy 윤찬식 강사님 import java.util.Objects; // 1. Object.equals()를 재정의하지 않을 경우 // 모든 객체는 오직 자기 자신과 동일하다. // 2. 객체 동일성이 아닌 논리적 동일성의 개념을 제공하기 위해서는 // equals()를 재정의해야 한다. class Point { private int mX; private int mY; public Point(int x, int y) { this.mX = x; this.mY = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + mX; result = prime * result + mY; return result; } @Override public boolean equals(Object obj) { // 1. 자기 자신인지 검사 - 성능 if (this == obj) return true; // 2. null 인지 체크 (모든 객체는 null과 동치 관계가 있지 않다.) if (obj == null) return false; // 3. 인자의 자료형이 정확한지 검사. if (!(obj instanceof Point)) return false; // 4. 자료형 변환 Point p = (Point) obj; // 5. 중요 필드 점검 return mX == p.mX && mY == p.mY; } } class Unit { private Point position; private Point start; // 1. 객체에 대한 참조는 null이 될 수 있다. // 필드가 많아지면 Objects.equal을 고려하자.(Google Guava / 1.7) @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null) return false; if (!(obj instanceof Unit)) return false; Unit p = (Unit) obj; // if (position == null) { // if (p.position != null) return false; // } else if (!position.equals(p.position)) return false; // return true; return Objects.equals(position, p.position) && Objects.equals(start, p.start); } } public class Example5 { public static void main(String[] args) { Point p1 = new Point(10, 20); Point p2 = new Point(10, 20); if (p1.equals(p2)) { System.out.println("Same"); } else { System.out.println("Not Same"); } } }
- 2_BigDecimal, 배열 비교
: 컴퓨터는 태생적으로 부동소수점을 정확히 표현할 수 없다.(2진수로 표현하기 때문에...)
: float, double은 == 로 비교하면 안된다. -> BigDecimal 사용
: 배열의 내용 비교 -> Arrays.equals()package kr.co.ioacademy; //iocademy 윤찬식 강사님 import java.math.BigDecimal; import java.util.Arrays; // 1. float이나 double은 ==으로 비교하면 안된다. // 2. 정밀한 연산을 필요로 한다면 BigDecimal 을 사용해야 한다. public class Example6 { public static void main(String[] args) { double value1 = 2.0 - 1.1; double value2 = 0.9; // if (value1 == value2) { if (Math.abs(value1 - value2) < 0.001) { System.out.println("same"); } else { System.out.println("not same"); } BigDecimal v = new BigDecimal(2.0).subtract(new BigDecimal(1.1)); System.out.println(v);; // 주의 사항 : BigDecimal(String)의 생성자를 사용해야 한다. BigDecimal v2 = new BigDecimal("2.0").subtract(new BigDecimal("1.1")); System.out.println(v2);; //------------------------------------------------- // 3. 배열 내용을 비교하려면 Object.equals() 가 아닌 Arrays.equals() // 사용해야 한다. int[] arr1 = new int[20]; int[] arr2 = new int[20]; System.out.println(arr1.equals(arr2)); System.out.println(Arrays.equals(arr1, arr2)); } }
- 3_hashCode
: Google Guava를 사용하여 toString 재정의
package kr.co.ioacademy; //iocademy 윤찬식 강사님 import java.util.HashMap; import java.util.Map; import java.util.Objects; import com.google.common.base.MoreObjects; // 핵심 : 같은 객체는 동일한 해시 코드 값을 가져야 한다. // 즉 equals를 재정의한 클래스는 반드시 hashCode도 재정의해야 한다. // 그래야 HashMap, HashSet, HashTable 등 hash 기반 컬렉션과 함께 사용하면 오동작 하지 않는다. public class Example7 { public static void main(String[] args) { Map<Person, String> m = new HashMap<>(); m.put(new Person("Tom", 42), "Google"); System.out.println(m.get(new Person("Tom", 42))); // toString()을 잘 만들어 놓으면 편리하다. System.out.println(new Person("IU", 42)); } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return MoreObjects.toStringHelper(this).add("name", name).add("age", age).toString(); } @Override public int hashCode() { // equals 에 이용된 중요 필드를 이용해서 hash를 생성하면 된다. return Objects.hash(name, age); } @Override public boolean equals(Object o) { if (o == this) return true; if (o instanceof Person) { Person p = (Person) o; return Objects.equals(name, p.name) && age == p.age; } return false; } }
객체 복제
: 인자 전달 방식
-> Call-by-Value vs. Call-by-Reference
: 객체 복사 방식
-> 얕은 복사(Shallow Copy) vs. 깊은 복사(Deep Copy)
class Num { public int num; public Num(int num) { this.num = num; } } public class CallBy { // 값을 복사해서 인자 전달 // value는 객체에 대한 레퍼런스 값, 프리미티브 타입의 값 public void callByValue(int a, int b) { System.out.println("callByValue in method: " + a + " " + b); int swap = a; a = b; b = swap; System.out.println("callByValue out method: " + a + " " + b); } // 해당 객체를 참조하는 객체를 복사해서 인자 전달(얕은 복사) // 해당 객체의 주소값을 직접 넘기는 것이 아니라 객체를 가리키고 있는 또 다른 주소값을 만들어서 넘긴다 void callByReference(Num a, Num b) { System.out.println("callByReference in method: " + a.num + " " + b.num); Num swap = a; a = b; b = swap; System.out.println("callByReference out method: " + a.num + " " + b.num); } // 객체의 멤버 필드값에 대한 복사가 필요(깊은 복사) void callByReference2(Num a, Num b) { System.out.println("callByReference2 in method: " + a.num + " " + b.num); int swap = a.num; a.num = b.num; b.num = swap; System.out.println("callByReference2 out method: " + a.num + " " + b.num); } public static void main(String[] args) { CallBy call = new CallBy(); int a = 5; int b = 10; Num n1 = new Num(5); Num n2 = new Num(10); call.callByValue(a, b); System.out.println("callByValue main method: " + a + " " + b + "\n"); //callByValue in method: 5 10 //callByValue out method: 10 5 //callByValue main method: 5 10 call.callByReference(n1, n2); System.out.println("callByReference main method: " + n1.num + " " + n2.num + "\n"); //callByReference in method: 5 10 //callByReference out method: 10 5 //callByReference main method: 5 10 call.callByReference2(n1, n2); System.out.println("callByReference2 main method: " + n1.num + " " + n2.num); //callByReference2 in method: 5 10 //callByReference2 out method: 10 5 //callByReference2 main method: 10 5 } }
- 1_clone, Cloneable
: 객체에 대한 깊은 복사
: 상속을 해주기 위한 클래스를 설계할 때, 잘 동작하는 protected clone 메소드를 그 클래스에 두지 않는다면 서브 클래스에서 Cloneable 인터페이스를 제대로 구현할 수 없다.
package kr.co.ioacademy; //iocademy 윤찬식 강사님 import com.google.common.base.MoreObjects; class Point implements Cloneable { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public String toString() { return "(" + x + ", " + y + ")"; } @Override public Point clone() { try { return (Point) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } // 객체 복제 하기 // 1. clone() 함수를 오버라이드 한다. (protected -> public) // : 오버라이딩할 메소드는 부모의 접근 제한자와 같거나 접근하기 더 쉬워야 한다. // 2. Cloneable 인터페이스를 구현해야 한다. // : 어떤 객체가 복제를 허용한다는 사실을 알리는데 쓰이는 용도이다. // 객체가 Cloneable 인터페이스를 구현하고 있으면, Object.clone() 은 // 객체가 가지고 있는 모든 멤버를 복사한다. class Unit implements Cloneable { private String name; private int age; private Point position; // 중요 : 변경 가능 객체에 대한 참조를 가지고 있으면 문제가 발생한다. public Unit(String name, int age, Point pos) { this.name = name; this.age = age; this.position = pos; } public void setPos(int x, int y) { position.setX(x); position.setY(y); } // public Object clone() { // 공변 반환형 : 재정의 메소드의 리턴 타입은 재정의 되는 메소드의 // 리턴 타입의 하위 클래스가 될 수 있다.(1.5) // @Override // public Unit clone() { // try { // return (Unit) super.clone(); // } catch (CloneNotSupportedException e) { // e.printStackTrace(); // } // // return null; // } @Override public Unit clone() { try { // 1. 전체 복사 후 Unit result = (Unit) super.clone(); // 2. 변경 가능 객체 복제 result.position = position.clone(); return result; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } @Override public String toString() { return MoreObjects.toStringHelper(this).add("name", name).add("age", age) .add("pos", position).toString(); } } public class Example8 { public static void main(String[] args) { Unit p1 = new Unit("Tom", 42, new Point(0, 0)); // Unit p2 = (Unit) p.clone(); Unit p2 = p1.clone(); p2.setPos(10, 20); // !!! System.out.println(p1); System.out.println(p2); } }
- 2_생성자 방어 복사
: 접근자를 이용하여 클라이언트가 값을 변경할 수 있다. -> 캡슐화가 깨짐
: 객체를 복사할 때 객체의 참조를 리턴하는 것이 아니라 객체의 복사본을 리턴한다. pos.setX(9999)로 x값을 변경해도 복사한 객체의 값을 바꾸므로 기존의 객체의 값은 그대로 유지된다.
package kr.co.ioacademy; //iocademy 윤찬식 강사님 import com.google.common.base.MoreObjects; public class Example9 { public static void main(String[] args) { // 1. 생성자 방어 복사가 필요하다. // : 인자의 유효성을 검사하기 전에 복사하고 나서, // 원본이 아닌 복사본의 유효성을 검사해야 한다. Point pos = new Point(100, 200); // Unit unit = new Unit(pos); String name = "Tom"; Unit unit = new Unit(pos, name); pos.setX(9999); // 2. 접근자 메소드에서도 참조를 방어 복사하여 리턴해야 한다. pos = unit.position(); pos.setX(9999); System.out.println(unit); } } class Unit { private Point position; private String name; public Unit(Point pos, String name) { position = pos.clone(); // position = pos; this.name = name; } public String name() { return name; } public Point position() { return position.clone(); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("pos", position).add("name", name).toString(); } } class Point implements Cloneable { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public String toString() { return "(" + x + ", " + y + ")"; } @Override public Point clone() { try { return (Point) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }