์ด๋ฒ ๊ธ์์๋ equals ๋ฉ์๋๋ฅผ ์ฌ์ ์ํ ๋ ๋ฐ๋์ ์ง์ผ์ผ ํ๋ ์ผ๋ฐ ๊ท์ฝ 5๊ฐ์ง๋ฅผ ์กฐ๊ธ ๋ ์์ธํ ์์๋ณด๊ณ ์ ํ๋ค.
1. ๋ฐ์ฌ์ฑ(reflexivity)
A.equals(A) == true
๋ฐ์ฌ์ฑ์ ๋จ์ํ ๋งํ๋ฉด ๊ฐ์ฒด๋ ์๊ธฐ ์์ ๊ณผ ๊ฐ์์ผ ํ๋ค๋ ๋ป์ด๋ค. ์ด ์๊ฑด์ ์ผ๋ถ๋ฌ ์ด๊ธฐ๋ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด ๋ง์กฑ์ํค์ง ๋ชปํ๊ธฐ๊ฐ ๋ ์ด๋ ค์ ๋ณด์ธ๋ค.
2. ๋์นญ์ฑ(symmetry)
A.equals(B) = B.equals(A)
๋์นญ์ฑ์ ๋ ๊ฐ์ฒด๋ ์๋ก์ ๋ํ ๋์น ์ฌ๋ถ์ ๋๊ฐ์ด ๋ตํด์ผ ํ๋ค๋ ๋ป์ด๋ค.
๋์๋ฌธ์๋ฅผ ๊ตฌ๋ณํ์ง ์๋ ๋ฌธ์์ด์ ๊ตฌํํ ๋ค์ ํด๋์ค๊ฐ ์๋ค๊ณ ํ์.
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
// ๋์นญ์ฑ ์๋ฐฐ!
@Override public boolean equals(Object o) {
if(o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(
((CaseInsensitiveString) o).s);
if(o instanceof String) // ํ ๋ฐฉํฅ์ผ๋ก๋ง ์๋ํ๋ค!
return s.equalsIgnoreCase((String o);
return false;
}
}
์๋ ์ฝ๋์์ cis.equals(s)๋ true๋ฅผ ๋ฐํํ๋ค๋ ๊ฒ์ ์ฝ๊ฒ ์ ์ ์๋ค.
๊ทธ๋ฌ๋ฉด s.equals(cis)๋ ์ด๋ค ๊ฒ์ ๋ฐํํ ๊น? CaseInsensitiveString์ equals๋ ์ผ๋ฐ String์ ์๊ณ ์์ง๋ง, String์ equals๋ CaseInsensitiveString์ ์กด์ฌ๋ฅผ ์์ง ๋ชปํ๋ค. ๊ทธ ๊ฒฐ๊ณผ, s.equals(cis)๋ false๋ฅผ ๋ฐํํ๋ค. ์ด๋ ๋์นญ์ฑ์ ๋ช ๋ฐฑํ ์๋ฐํ๋ ๊ฒ์ด๋ค. (์ฝ๋์์ Polish์ polish์ ๋์๋ฌธ์๋ฅผ ์ ํ์ธํ๊ธฐ ๋ฐ๋๋ค.)
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s)); // true
System.out.println(s.equals(cis)); // false
๋ค๋ฅธ ํ์ ์ ์ง์ํ๊ธฐ ์์ํ๋ฉด ๋ฌธ์ ๊ฐ ๋ณต์กํด์ง๊ณ ๋์นญ์ฑ์ด ๊นจ์ง ์ ์๋ค. ๊ทธ๋ ๊ฒ ๋๋ฌธ์ ๋์นญ์ฑ์ ์ ์งํ๊ธฐ ์ํด์๋ ๋ค๋ฅธ ํ์ ์ ์ง์ํ์ง ๋ง์์ผ ํ๋ค.
@Override public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsenstiveString) o).s.equalsIsIgnoreCase(s));
}
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s)); // false
System.out.println(s.equals(cis)); // false
3. ์ถ์ด์ฑ(transitivity)
A.equals(B)&&B.equals(C), A.equals(C)
์ถ์ด์ฑ์ ์ฒซ ๋ฒ์งธ ๊ฐ์ฒด์ ๋ ๋ฒ์งธ ๊ฐ์ฒด๊ฐ ๊ฐ๊ณ , ๋ ๋ฒ์งธ ๊ฐ์ฒด์ ์ธ ๋ฒ์งธ ๊ฐ์ฒด๊ฐ ๊ฐ๋ค๋ฉด, ์ฒซ ๋ฒ์งธ ๊ฐ์ฒด์ ์ธ ๋ฒ์งธ ๊ฐ์ฒด๋ ๊ฐ์์ผ ํ๋ค๋ ๋ป์ด๋ค.
๊ฐ๋จํ 2์ฐจ์์์์ ์ ์ ํํํ๋ ํด๋์ค๊ฐ ์๋ค๊ณ ํด๋ณด์.
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override public boolean equals(Object o) {
if(!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
}
์๊ฐ์ ํ๋ฆ 1
์ด ํด๋์ค๋ฅผ ํ์ฅํ ์์์ด ์๋ ์ ์ด ์๋ค๊ณ ํด๋ณด์. ์ด์ ๊น์ง ๋ฐฐ์ด ๊ฒ์ ๋ฐํ์ผ๋ก equals๋ฅผ ์๋์ ๊ฐ์ด ๋ง๋ค ์ ์๋ค. ํ์ง๋ง ์ด๋ ๋ค์ ๋์นญ์ฑ์ ์งํค์ง ๋ชปํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ๋ค.
public class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// ๋์นญ์ฑ ์๋ฐฐ!
@Override public boolean equals(Object o) {
if(!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
}
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp)); // true
System.out.println(cp.equals(p)); // false
์๊ฐ์ ํ๋ฆ 2
๊ทธ๋ฌ๋ฉด ํ์ ๊น์ง ๊ณ ๋ คํ์ฌ ๋ง๋ equals()๋ ์ด๋จ๊น? ์๋ ๋ฐฉ์์ ๋์นญ์ฑ์ ์ง์ผ์ฃผ์ง๋ง, ์ถ์ด์ฑ์ ๊นจ๋ฒ๋ฆฐ๋ค. ๋ํ, ์ด ๋ฐฉ์์ ๋ฌดํ ์ฌ๊ท์ ๋น ์ง ์ํ๋ ์๋ค. ColorPoint์ ๊ฐ์ ๋ ๋ฒจ์ ์๋ Point์ ์๋ธํด๋์ค๊ฐ ์๋ค๋ฉด StackOverflowError๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
// ์ถ์ด์ฑ ์๋ฐฐ!
@Override public boolean equals(Object o) {
if(!(o instanceof Point))
return false;
// o๊ฐ ์ผ๋ฐ Point๋ฉด ์์์ ๋ฌด์ํ๊ณ ๋น๊ตํ๋ค.
if(!(o instanceof ColorPoint))
return o.equals(this); // ๋ฌดํ ์ฌ๊ท์ ๋น ์ง ์ํ์ด ์์.
// o๊ฐ ColorPoint๋ฉด ์์๊น์ง ๋น๊ตํ๋ค.
return super.equals(o) && ((ColorPoint) o).color == color;
}
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.println(p1.equals(p2)); // true
System.out.println(p2.equals(p1)); // true
System.out.println(p1.equals(p3)); // false (true๊ฐ ๋์์ผ์ง ์ถ์ด์ฑ์ด ์ง์ผ์ง๋ ๊ฒ)
์๊ฐ์ ํ๋ฆ 3
๊ทธ๋ฌ๋ฉด ์ถ์ด์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด์ Point์ ColorPoint๋ฅผ ๋น๊ตํ ๋ ๋ค๋ฅด๋ค๊ณ ๋์ค๋ฉด ๋์ง ์์๊น๋ผ๊ณ ์๊ฐํ ์ ์๋ค. (p2.equals(p1)์ด false๊ฐ ๋๋ค๋ ๋ง์ด๋ค.) ๊ทธ๋ฆฌ๊ณ Point ํด๋์ค์ ์ฝ๋๋ฅผ ์๋์ ๊ฐ์ด ๋ฐ๊ฟ๋ณผ ์๋ ์๋ค. ์ฆ, Point๋ Point๋ผ๋ฆฌ, ColorPoint๋ ColorPoint๋ผ๋ฆฌ ๋น๊ตํด์ผ ํ๋ค๋ ๊ฐ๋ ์ด๋ค.
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// ๋ฆฌ์ค์ฝํ ์นํ ์์น ์๋ฐฐ!
@Override public boolean equals(Object o) {
if(o == null || o.getClass() != getClass()) // ๋ฐ๋ ๋ถ๋ถ
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
}
์์ฝ๊ฒ๋ ์ด๋ ๊ฒ ์๊ฐํ๋ฉด ์ ๋๋ค! ์ด์ ๋ ๋ฆฌ์ค์ฝํ ์นํ ์์น์ ์๋ฐฐ๋๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฆฌ์ค์ฝํ ์นํ ์์น์ ๊ฐ๋จํ๊ฒ ์ค๋ช ํ์๋ฉด, ์์ ํด๋์ค ํ์ ์ผ๋ก ๋์ํ๋ ์ด๋ค ์ฝ๋๊ฐ ์์ ๋, ๊ทธ ์์ ํด๋์ค ๋์ ์ ์ด๋ค ํ์ ํด๋์ค ํ์ ์ ์ธ์คํด์ค๋ฅผ ์ฃผ๋๋ผ๋ ๊ทธ๋๋ก ๋์ผํ๊ฒ ๋์ํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค. ์๋ ์ฝ๋์์ ๋ฆฌ์ค์ฝํ ์นํ ์์น์ด ์๋ฐฐ๋๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ฆ, ๊ตฌ์ฒด ํด๋์ค๋ฅผ ํ์ฅํด ์๋ก์ด ๊ฐ์ ์ถ๊ฐํ๋ฉด์ equals ๊ท์ฝ์ ๋ง์กฑ์ํฌ ๋ฐฉ๋ฒ์ ์กด์ฌํ์ง ์๋๋ค. ์ด๋ฌํ ์ํฉ์์ ์ถ์ด์ฑ๊ณผ ๋์นญ์ฑ์ ์๋ฐํ์ง ์๋๋ก equals๋ฅผ ๋ง๋ค ์ ์๋ค. (๊ฐ์ฒด ์งํฅ์ ์ถ์ํฉ์ ์ด์ ์ ํฌ๊ธฐํ์ง ์๋ ํ์ ๋ง์ด๋ค.)
public class CounterPoint extends Point {
private static final AtomicInteger counter = new AtomicInteger();
public CounterPoint(int x, int y) {
super(x, y);
counter.incrementAndGet();
}
public static int numberCreated() { return counter.get(); }
}
// CounterPoint๋ฅผ ์ฌ์ฉํ๋ ํ
์คํธ ํ๋ก๊ทธ๋จ
public class CounterPointTest {
// ๋จ์ ์ ์์ ๋ชจ๋ ์ ์ ํฌํจํ๋๋ก unitCircle์ ์ด๊ธฐํํ๋ค.
private static final Set<Point> unitCircle = Set.of(
new Point(1, 0), new Point(0, 1),
new Point(-1, 0), new Point(0, -1));
public static boolean onUnitCircle(Point p) {
return unitCircle.contains(p);
}
}
Point p1 = new Point(1, 0);
Point p2 = new CounterPoint(1, 0);
System.out.println(onUnitCircle(p1)); // true
System.out.println(onUnitCircle(p2)); // false (๋ฆฌ์ค์ฝํ์นํ์์น์ ๋ง์กฑํ๋ค๋ฉด true๊ฐ ๋์์ผํ๋ค.)
ํด๊ฒฐ์ฑ
์ด์ ๋ํ ํด๊ฒฐ์ฑ ์ผ๋ก ์ปดํฌ์ง์ ์ ๊ถ์ฅํ๊ณ ์๋ค. ์์์ ๋ฐ์์ ํ๋๋ฅผ ์ถ๊ฐํ๊ณ ์ถ์ ๊ฒฝ์ฐ์๋ ์์ ๋์ ์ปดํฌ์ง์ ์ ์ฌ์ฉํด์ ํ๋๋ฅผ ์ถ๊ฐํ๋๋ก ํ์.
public class colorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = Objects.requireNonNull(color);
}
/**
* ์ด ColorPoint์ Point ๋ทฐ๋ฅผ ๋ฐํํ๋ค.
*/
public Point asPoint() {
return point;
}
@Override public boolean equals(Object o) {
if(!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
}
}
Point p1 = new Point(1, 0);
Point p2 = new ColorPoint(1, 0, Color.RED).asPoint(); // asPoint ์ฌ์ฉ๋ฒ
// Point p2 = new ColorPoint(1, 0, Color.RED); // ์ด๋ ๊ฒ๋ ์๋๋ค.
์ถ์ด์ฑ์ ๋ํ ๋ด์ฉ์ด ๊ธธ์๋๋ฐ, ๊ฐ๋จํ ์ ๋ฆฌํ๋ฉด ์๋์ ๊ฐ๋ค.
- [์๊ฐ์ ํ๋ฆ 1] ๊ธฐ์กด ๋ฐฉ๋ฒ ์ฌ์ฉ - ๋์นญ์ฑ X
- [์๊ฐ์ ํ๋ฆ 2] ํ์ ๊น์ง ๊ณ ๋ คํ์ฌ ๋ง๋ equals ์ฌ์ฉ - ๋์นญ์ฑ O, ์ถ์ด์ฑ X
- [์๊ฐ์ ํ๋ฆ 3] ๊ฐ๊ฐ์ ํ์ ๊น์ง ๊ณ ๋ คํ์ฌ ๋ง๋ equals ์ฌ์ฉ - ๋์นญ์ฑ O, ์ถ์ด์ฑ O, ๋ฆฌ์ค์ฝํ ์นํ ์์น X
- [ํด๊ฒฐ์ฑ ] ์ปดํฌ์ง์ ์ ํ์ฉํ์ฌ ์ฌ์ฉํ์
4. ์ผ๊ด์ฑ(consistency)
A.equals(B) == A.equals(B)
์ผ๊ด์ฑ์ ๋ ๊ฐ์ฒด๊ฐ ๊ฐ๋ค๋ฉด(์ด๋ ํ๋ ํน์ ๋ ๊ฐ์ฒด ๋ชจ๋๊ฐ ์์ ๋์ง ์๋ ํ) ์์ผ๋ก๋ ์์ํ ๊ฐ์์ผ ํ๋ค๋ ๋ป์ด๋ค. ๊ฐ๋ณ ๊ฐ์ฒด๋ ๋น๊ต ์์ ์ ๋ฐ๋ผ ์๋ก ๋ค๋ฅผ ์๋ ํน์ ๊ฐ์ ์๋ ์๋ ๋ฐ๋ฉด, ๋ถ๋ณ ๊ฐ์ฒด๋ ํ๋ฒ ๋ค๋ฅด๋ฉด ๋๊น์ง ๋ฌ๋ผ์ผ ํ๋ค.
๋ํ, ํด๋์ค๊ฐ ๋ถ๋ณ์ด๋ ๊ฐ๋ณ์ด๋ equals์ ํ๋จ์ ์ ๋ขฐํ ์ ์๋ ์์์ด ๋ผ์ด๋ค๊ฒ ํด์๋ ์ ๋๋ค. (๋๋ฌด ๋ณต์กํ๊ฒ ๊ตฌํํ๋ฉด ์ ๋๋ค.) ์ต์ข ์ ์ผ๋ก ์ด๋ค ๊ฒ์ ๊ฐ๋ฆฌํค๋๋ก ๋น๊ตํ๋ฉด ๋ณต์กํ ๊ฒ์ด๋ค. ์๋๋ ์ต์ข ์ ์ธ IP๋ฅผ ๋น๊ตํ์ฌ ๋ณต์กํ๊ฒ ๊ตฌํํ ์์์ด๋ค.
URL google1 = new URL("https", "about.google", "/products/");
URL google2 = new URL("https", "about.google", "/products/");
System.out.println(google1.equals(google2)); // true์ผ ์๋ ์๊ณ , false์ผ ์๋ ์๋ค.
5. null-์๋
์ด๊ฒ์ ๋ชจ๋ ๊ฐ์ฒด๊ฐ null๊ณผ ๊ฐ์ง ์์์ผ ํ๋ค๋ ๋ป์ด๋ค. ์ ๋ ฅ์ด null์ด๋ฉด ํ์ ํ์ธ ๋จ๊ณ์์ false๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ null ๊ฒ์ฌ๋ฅผ ๋ช ์์ ์ผ๋ก ํ์ง ์์๋ ๋๋ค.
์ด์์ผ๋ก equals ๋ฉ์๋๋ฅผ ์ฌ์ ์ํ ๋ ๋ฐ๋์ ์ง์ผ์ผ ํ๋ ์ผ๋ฐ ๊ท์ฝ 5๊ฐ์ง๋ฅผ ์์ธํ ์์๋ณด์๋ค.
ํด๋น ๊ธ์ ๋ฐฑ๊ธฐ์ ๋์ '์ดํํฐ๋ธ ์๋ฐ ์๋ฒฝ ๊ณต๋ต'์ ์๊ฐํ๊ณ ์์ฑํ ๊ฒ์ ๋๋ค.