2023. 1. 26. 15:11ใJAVA/Effective JAVA
item10. equals๋ ์ผ๋ฐ ๊ท์ฝ์ ์ง์ผ ์ฌ์ ์ํ๋ผ.
equals ๋ฉ์๋๋ฅผ ์ฌ์ ์ํ ๋๋ ๋ฐ๋์ ์ผ๋ฐ ๊ท์ฝ์ ๋ฐ๋ผ์ผ ํ๋ค.
equals ๊ท์ฝ
1๏ธโฃ ๋ฐ์ฌ์ฑ reflexivity
: null ์ด ์๋ ๋ชจ๋ ์ฐธ์กฐ ๊ฐ x์ ๋ํด x.equals(x) == true ์ด๋ค.
๋จ์ํ ๋งํ๋ฉด ๊ฐ์ฒด๋ ์๊ธฐ ์์ ๊ณผ ๊ฐ์์ผ ํ๋ค๋ ๋ป์ด๋ค. ์ด ์๊ฑด์ ๋๋ถ๋ถ?์ด ์๋๋ผ ์์งํ ๊ฑฐ์ ๋ค ๋ง์กฑํ๋ ์กฐ๊ฑด์ผํ ๋ฐ ๋ง์ฝ ์ด ์๊ฑด์ ์ด๊ธด ํด๋์ค๊ฐ ์๋ค๋ฉด ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ์ปฌ๋ ์ ์ ๋ฃ์ ๋ค์ contains ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋ฐฉ๊ธ ๋ฃ์ ์ธ์คํด์ค๊ฐ ์๋ค๊ณ ๋ตํ ๊ฒ์ด๋ค.
2๏ธโฃ ๋์นญ์ฑ symmetry
: null ์ด ์๋ ๋ชจ๋ ์ฐธ์กฐ ๊ฐ x, y ์ ๋ํด x.equals(y) ๊ฐ true์ด๋ฉด y.equals(x)๋ true์ด๋ค.
x.equals(y) == y.equals(x)
๋์นญ์ฑ์ ์ฆ ๋ ๊ฐ์ฒด๋ ์๋ก์ ๋ํ ๋์น ์ฌ๋ถ์ ๋๊ฐ์ด ๋ตํด์ผ ํ๋ค๋ ๋ป์ด๋ค.
public class CaseInsensitiveString
{
private final String s;
public CaseInsensitiveString(String s)
{
this.s = Objects.requireNonNull(s);
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
}
if (obj instanceof String) {
return s.equalsIgnoreCase((String) obj);
}
return false;
}
public static void main(String[] args)
{
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String polish = "polish";
System.out.println("cis.equals(polish) = " + cis.equals(polish));
System.out.println("polish.equals(cis) = " + polish.equals(cis)); // A.equals(B) != B.equals(A) ๋์นญ์ฑ x
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
System.out.println("list.contains(polish) = " + list.contains(polish));
}
// ์์ ๋ equals
// @Override
// public boolean equals(Object obj)
// {
// return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString) obj).s.equalsIgnoreCase(s);
// }
}
-> CaseInsensitiveString ์ด ํด๋์ค๋ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ณํ์ง ์๋ ๋ฌธ์์ด์ ๊ตฌํํ ํด๋์ค์ด๋ค.
System.out.println("cis.equals(polish) = " + cis.equals(polish));
-> ์ด๋ ๊ฒ ํ๋ฉด true๋ฅผ ๋ฐํํ๊ฒ ์ง๋ง
System.out.println("polish.equals(cis) = " + polish.equals(cis));
-> String์ equals๋ CaseInsensitiveString ํด๋์ค์ ์กด์ฌ๋ฅผ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋น์ฐํ false๋ฅผ ๋ฐํํ๋ค.
public boolean equals(Object obj)
{
if (obj instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
}
if (obj instanceof String) {
return s.equalsIgnoreCase((String) obj);
}
return false;
}
๊ทธ๋ฌ๋ฏ๋ก ์ด equals๋ ๋์นญ์ฑ์ ์๋ฐํ๋ ์๋ชป๋ ๋ฉ์๋์ด๋ค.
@Override
public boolean equals(Object obj)
{
return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString) obj).s.equalsIgnoreCase(s);
}
์์ equals์์ ์์ ๋ equals ๋ฉ์๋์ด๋ค.
public static void main(String[] args)
{
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
CaseInsensitiveString cis2 = new CaseInsensitiveString("polish");
String polish = "polish";
System.out.println("cis.equals(polish) = " + cis.equals(polish));
System.out.println("polish.equals(cis) = " + polish.equals(cis)); // A.equals(B) != B.equals(A) ๋์นญ์ฑ x
System.out.println("cis.equals(cis2) = " + cis.equals(cis2));
System.out.println("cis2.equals(cis) = " + cis2.equals(cis));
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
System.out.println("list.contains(polish) = " + list.contains(polish));
}
equals๋ฅผ ์์ ํ ๋ค์ ์คํํ๋ฉด cis.equals(cis2) == cis2.equals(cis)๋ฅผ ๋ง์กฑํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ๋ํ cis.equals(polist) == false, polist.equals(cis) == false ๋ฐํ๋๋ ๊ฒ๋ ํ์ธํ ์ ์๋ค.
3๏ธโฃ ์ถ์ด์ฑ transitivity
: null ์ด ์๋ ๋ชจ๋ ์ฐธ์กฐ ๊ฐ x, y, z์ ๋ํด x.equals(y)๊ฐ true ์ด๊ณ , y.equals(z)๋ true์ด๋ฉด x.equals(z)๋ true์ด๋ค.
x.equals(y) && y.equals(z) => x.equals(z)
์ถ์ด์ฑ์ ์ด์ ์ฒซ๋ฒ์งธ ๊ฐ์ฒด์ ๋๋ฒ์งธ ๊ฐ์ฒด๊ฐ ๊ฐ๊ณ , ๋๋ฒ์งธ ๊ฐ์ฒด์ ์ธ๋ฒ์งธ ๊ฐ์ฒด๊ฐ ๊ฐ๋ค๋ฉด, ์ฒซ๋ฒ์งธ ๊ฐ์ฒด์ ์ธ๋ฒ์งธ ๊ฐ์ฒด๋ ๊ฐ์์ผ ํ๋ค๋ ๋ป์ด๋ค.
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 obj)
{
if (!(obj instanceof Point)) {
return false;
}
Point p = (Point) obj;
return p.x == x && p.y == y;
}
@Override
public int hashCode()
{
return 31 * x + y;
}
}
-> Point๋ 2์ฐจ์์์ ์ ์ ํํํ๋ ํด๋์ค์ด๋ค.
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 obj)
{
if (!(obj instanceof ColorPoint)) {
return false;
}
return super.equals(obj) && ((ColorPoint) obj).color == color;
}
}
๊ทธ๋ฆฌ๊ณ ColorPoint๋ Point๋ฅผ ํ์ฅํด์ ์์ ํ๋๋ฅผ ์ถ๊ฐํ ํด๋์ค์ด๋ค.
equals๋ฅผ ์ฌ์ ์ํด์ ์์น์ ์์์ด ๊ฐ์ ๋๋ง true๋ฅผ ๋ฐํํ๋๋ก ํ๋ค.
public static void main(String[] args)
{
// ์ฒซ๋ฒ์งธ equals๋ ๋์นญ์ฑ์ ์๋ฐฐํ๋ค.
Point point = new Point(1, 2);
ColorPoint colorPoint = new ColorPoint(1, 2, Color.RED);
System.out.println(point.equals(colorPoint) + " " + colorPoint.equals(point));
}
์ด ์ฝ๋๋ฅผ ์คํํด๋ณด๋ฉด treu, false๊ฐ ์ถ๋ ฅ๋๋๋ฐ
Point์ equals๋ ์์์ ๋ฌด์ํ๊ธฐ ๋๋ฌธ์ ๋น๊ตํ์ ๋ true๋ฅผ ๋ฐํํ์ง๋ง, ColorPoint์ equals๋ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ํด๋์ค ์ข ๋ฅ๋ง๋ค ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๋งค๋ฒ false๋ฅผ ๋ฐํํ ๊ฒ์ด๋ค.
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof Point)) {
return false;
}
// obj๊ฐ ์ผ๋ฐ Point์ด๋ฉด color๋ฅผ ๋ฌด์ํ๊ณ ๋น๊ตํ๋ค.
if (!(obj instanceof ColorPoint)) {
return obj.equals(this);
}
// obj๊ฐ ColorPoint์ด๋ฉด ์์๊น์ง ๋น๊ตํ๋ค.
return super.equals(obj) && ((ColorPoint) obj).color == color;
}
๊ทธ๋ ๋ค๋ฉด ์ด๋ฒ์ Point๋ผ๋ฉด ์์์ ๋ฌด์ํ๊ณ ๋น๊ตํ๋๋ก ์ค์ ํ๋ค.
๊ทธ๋ฆฌ๊ณ ColorPoint๋ผ๋ฉด ์์๊น์ง ๋น๊ตํ๋๋ก ์ค์ ํ๋ค.
public static void main(String[] args)
{
// ๋๋ฒ์งธ equals๋ ์ถ์ด์ฑ์ ์๋ฐฐํ๋ค.
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
p1.equals(p2) == true, p2.equals(p3) == true ๋ผ์ ์ถ์ด์ฑ์ ์๊ฐํ๋ฉด p1.equals(p3) == true ์ฌ์ผ ํ์ง๋ง ๊ฒฐ๊ณผ๋ false ์ด๋ค.
์ฆ, ์ถ์ด์ฑ์ด ๊นจ์ง๋ค. p1๊ณผ p2๋ ์์์ ๊ณ ๋ คํ์ง ์์์ง๋ง, p1์ p3 ๋น๊ตํด์๋ ์์๊น์ง ๊ณ ๋ คํ๊ธฐ ๋๋ฌธ์ด๋ค..!
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 obj)
{
if (obj == null || obj.getClass() != getClass()) {
return false;
}
Point p = (Point) obj;
return p.x == x && p.y == y;
}
@Override
public int hashCode()
{
return 31 * x + y;
}
}
์ด๋ฒ์๋ Point์ equals๋ฅผ instanceof ๋ก ๊ฒ์ฌํ๋๊ฒ ์๋ getClass๋ก ๊ฒ์ฌํ๋ฉด ๊ตฌ์ฒด ํด๋์ค๋ฅผ ํ์ฅํด ์๋ก์ด ๊ฐ์ ์ถ๊ฐํ๋ฉด์ 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();
}
}
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);
}
public static void main(String[] args)
{
Point p1 = new Point(1, 0);
Point p2 = new CounterPoint(1, 0);
System.out.println("onUnitCircle(p1) = " + onUnitCircle(p1)); // true ์ถ๋ ฅ
// true๋ฅผ ์ถ๋ ฅํด์ผ ํ์ง๋ง, Point์ equals๊ฐ getClass๋ฅผ ์ฌ์ฉํด ์์ฑ๋์๋ค๋ฉด false๋ฅผ ์ถ๋ ฅํ๋ค.
System.out.println("onUnitCircle(p2) = " + onUnitCircle(p2));
}
}
Point์ ํ์ ํด๋์ค์ธ CounterPoint๋ Point์ด๋ฏ๋ก ์ด๋์๋ Point๋ก์จ ํ์ฉ๋ ์ ์์ด์ผ ํ๋๋ฐ ์ง๊ธ์ onUnitCircle(p2)๋ฅผ ํ๋ฉด false๊ฐ ๋ฐํ๋๋ค. ์๋ํ๋ฉด Set์ ํฌํจํ์ฌ ๋๋ถ๋ถ์ ์ปฌ๋ ์ ์ containsํ ๋ equals ๋ฉ์๋๋ฅผ ์ด์ฉํ๋๋ฐ CounterPoint์ ์ธ์คํด์ค๋ Point์ ๊ฐ์ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof Point)) {
return false;
}
Point p = (Point) obj;
return p.x == x && p.y == y;
}
๋์ Point์ equals ๋ฅผ instanceOf ๊ธฐ๋ฐ์ผ๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ๋ค๋ฉด onUnitCirCle ๋ฉ์๋๊ฐ ์ ๋๋ก ๋์ํ ๊ฒ์ด๋ค. (CounterPoint๋ ํ๋๋ฅผ ์ถ๊ฐํ์ง ์์๊ธฐ ๋๋ฌธ์)
์ฑ ์์๋ ์ธ๊ธํ๊ณ ์๋๋ฐ
๊ตฌ์ฒด ํด๋์ค๋ฅผ ํ์ฅํด ์๋ก์ด ๊ฐ์ ์ถ๊ฐํ๋ฉด์ equals ๊ท์ฝ์ ๋ง์กฑ์ํฌ ๋ฐฉ๋ฒ์ ์กด์ฌํ์ง ์๋๋ค!!
4๏ธโฃ ์ผ๊ด์ฑ consistency
: null ์ด ์๋ ๋ชจ๋ ์ฐธ์กฐ ๊ฐ x, y์ ๋ํด x.equals(y)๋ฅผ ๋ฐ๋ณตํด์ ํธ์ถํ๋ฉด ํญ์ true๋ฅผ ๋ฐํํ๊ฑฐ๋ ํญ์ false๋ฅผ ๋ฐํํ๋ค.
x.equals(y) == x.equals(y)
5๏ธโฃ null ์๋
: null ์ด ์๋ ๋ชจ๋ ์ฐธ์กฐ ๊ฐ x์ ๋ํด x.equals(null)์ false ์ด๋ค.
x.equals(null) == false