Object
클래스는 모든 클래스의 조상이다.
특징으로는 java 에 존재하는 모든 클래스는 Object 클래스를 상속하고있다.
오늘은 Object
의 메소드 중에 equals()
와 hashCode()
를 다루겠다 🫡
동일성 vs 동등성 🤔
이 둘의 차이를 간단히 말하자면 동등성은 ==
연산을 했을때 같은 것이고, 동일성은 우리가 같은 값이라고 정의한 것, 즉 equals()
메소드를 오버라이딩해서 비교하고 같은 값을 말한다
hashCode
란 해싱 알고리즘에 의해 반환된 정수 값,HashSet
과 같은 자료 구조에서 사용되는 Hash Table에서의 hashing을 가능하게 하는 것을 목적
HashSet
같은 자료구조에서 같은 값인지 판별할 때는 hashCode()
의 결과값을 capacity(용량) 으로 % 모듈러 연산한 값을 비교한 후 equals()
를 통해서 비교한다.hashCode()
로 먼저 비교하는 이유는 hashCode()
가 equals()
보다 가벼우며 이를 통해서 비교할 값의 후보군을 확 줄일 수 있기 때문이다 👍아래 코드로 HashSet의 예를 보겠다 👀
@AllArgsConstructor
public class Good {
int a;
String b;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Good good = (Good)obj;
return a == good.a && Objects.equals(b, good.b);
}
@Override
public int hashCode() {
return Objects.hash(a, b);
}
}
-> 여기서 오버라이딩한 equals()
는 a, b 값이 같다면 동등함을 나타내고 hashCode() 는 같은 a, b 가 같다면 같은 해시코드를 리턴한다.
위 코드를 아래 코드로 적용해보자 👊
public class Hello {
public static void main(String[] args) {
Good good1 = new Good(1, "a");
Good good2 = new Good(1, "a");
Set<Good> set = new HashSet<>();
set.add(good1);
set.add(good2);
System.out.println("set size : " + set.size());
}
}
결과는 아래와 같다
equals()
, hashCode()
에 의해 두 객체가 같다고 판별되어 사이즈가 1인걸 볼 수 있다.
하지만 여기서 hashCode
메소드를 주석처리하면!
equals()
가 동작하더라도 hashCode()
에서 다른 값이라고 판별되기 때문에 1의 사이즈가 나오는 것을 볼 수 있다.
이번에는 객체지향적인 설계에 있어서 강한 응집력, 약한 결합력 그리고 객체의 확실한 역할에 대해서 예시를 통해 알아보겠다.
지난 #1 포스팅의 Enum 활용과 이어지는 예다.
//Command Class
@Getter
@AllArgsConstructor
public class AppendCommand {
private AppendType appendType;
private String target;
private String source;
@RequiredArgsConstructor
public enum AppendType {
FRONT((a, b) -> b.concat(a)),
END(String::concat),
;
private final BiFunction<String, String, String> appender;
public String append(String str1, String str2) {
return this.appender.apply(str1, str2);
}
}
}
위 코드는 두 target, source
을 통해서 apendType
이 FRONT
면 source 앞에 target 을 붙이고 END 면 source 뒤에 target 을 붙이는 간단한 예시다.
해당 클래스를 사용하려면 클래스를 사용하는 Client
는 아래와 같은 코드를 가져야한다.
public class Client {
public String executeCommand(AppendCommand command) {
AppendType appendType = command.getAppendType();
String source = command.getSource();
String target = command.getTarget();
return appendType.append(source, target);
}
}
위 코드의 문제점은 무엇일까? 🤔
나는 아래 두 가지 문제점을 대표적으로 생각했다.
AppendCommand
의 역할이 명확하지 않다Client
가AppendCommand
에 강하게 결합되어있다.
1번 문제에 대해 설명하자면 AppendCommand
라는 클래스를 설계 할 때는 분명 문자열을 append
해주는 역할을 수행하는 클래스로 설계했을 것이다.
하지만 실제로 AmmendCommand
는 append
를 해주는 역할을 명확하게 수행하지 않는다. 직접적으로 해당 역할을 수행하지 않고 append
하는데 필요한 재료들만 들고있는 셈이다.
다음으로 2번 문제에 대해 설명하자면 Client
는 AppendCommand
라는 객체만 들고있을 뿐이며, 그저 들고있는 AppendCommand
를 통해서 append
를 하고싶을 뿐이지 AppendCommand
가 들고있는 속성들에 대해서는 궁금하지 않다.
하지만 위 코드는
AppendType appendType = command.getAppendType();
String source = command.getSource();
String target = command.getTarget();
이런식으로 AppendCommand
의 속성을 가져오며 부수적인 속성들과 결합이 추가된다.
이렇게되면 AppendCommand
의 속성 혹은 호출 방법, 기타 등등 변화가 생긴다면 Client
도 코드를 수정해야하는 문제가 생긴다.
해결방법 💡
해결방법은 간단하다! 😀
불필요한 Getter
를 제거하고 AppendCommand
의 역할을 확실히 수행할 메소드를 만들어주면 된다.
해결방법을 적용하면
//Before
@Getter
@AllArgsConstructor
public class AppendCommand {
private AppendType appendType;
private String target;
private String source;
}
//After
@AllArgsConstructor
public class AppendCommand {
private AppendType appendType;
private String target;
private String source;
public String execute() {
return appendType.append(target, source);
}
}
→ Getter
를 제거하고 execute
라는 append
를 수행하는 메소드를 정의했다.
이로 인해 Client
에서는
//Before
public class Client {
public String executeCommand(AppendCommand command) {
AppendType appendType = command.getAppendType();
String source = command.getSource();
String target = command.getTarget();
return appendType.append(source, target);
}
}
//After
public class Client {
public String executeCommand(AppendCommand command) {
return command.execute();
}
}
→ AppendCommand
의 속성을 사용해 append
를 호출하는 코드 제거
이렇게 변경되면 Client
는 AppendCommand
와의 결합력이 약해지며 AppendCommand
의 응집도는 올라가고 역할 또한 명확해진다!! 😄
결과적으로 코드 가독성 또한 좋아지고 유지보수 또한 유리해지며 객체지향적 코드에 가까워질 수 있다 👍