Object 클래스의 equals()는 매개변수로 받은 참조변수의 값(객체의 주소)을 비교하여 같은 객체인지 아닌지를 boolean 값으로 반환하는 메서드이다.
비교하고 싶은 인스턴스 변수(iv)끼리 비교하도록 equals() 메서드를 오버라이딩해서 사용하면 된다.
class Value {
int value;
Value () {};
Value (int value) {
this.value = value;
}
public boolean equals(Object obj) { // 오버라이딩 안하면 객체의 주소를 비교
// 참조변수의 형변환 전에는 반드시 instanceof로 확인해야한다.
if(!(obj instanceof Value)) return false;
Value v = (Value) obj;
return this.value == v.value;
}
}
instanceof 연산자는 형변환이 가능한지 확인하는 연산자이다. 참조변수의 형변환은 상속관계에서만 가능하다. 상속관계가 아닌 다른 참조변수가 매개변수로 들어온다면 비교할 필요 없이 false를 반환하면 된다.
메서드 오버라이딩은 선언부가 같아야되므로 아래와 같이 정의하면 Object 클래스의 equals()를 오버라이딩한 게 아니라 그냥 별개의 메서드를 정의한 게 되므로 주의하자.
public boolean equals(Value v) {
return this.value == v.value;
}
2. hashCode()
Object 클래스의 hashCode() 메서드는 객체의 주소를 int로 변환한 해시코드를 만들어 반환하는 함수이다.
equals() 메서드를 오버라이딩하면 hashCode() 메서드 또한 오버라이딩해야 한다.
class Card {
String kind;
int number;
static int width = 100;
static int height = 250;
Card () {
this("SPADE", 1);
}
public Card(String kind, int number) {
this.kind = kind;
this.number = number;
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Card)) return false;
Card c = (Card) obj;
return this.kind.equals(c.kind) && this.number == c.number;
}
@Override
public int hashCode() {
return Objects.hash(kind, number);
}
}
* String 클래스에는 이미 equals()가 문자열의 내용끼리 비교하도록 오버라이딩되어있기 때문에 String 타입의 변수 kind는 equals()로 비교한다.
* Objects.hash() : 여러 객체의 해시 코드를 결합하여 새로운 해시 코드를 생성하는 메서드이다. 가변 인자를 받으므로 여러 개의 필드를 넣어도 된다. Object가 아닌 Objects임에 유의하자
BidProductResponseDto가 생성될 때 경매 상품의 제시자가 아무도 없어 topBid가 null인 경우 NPE가 발생했다.
public BidProductResponseDto(BidProduct bidProduct) {
id = bidProduct.getId();
name = bidProduct.getName();
description = bidProduct.getDescription();
startPrice = bidProduct.getStartPrice();
expirationPeriod = bidProduct.getExpirationPeriod();
feetSize = bidProduct.getFeetsize();
footSize = bidProduct.getFootsize();
footPicture = bidProduct.getFootpicture();
brand = new BrandResponseDto(bidProduct.getBrand());
topBid = new BidResponseDto(bidProduct.getTopBid());
status = bidProduct.getStatus();
createdAt = bidProduct.getCreatedAt();
updatedAt = bidProduct.getUpdatedAt();
for (int i = 0; i < bidProduct.getBids().size(); i++) {
bidResponseDtoList.add(new BidResponseDto(bidProduct.getBids().get(i)));
}
}
해결방안
입찰이 하나도 없을 때 빈 객체를 생성한 뒤, topBid의 제시자는 경매상품 출품자로, topBid의 가격은 경매 시작가로 설정한다.
public BidProductResponseDto(BidProduct bidProduct) {
this.id = bidProduct.getId();
this.name = bidProduct.getName();
this.author = bidProduct.getUser().getName();
this.description = bidProduct.getDescription();
this.startPrice = bidProduct.getStartPrice();
this.expirationPeriod = bidProduct.getExpirationPeriod();
this.feetSize = bidProduct.getFeetsize();
this.footSize = bidProduct.getFootsize();
this.footPicture = bidProduct.getFootpicture();
this.brand = new BrandResponseDto(bidProduct.getBrand());
// topBid 설정 (경매 제시가가 아직 없을 경우 처리)
if (bidProduct.getTopBid() != null) {
this.topBid = bidProduct.getTopBid();
} else {
// topBid가 없을 때 topBid 제시자는 경매상품 등록자로, 가격은 startPrice로 설정
this.topBid = new Bid();
this.topBid.setUser(bidProduct.getUser());
this.topBid.setBidPrice(bidProduct.getStartPrice());
}
this.status = bidProduct.getStatus();
this.createdAt = bidProduct.getCreatedAt();
this.updatedAt = bidProduct.getUpdatedAt();
this.bidResponseDtoList = bidProduct.getBids().stream()
.map(BidResponseDto::new)
.sorted(Comparator.comparing(BidResponseDto::getBidPrice))
.toList();
}
고찰할 점
@Table(name = "bidproduct")
public class BidProduct extends Timestamped{
...
/**
* 연관관계 - Foreign Key 값을 따로 컬럼으로 정의하지 않고 연관 관계로 정의합니다.
*/
@OneToMany(mappedBy = "bidProduct", cascade = CascadeType.REMOVE)
private List<Bid> bids = new ArrayList<>();
@OneToOne
@JoinColumn(name = "topBid")
private Bid topBid;
...
}
BidProduct와 topBid는 일대일 단방향 연관관계인데 topBid가 null인 경우를 (topBid의 price가 null인 경우, topBid의 user가 null인 경우 등등..) 메서드마다 매번 핸들링해줘야 했다. 아예 topBid의 초기값을 설정하는 게 더 나은 방법이었을까?