equals()
로 비교해서 일치하는 값만 반환객체에 대한 해시 코드 값을 반환.
해당 정수 값은 JVM 구현에 따라 달라진다.
Java 17 기준 Object 의 hashCode 설명
한 Java 어플리케이션의 실행 동안 동일한 객체에 두 번 이상 호출될 때, hashCode 메소드는 객체의 equals 비교에 사용되는 정보가 수정되지 않았다면 일관성 있게 동일한 정수를 반환해야 한다. 이 정수는 어플리케이션의 한 실행에서 다른 실행으로 일관성을 유지할 필요는 없다.
두 객체가 equals 메소드에 따라 같으면, 두 객체 각각에 hashCode 메소드를 호출하면 동일한 정수 결과를 생성해야 한다.
두 객체가 equals 메소드에 따라 다르다면, 두 객체 각각에 hashCode 메소드를 호출하면 서로 다른 정수 결과를 생성할 필요는 없다. 그러나, 프로그래머는 equals로 다른 객체에 대해 서로 다른 정수 결과를 생성하면 해시 테이블의 성능이 향상될 수 있다는 것을 알아야 한다.
HashMap 이나 HashSet은 hashCode()
로 먼저 해시값이 같은지 비교하고, 같고 후보가 여러개면 equals()
로 한번 더 비교하는 프로세스로 진행되는 것이다.
그렇기 때문에 hashCode()
,equals()
를 오버라이딩 해주지 않으면 객체 내부 필드 값이 다 같더라도 다른 값으로 취급한다.
레퍼런스 변수를 String 과 +
연산 할때!
Object 기본 toString
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
🤔 이해가 안가는 case
Integer a = null;
System.out.println("하이" + a);
// 출력 : "하이null"
레퍼런스 변수를 String 과 +
연산 할때 toString 을 호출한다는데 왜 이경우에는 npe 안남?? a.toString() 하면 npe 나야 되잖아!!!!!
이유는?
자바에서는 피연산자 중 하나가 문자열일 경우 나머지 피연산자도 문자열로 자동 변환해 문자열 결합 연산을 수행한다. 이때 문자열을 자동 변환하기 위해 String.valueof()
메소드를 사용한다.
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
여기서는 깔꼬롬 하게 null 이면 문자열 "null" 을 반환해 줌을 알 수 있다.
Tip.
String 은 내부 문자열을 수정 할 수 없기에 +
연산자 작업을 할때 새로운 객체를 생성하게 된다. 그렇기에 새로운 객체가 계속 생성,파기를 반복해서 효율성이 좋지 않다. 그래서 Java에서는 문자열 연결 연산자(+
)가 사용될 때, 컴파일러는 이를 최적화하기 위해 내부적으로 StringBuilder를 사용하는 코드로 변환한다.
Java Language Specification (JLS)의 섹션 15.18.1에 따르면:
"The Java programming language does not have an operator for string concatenation, but the + and += operators perform string concatenation when given an operand of type String (§4.3.3). The string concatenation operator is syntactic sugar for StringBuilder (or StringBuffer) operations."
즉, 문자열 연결 연산자는 문법적 설탕(syntactic sugar)이며, 실제로 StringBuilder나 StringBuffer 작업을 하고 있는 것을 알 수 있다.
=> 가독성 좋은 코드를 위해 많이 사용하자!
사용 이유 :
코드 간의 응집성, 연관관계를 명확하게 하기 위해서.
// A 코드
var a = method();
if(a ==null) new RuntimeException();
// B코드
var a = method().orElseThrow(RuntimeException::new);
// 메소드 참조 문법
A코드는 첫줄과 두번째 줄 사이에 다른 코드가 들어 갈 수도 있기에, 두 코드 간의 연관성이 희석된다.
Optional 안티패턴 (추가예정)
Optional<String> a = method();
if(a.isPresent()) return a;
옵셔널을 사용해도, 값을 가져오는 코드와 null 검사를 하는 코드가 분리되어 있다. 이런 경우 옵셔널 사용 의미가 없다. 나가리다.
🙏 주의
orElse(T other): 값이 존재하면 그 값을 반환하고, 그렇지 않으면 other를 반환합니다.
-> 파라미터에 표현문이 전달 되면, 값의 null 유무에 상관 하지 않고 표현문 실행.
orElseGet(Supplier<? extends T> other): 값이 존재하면 그 값을 반환하고, 그렇지 않으면 Supplier에서 제공하는 값을 반환합니다.
-> 값이 null 이 아니면 함수 실행하지 않음.
보통 if문은
1. 유효성 검사를 하거나 2. 각각 다른 프로세스를 거치게 하기 위해 사용한다.
1번의 경우는 유효성 검사를 해당 객체 생성 시점에 하는 것이 좋고,
2번의 경우는 다른 프로세스를 적용할 분기를 나눌 책임을 기준이 되는 객체 안으로 넣는 것이 좋다.
하지만 1,2 번 모두 비즈니스 로직이 깊게 연관 되는 경우 if 문으로 처리하는게 나을 수도 있다.
🤔 만약 게시글의 글쓴이 type 을 검사해 글을 쓸 수 있는지 검사하는 로직이라면? (내 코드에 적용하기)
1 2 3 4 5 6 7 8 9 10 | public UserCreateLostResponse postLost(Integer userId, UserLostCreateRequest lostRequest) { if (lostRequest.getWriterTypeInEnum() == WriterType.USER) { if (!lostRequest.getUserId().equals(userId)) { ... } } else { throw new CustomException(INVALID_AUTHORITY, "This is not a user post"); } ... } | cs |
서비스 로직 내부에서 if/else 문으로 유효성 검사를 진행 하는 것.
1 2 3 4 5 6 7 8 | public UserLostCreateRequest(WriterType writerType, ...) { if(!writerType.equals(WriterType.USER)) { throw new CustomException(INVALID_AUTHORITY, "This is not a user post"); } ... this.writerType = writerType; ... } | cs |
객체 생성 과정에서 생성자 내부에서 유효성 검사를 진행 하는 것.
후자 처럼 if 문을 서비스 코드에서 제거 할 수 있다.
결합도 : 서로의 변화에 영향을 많이 받는 정도
응집도 : 비슷한 것 끼리 모여 있는 정도
-> 우리는 결합도는 낮으며 응집도는 높은 코드를 짜야한다.
=> 변경에 영향을 적게 받는 유지보수하기 좋은 코드.
getter 는 클래스의 필드를 그대로 노출 시켜 캡슐화를 깰 수 있다.
그렇기에 필드 값 자체가 필요한 것이 아니라면, getter를 없애고 해당 필드 값을 사용해 연산을 하는 함수 안에서 필드 값을 사용하는 방식을 취해야 한다.
즉, 필드 얻기 따로 계산 로직 따로 = 응집력이 낮은 코드!
-> 하지만 현실적으로 getter 없이 개발하기는 힘들다..
우선 나는 Entity 같은 도메인 모델에서는 setter 을 쓰지 않는다.
dto 나 다른 객체에서도 코드 외부에서는 setter 을 쓰지 않고, 객체 내부 메소드에서 해결하려고 한다.
이유 : 넘 웃기고 이해가 쏙쏙 되는 setter 쓰면 안되는 이유
오늘의 느낀점 : 객체지향적 프로그래밍을 하기 위해서는, 연관관계를 명확하게 하는 것과 실수 할 수 있는 모든 기회 말살, 정확한 책임 유무 판단을 잘해야 하는 것 같다. 앞으로 객체 감수성이 뛰어난 사람이 되기 위해 아자아자 파이팅!