- 객체 비교
- 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;
}
}