[์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ”] Item10. equals๋Š” ์ผ๋ฐ˜ ๊ทœ์•ฝ์„ ์ง€์ผœ ์žฌ์ •์˜ํ•˜๋ผ. (๋Œ€์นญ์„ฑ, ๋ฐ˜์‚ฌ์„ฑ, ์ถ”์ด์„ฑ)

2023. 1. 26. 15:11ใ†JAVA/Effective JAVA

728x90
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 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90