item 10 huisoo - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

equals๋Š” ์ผ๋ฐ˜ ๊ทœ์•ฝ์„ ์ง€์ผœ ์žฌ์ •์˜ํ•˜๋ผ

  • equals ๋ฉ”์„œ๋“œ๋Š” Object์˜ ์ •๋ณด๋“ค์— ๋Œ€ํ•ด ๋™๋“ฑ์„ฑ์„ ๋น„๊ตํ•˜๋Š” ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
  • equals ๋ฉ”์„œ๋“œ๋ฅผ ์ž˜๋ชป ์ž‘์„ฑํ•˜๋ฉด ์ž˜๋ชป๋œ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

equals๋ฅผ ์žฌ์ •์˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

  • ๊ฐ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ณธ์งˆ์ ์œผ๋กœ ๊ณ ์œ ํ•˜๋‹ค

๊ฐ’์ด ์•„๋‹Œ ๋™์ž‘์„ ๊ฐœ์ฒด๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ (ex. Thread)

  • ๋…ผ๋ฆฌ์  ๋™์น˜์„ฑ(p->q, ~q->~p)์„ ํ™•์ธ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์›์น˜ ์•Š๊ฑฐ๋‚˜ ์• ์ดˆ์— ํ•„์š”์น˜ ์•Š๋‹ค๊ณ  ํŒ๋‹จ ํ•  ์ˆ˜ ์žˆ๋‹ค(๊ธฐ๋ณธ equals๋กœ ํ•ด๊ฒฐ).

  • ์ƒ์œ„ํด๋ž˜์Šค์—์„œ ์žฌ์ •์˜ํ•œ equals๊ฐ€ ํ•˜์œ„ ํด๋ž˜์Šค์—๋„ ์ ์šฉ๋œ๋‹ค.

set, Map, List์˜ ๊ฒฝ์šฐ Abstract(Type)์˜ equals๋ฅผ ์“ด๋‹ค.

  • ํด๋ž˜์Šค๊ฐ€ private, package-private์—ฌ์„œ equals๋ฅผ ํ˜ธ์ถœํ•  ์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ
  • ์‹ฑ๊ธ€ํ„ด์„ ๋ณด์žฅํ•˜๋Š” ํด๋ž˜์Šค(์ธ์Šคํ„ด์Šค ํ†ต์ œ ํด๋ž˜์Šค, Enum(์—ด๊ฑฐํƒ€์ž…))์ธ ๊ฒฝ์šฐ- ๊ฐ์ฒด ๊ฐ„ ๋™๋“ฑ์„ฑ, ๋™์ผ์„ฑ์ด ๋ณด์žฅ๋œ๋‹ค.

equals๋ฅผ ์žฌ์ •์˜ํ•˜๋Š” ๊ฒฝ์šฐ 5๊ฐ€์ง€ ์ผ๋ฐ˜ ๊ทœ์•ฝ

1. ๋ฐ˜์‚ฌ์„ฑ

null์ด ์•„๋‹Œ ๋ชจ๋“  ์ฐธ์กฐ ๊ฐ’ x์— ๋Œ€ํ•ด x.equals(x)๋ฅผ ๋งŒ์กฑํ•ด์•ผํ•œ๋‹ค. ์ž๊ธฐ ์ž์‹๊ณผ ๋น„๊ตํ–ˆ์„ ๋•Œ ๊ฐ™์•„์•ผ ํ•œ๋‹ค.

2. ๋Œ€์นญ์„ฑ

null์ด ์•„๋‹Œ ๋ชจ๋“  ์ฐธ์กฐ ๊ฐ’ x,y์— ๋Œ€ํ•ด x.equals(y)๊ฐ€ true ์ด๋ฉด y.equals(x) true๋ฅผ ๋งŒ์กฑํ•ด์•ผํ•œ๋‹ค.

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;
  }
}
  • ์œ„ ์ฝ”๋“œ ์‹คํ–‰์‹œ x.equals(y)๋Š” true์ด์ง€๋งŒ y.equals(x)๋Š” false๋กœ ๋Œ€์นญ์„ฑ์„ ์œ„๋ฐ˜ํ•œ๋‹ค.
  • String ํด๋ž˜์Šค์—์„œ๋Š” CaseInsentiveString ํด๋ž˜์Šค๋ฅผ ๋ชจ๋ฅธ๋‹ค.
@Override
public boolean equals(Object o) {
  return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
//ํ˜•๋ณ€ํ™˜์„ ํ†ตํ•ด ๊ฐ„๋‹จํžˆ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

3. ์ถ”์ด์„ฑ

null์ด ์•„๋‹Œ ๋ชจ๋“  ์ฐธ์กฐ ๊ฐ’ x,y,z์— ๋Œ€ํ•ด x.equals(y)๊ฐ€ true์ด๊ณ , y.equals(z)๊ฐ€ true์ด๋ฉด x.equals(z)๋„ true๋ฅผ ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค.

์˜ˆ์‹œ) Pointํด๋ž˜์Šค์™€ ColorPointํด๋ž˜์Šค(ColorPoint๋Š” Pointํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•œ ํด๋ž˜์Šค์ด๋‹ค.)

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 this.x == p.x && this.y == p.y;
  }
}
//equals ๋ฉ”์„œ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ์ƒ‰์ƒ ์ •๋ณด๋Š” ๋ฌด์‹œํ•œ ์ฑ„ ๋น„๊ต๋ฅผ ํ•œ๋‹ค.
//color ์ •๋ณด๋ฅผ ๋†“์น˜๋‹ˆ ๋ฐ›์•„ ๋“ค์ผ ์ˆ˜ ์—†๋‹ค.

class ColorPoint extends Point {
  
  private final Color color;

  @Override
  public ColorPoint(int x, int y, Color color) {
    super(x,y);
    this.color = color;
  }
}
  • ๋Œ€์นญ์„ฑ ์œ„๋ฐฐ ์ฝ”๋“œ
class ColorPoint extends Point {
  
  private final Color color;

  @Override
  public boolean equals(Object o) {
    if(!(o instanceof ColorPoint)) return false;
    
    return super.equals(o) && this.color == ((ColorPoint) o).color;
  }
}


Point p = new Point(1,2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);

// Point๋ฅผ ColorPoint์— ๋น„๊ตํ•œ ๊ฒฐ๊ณผ์™€ ColorPoint๋ฅผ Point์™€ ๋น„๊ตํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค.
System.out.println(p.equals(cp)); //true --> ์ƒ‰์ƒ์„ ๋ฌด์‹œํ•จ
System.out.println(cp.equals(p)); //false -->ํด๋ž˜์Šค๊ฐ€ ๋‹ค๋ฅด๋‹ค๋ฉฐ false
  • ์ถ”์ด์„ฑ ์œ„๋ฐ˜ Case
class ColorPoint extends Point {
  
  private final Color color;

  @Override
  public boolean equals(Object o) {
    if(!(o instanceof Point)) return false;

    //o๊ฐ€ ์ผ๋ฐ˜ Point์ด๋ฉด ์ƒ‰์ƒ์„ ๋ฌด์‹œํ–๊ณ  x,y์ •๋ณด๋งŒ ๋น„๊ตํ•œ๋‹ค.
    if(!(o instanceof ColorPoint)) return o.equals(this);
    
    //o๊ฐ€ ColorPoint์ด๋ฉด ์ƒ‰์ƒ๊นŒ์ง€ ๋น„๊ตํ•œ๋‹ค.
    return super.equals(o) && this.color == ((ColorPoint) o).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(p3)); //true  --> ์ƒ‰์ƒ ๋ฌด์‹œ
System.out.println(p1.equals(cp3)); //false  --> ์ถ”์ด์„ฑ์ด ๊นจ์กŒ๋‹ค. ์ƒ‰์ƒ๊นŒ์ง€ ๊ณ ๋ ค(Red != BLUE)


  • ๋ฌดํ•œ์žฌ๊ท€ ๋ฐœ์ƒ case
class SmellPoint extends Point {
  
  private final Smell smell;

  @Override
  public boolean equals(Object o) {
    if(!(o instanceof Point)) return false;

    //o๊ฐ€ ์ผ๋ฐ˜ Point์ด๋ฉด ์ƒ‰์ƒ์„ ๋ฌด์‹œํ–๊ณ  x,y์ •๋ณด๋งŒ ๋น„๊ตํ•œ๋‹ค.
    if(!(o instanceof SmellPoint)) return o.equals(this);
    
    //o๊ฐ€ ColorPoint์ด๋ฉด ์ƒ‰์ƒ๊นŒ์ง€ ๋น„๊ตํ•œ๋‹ค.
    return super.equals(o) && this.smell == ((SmellPoint) o).smell;
  }
}

Point p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new SmellPoint(1, 2, Smell.SWEET);

System.out.println(p1.equals(p2)); // StackOverflow

//StackOverflow ์ด์œ 
/*p1๋Š” ColorPoint ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค์ด๋‹ค.
p1.equals(p2) ์ฝ”๋“œ๋Š” ColorPoint์˜ equals ๋ฉ”์„œ๋“œ๋ฅผ ํƒ„๋‹ค.
ColorPoint์˜ 2๋ฒˆ์งธ if์— ๊ฑธ๋ฆฐ๋‹ค(o๋Š” SmellPoint ํƒ€์ž…์ด๊ธฐ ๋•Œ๋ฌธ์—, SmellPoint๋Š” Point์ด์ง€๋งŒ ColorPoint๋Š” ์•„๋‹˜)
o๊ฐ€ SmellPoint์ด๋ฏ€๋กœ SmellPoint์˜ equals๋ฅผ ํƒ„๋‹ค.
๋‹ค์‹œ ๋‘ ๋ฒˆ์งธ if์—์„œ ๊ฑธ๋ฆฐ๋‹ค.
๋˜ ๋‹ค์‹œ ColorPoint์˜ equals๋ฅผ ํƒ„๋‹ค.
๋ฌดํ•œ ์žฌ๊ท€๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.*/

๊ฐ์ฒด์ƒ์„ฑ๊ฐ€๋Šฅ(instantiable)๋ฅผ ์ƒ์†ํ•˜์—ฌ ์ƒˆ๋กœ์šด ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด equals ๊ทœ์•ฝ์„ ์–ด๊ธฐ์ง€ ์•Š์„ ๋ฐฉ๋ฒ•์ด ์—†๋‹ค. ์ƒ์†์„ ์ด์šฉํ•  ๊ฒฝ์šฐ ๋™์น˜๊ด€๊ณ„๊ฐ€ ๊นจ์ง„๋‹ค. (getClass๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค.)

  • ๋ฆฌ์Šค์ฝ”ํ”„ ์น˜ํ™˜ ์›์น™

์–ด๋–ค ํƒ€์ž…์— ์žˆ์–ด ์ค‘์š”ํ•˜๋‹ค๋ฉด, ๊ทธ ํ•˜์œ„ ํƒ€์ž…์—์„œ๋„ ์ค‘์š”ํ•˜๋‹ค. ์ž์‹ ํด๋ž˜์Šค๋Š” ๋ถ€๋ชจ ํด๋ž˜์Šค์—์„œ ๊ฐ€๋Šฅํ•œ ํ–‰์œ„๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๋ถ€๋ชจ.equals(์ž์‹) = false

class Point {
  
  private final int x;
  private final int y;

  private static final Set<Point> unitCircle = Set.of(new Point(0, -1),
   new Point(0, 1),
   new Point(-1, 0),
   new Point(1, 0)
  );

  public static boolean onUnitCircle(Point p) {
    return unitCircle.contains(p);
  }

  @Override
  public boolean equals(Object o) {
    if(o == null || o.getClass() != this.getClass()) {
      return false;
    }

    Point p = (Point) o;
    return this.x == p.x && this.y = p.y;
  }
}
//๊ฐ™์€ ๊ตฌํ˜„ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด์™€ ๋น„๊ตํ•  ๋•Œ๋งŒ true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ์ƒ์†๋Œ€์‹  ์ปดํฌ์ง€์…˜์„ ์‚ฌ์šฉํ•˜๋ผ ์ƒ์† ๋Œ€์‹ ์— Point ๋ณ€์ˆ˜๋ฅผ ๊ฐ–๋„๋ก ๊ตฌ์„ฑํ•œ๋‹ค.
public ColorPoint {
  private Point point;
  private Color color;

  public ColorPoint(int x, int y, Color color) {
   **** this.point = new Point(x, y);
    this.color = Objects.requireNonNull(color);
  }

  ****public Point asPoint() {
    return this.point;
  }

  @Override
  public boolean equals(Object o) {
    if(!(o instanceof ColorPoint)) {
      return false;
    }
    ColorPoint cp = (ColorPoint) o;
    return this.point.equals(cp) && this.color.equals(cp.color);
  }
}

4. ์ผ๊ด€์„ฑ

null์ด ์•„๋‹Œ ๋ชจ๋“  ์ฐธ์กฐ๊ฐ’ x,y์— ๋Œ€ํ•ด x.equals(y)๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ํ˜ธ์ถœํ•˜๋ฉด ํ•ญ์ƒ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ํ•ญ์ƒ false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.(๋ถˆ๋ณ€๊ฐ์ฒด ์—ฌ๋ถ€)

5. null์•„๋‹˜

null์ด ์•„๋‹Œ ๋ชจ๋“  ์ฐธ์กฐ๊ฐ’ x์— ๋Œ€ํ•ด x.equals(null)์€ false์ด๋‹ค.

์š”์•ฝ equals ๋ฉ”์„œ๋“œ ๊ตฌํ˜„๋ฐฉ๋ฒ•

  1. == ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•ด ์ž๊ธฐ ์ž์‹ ์˜ ์ฐธ์กฐ์ธ์ง€ ํ™•์ธํ•˜๋ผ

์ž๊ธฐ์ž์‹ ์ด๋ฉด true

  1. instanceof ์—ฐ์‚ฐ์ž๋กœ ์ž…๋ ฅ์ด ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์ธ์ง€ ํ™•์ธํ•˜๋ผ

null ๊ฒ€์‚ฌ

  1. ์ž…๋ ฅ์„ ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์œผ๋กœ ํ˜•๋ณ€ํ™˜ํ•˜๋ผ.
  2. ์ž…๋ ฅ ๊ฐ์ฒด์™€ ์ž๊ธฐ ์ž์‹ ์˜ ๋Œ€์‘๋˜๋Š” ํ•ต์‹ฌ ํ•„๋“œ๋“ค์ด ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š”์ง€ ํ•˜๋‚˜์”ฉ ๊ฒ€์‚ฌํ•˜๋ผ.

๊ธฐ๋ณธ ํƒ€์ž… ํ•„๋“œ๋Š” ==์—ฐ์‚ฐ์ž๋กœ ๋น„๊ต ์ฐธ์กฐํƒ€์ž… ํ•„๋“œ๋Š” equals()๋กœ ๋น„๊ต float์€ Float.compare(float,float), double์€ Double.compare(double,double)๋กœ ๋น„๊ต, Float.NaN, -0.0f ๋•Œ๋ฌธ

  1. ์„ฑ๋Šฅ์„ ์œ„ํ•ด ๋‹ค๋ฅผ ๊ฐ€๋Šฅ์„ฑ์ด ๋” ํฌ๊ฑฐ๋‚˜, ๋น„์šฉ์ด ์‹ผ ํ•„๋“œ๋ฅผ ๋จผ์ € ๋น„๊ตํ•˜๋ผ.

** ์œ„ ๊ตฌํ˜„๋ฐฉ๋ฒ•์œผ๋กœ ๋‹ค ๊ตฌํ˜„ํ–ˆ๋‹ค๋ฉด, ๋Œ€์นญ์„ฑ/์ถ”์ด์„ฑ/์ผ๊ด€์„ฑ์„ ํ™•์ธํ•œ๋‹ค. ** ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด ๋Œ๋ ค๋ณด๊ฑฐ๋‚˜ AutoValue ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์ž. ** ์‚ฌ๋žŒ < IDE < AutoValue

์ฃผ์˜์‚ฌํ•ญ

  • equals๋ฅผ ์žฌ์ •์˜ํ•  ๋• hashCode๋„ ๋ฐ˜๋“œ์‹œ ์žฌ์ •์˜ํ•˜์ž(Item 11)
  • ๋„ˆ๋ฌด ๋ณต์žกํ•˜๊ฒŒ ํ•ด๊ฒฐํ•˜๋ ค ํ•˜์ง€ ๋ง์ž.
  • ํ•„๋“œ์˜ ๋™์น˜์„ฑ๋งŒ ๊ฒ€์‚ฌํ•ด๋„ equals ๊ทœ์•ฝ์„ ์–ด๋ ต์ง€ ์•Š๊ฒŒ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ณ„์นญ(alias)๋Š” ๋น„๊ตํ•˜์ง€ ์•Š๋Š”๊ฒŒ ์ข‹๋‹ค.
  • object ์™ธ์˜ ํƒ€์ž…์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š” equals ๋ฉ”์„œ๋“œ๋Š” ์„ ์–ธํ•˜์ง€ ๋ง์ž.
//์ž˜๋ชป๋œ ์˜ˆ - ์ž…๋ ฅ ํƒ€์ž…์€ ๋ฐ˜๋“œ์‹œ Object ์—ฌ์•ผ ํ•œ๋‹ค!
//์ปดํŒŒ์ผ ๋˜์ง€ ์•Š์Œ, Object.equals๊ฐ€ ์•„๋‹˜์œผ๋กœ ๋‹ค์ค‘์ •์˜ ํ•œ ๊ฒƒ์ด๋‹ค.
@Override public boolean equals(MyClass o){
    ....
}

๊ฒฐ๋ก 

๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด equals๋ฅผ ์žฌ์ •์˜ํ•˜์ง€ ๋ง์ž. ๋งŽ์€ ๊ฒฝ์šฐ Object์˜ equals๊ฐ€ ๋น„๊ต๋ฅผ ์ •ํ™•ํžˆ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์žฌ์ •์˜ ํ•  ๋•Œ๋Š” ๊ทธ ํด๋ž˜์Šค์˜ ํ•ต์‹ฌํ•„๋“œ ๋ชจ๋‘๋ฅผ ๋น ์ง์—†์ด, ๋‹ค์„ฏ๊ฐ€์ง€ ๊ทœ์•ฝ์„ ํ™•์‹คํžˆ ์ง€์ผœ๊ฐ€๋ฉฐ ๋น„๊ตํ•œ๋‹ค.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ