==와 equals 의 차이
== ) 동일성 비교 (identity) : 같은 주소값을 가진 객체인가 비교. (주소값 비교)
equals ) 동등성 비교(equality) : 같은 내용을 가진 객체인가. (내용 비교)
equals & hashCode
현재객체.equals(다른객체)
- 현재객체.필드명 이 다른객체.필드명 과 같은지 (다르면 return false)
equals를 제대로 사용하기 위해서는 Override가 필요하고, Override를 안했을 경우 obj의 equals를 쓰기 때문에 false가 나온다.
equals의 비교결과가 true라면, hashCode 값도 같아야 한다. 이 역시 오버라이딩이 필요하다.
특정 필드값(동등성 비교에 사용)기준으로 hashCode를 재생성한다.
보통 equals랑 hashCode 오버라이딩을 같이 한다. ===> '내용에 따른 식별자' 라고 보면 된다.
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Car))
return false;
Car other = (Car)obj;//Car 타입으로 변환
if(!this.carName.equals(other.carName))
return false;
if(!this.color.equals(other.color))
return false;
if(this.doorNum!=other.doorNum)//기본타입
return false;
return true;
}
Car mySonata=new Car("소나타","펄화이트",4);
System.out.println(mySonata); //소나타, 펄화이트, 문4개
//객체비교
Car yourSonata = new Car("소나타","펄화이트",4);
System.out.println(yourSonata);
System.out.println(mySonata==yourSonata);
System.out.println(mySonata.equals(yourSonata));
1번째줄은 false, 2번째줄은 true로 나온다.
@Override
public int hashCode() {
return Objects.hash(carName,color,doorNum);
}
System.out.println(mySonata.hashCode());
System.out.println(yourSonata.hashCode());
같은 해시코드값이 나온다.
Clone & 복사생성자
동일한 내용을 가진 객체복제 clone
새로운 객체지만 동일한 내용을 가지고 있으므로 equals와 hashCode의 값이 동일하다고 나와야 한다.
복사생성자 other는 다른 객체가 입력될때 같게 해주는 생성자이다.
public Car(Car other){
this.carName=other.carName;
this.color=other.color;
this.doorNum=other.doorNum;
}
//복사생성자 other. 객체 내 필드를 모두 써주어야 한다.
public Car clone(){
return new Car(this); //복사생성자 만들어야함.
}
Car hisSonata=mySonata.clone();
System.out.println(hisSonata);
System.out.println(hisSonata==mySonata);
System.out.println(hisSonata.equals(mySonata));
System.out.println(hisSonata.hashCode());
결과 )
소나타, 펄화이트, 문4개
false
true
아까와 같은 해시코드
다형성(Polymorphism)
- 객체지향 프로그래밍의 3대 특징 중 하나.
- 여러개의 형태를 갖는다는 의미
- 상속을 이용한 기술로, 자식객체를 부모클래스타입의 참조변수로 담아 제어할 수 있는 기술.
= 상위 타입으로 하위 타입의 객체를 사용할 수 있음
class Circle extends Shape 가 존재할 때, Shape s = new Circle(); 이 가능한 기술을 의미.
upcasting & downcasting (업캐스팅 , 다운캐스팅)
Up-casting : 부모타입으로 변환 (자동으로 일어남)
Down-casting : 자식타입으로 변환(명시적 형변환 필요)
Animal, Tiger, Lion class가 다음과 같은 구조이다.
원래는 Animal보다 Lion이 할 수 있는 것이 많다. (사용할 수 있는 함수가 많고, 필드가 많음.)
하지만 다형성을 통해 Animal로도 자식 객체의 내용을 사용하게 될 것이다.
먼저 클래스의 업캐스팅과 다운캐스팅을 살펴보자.
public void test1() {
Lion lion=new Lion();
Animal animal1=lion; //업캐스팅
lion.say();
lion.punch();
// animal1.punch(); .say()는 나오는데 .punch()는 안나옴
//Animal타입의 변수에 담기면 Lion의 메소드 사용불가!
Lion lionAgain=(Lion)animal1;//부모타입 -> 자식타입 (명시적 형변환 필요) 다운캐스팅
lionAgain.say();
lionAgain.punch();
Animal animal2 =new Tiger();//up-casting
animal2.say();
((Tiger)animal2).kick();//down-casting
}
업캐스팅과 다운캐스팅은 위의 코드와 같이 발생한다.
참고로 부모타입의 변수에는 모든 자식타입의 객체를 담을 수 있다.
Animal[] arr=new Animal[2];
arr[0]=new Lion();
arr[1]=new Tiger();
다음과 같이 가능.
instanceof
해당타입으로 변환할 수 있는지 검사하는 도구라고 보면 된다.
해당타입의 객체인 경우 true를 반환, true가 반환되면 해당 타입으로 안전한 형변환이 보장된다.
for (int i=0;i<arr.length;i++) {
if(arr[i] instanceof Lion) {
((Lion) arr[i]).punch();
}
if(arr[i] instanceof Tiger) {
((Tiger) arr[i]).kick();
}
}
tiger는 Animal의 자식클래스이고, Animal은 Object의 자식클래스이다.
즉, tiger는 Animal로도, Object로도 형변환이 가능하다.
Tiger tiger=new Tiger();
System.out.println(tiger instanceof Tiger);
System.out.println(tiger instanceof Animal);
System.out.println(tiger instanceof Object);
다형성 활용
1. 매개변수 선언부
2. 리턴타입
매개변수 선언부만 다르게 해도 아들 객체를 불러낼 수 있음
public void action(Tiger tiger){}
public void action(Lion lion){}
--> 메소드이름이 같고 매개변수 선언부가 다르므로 이는 오버로딩의 한 종류
public void test3(){
Tiger tiger=new Tiger();
Lion lion = new Lion();
action(tiger);
action(lion);
}
이러한 메소드 test3가 존재할 때, 위와 같은 방법으로도 불러낼 수 있지만, 이는 두번 적어야 해서 번거롭다.
public void action(Object obj){ }를 통해 한번에 자식타입별 메소드를 호출할 수 있다.
public void action(Animal animal){
if(animal instanceof Tiger)
((Tiger)animal).kick();
else if(animal instanceof Lion)
((Lion)animal).punch();
}
animal의 객체가 Tiger일때 tiger.kick();을 실행하고, Lion일때 lion.kick();을 실행한다.
만약 아래와 같은 경우라면?
public void action(Tiger tiger){
System.out.prinln("tiger");
}
public void action(Animal animal){
if(animal instanceof Tiger)
((Tiger)animal).kick();
else if(animal instanceof Lion)
((Lion)animal).punch();
}
test3()에서 action(tiger);를 실행할 때, 위의 action을 실행할지, 아래 action을 실행할지 의문이 들 것이다.
하지만 답은 간단하다. 정확히 일치하는 타입인 public void action(Tiger tiger)로 간다.
하지만 아래와 같은 경우라면?
public void action(Object obj){}
public void action(Animal animal){
if(animal instanceof Tiger)
((Tiger)animal).kick();
else if(animal instanceof Lion)
((Lion)animal).punch();
}
좀 더 정확한 타입(가까운 타입) 인 public void action(Animal animal)이 실행된다.
여기서 가까운 타입이란,
이렇게 있을때, Tiger와 Object보다는 Tiger와 Animal이 가까운 타입이다.
리턴타입을 이용하는 것은 다음과 같이 쓴다.
public void test4() {
Animal animal = animalGenerator();
System.out.println(animal);
}
public Animal animalGenerator() {
Random rnd=new Random();
if(rnd.nextBoolean()) {
return new Tiger();
}
else {
return new Lion();
}
}
동적바인딩 & 정적바인딩
정적바인딩 - 컴파일타임에서 메소드를 실행할 객체타입 기준으로 바인딩
동적바인딩 - 1. 다형성이 적용된 상태에서 2. 오버라이드 된 메소드를 호출할 경우, 자식타입의 메소드가 바인딩되는 것. = 실행하는 메소드가 연결된다는 뜻
정적바인딩은 Animal안의 say가 실행되는것.
동적바인딩은, Tiger에서 say를 오버라이드 했을 경우 Animal의 say()를 실행해도 tiger의 say가 실행되는 것을 의미한다.
* 동적바인딩은 ctrl+클릭을 통해서 이클립스 내에서 함수를 찾으려고 하면 못찾는다.