업무 중 마주한 내용에 대해 추가적으로 알아보고자 공부하고 작성하였습니다.
Java 코드를 보던 중 아래와 같은 경우를 보게 되었습니다 (예시입니다).
Queue<Integer> q = new LinkedList<>();
if (q instanceof LinkedList) {
System.out.println("true");
}
instanceof 연산자입니다.
단순히 뜻을 유추해 보자면 q가 LinkedList의 instance인지 true/false 결과를 리턴하는 연산자로 보입니다.
실제로 그런 역할을 합니다.
무슨 말이냐 하면,
object instanceof type
위와 같은 코드가 있을 때, object가 type을 상속받는 클라스라면 true를 리턴하고, 그렇지 않다면 false를 리턴합니다.
위 코드의 경우 queue는 LinkedList를 이용하여 구현되었기 때문에, 위 코드가 true가 됩니다.
Queue<Integer> q = new LinkedList<>();
if (q instanceof TreeSet) {
System.out.println("true");
}
else System.out.println("false");
위와 같은 경우에는 어떻게 될까요?
queue는 TreeSet을 상속받지 않았기 때문에 false를 출력하는 것으로 예상할 수 있습니다.
마찬가지로 출력되네요.
모든 객체는 Object 클라스를 상속받기 때문에, 객체 a에 대해 a instanceof Object는 항상 참이 됩니다.
Queue<Integer> q = new LinkedList<>();
if (q instanceof Object) {
System.out.println("true");
}
else System.out.println("false");
a가 null일 경우, 아무것도 상속 받지 않았기 때문에 (정확히는 객체가 아니기 때문에) 항상 거짓이 됩니다.
Queue<Integer> q = null;
if (q instanceof Object) {
System.out.println("true");
}
else System.out.println("false");
이렇듯 instanceof 연산자는 a가 Object를 상속 받은 클라스인지 알아볼 수 있는 연산자입니다.
타입 비교 연산자 (type comparison operator)라고도 불리는데, 인스턴스를 타입으로 비교하기 떄문입니다.
사용을 지양해야 하는 이유
instanceof 연산자는 기능적으로는 아무런 문제가 없지만, instanceof를 사용하는 코딩 습관은 좋지 못하다는 이야기가 있습니다.
물론 필연적으로 사용해야 할 때도 있지만, 좋은 코드 설계는 instanceof 사용을 피하는 편이라고 합니다.
class Animal {}
//Fish, Bird, Kangaroo가 각각 Animal을 상속받음
class Fish extends Animal {
void swim(){
System.out.println(“Swim”);
}
}
class Bird extends Animal {
void fly(){
System.out.println(“Fly”);
}
}
class Kangaroo extends Animal {
void jump(){
System.out.println(“Jump”);
}
}
//나쁜 사용 예
public final class BadUseOfInstanceOf {
public static void main(String[] args){
makeItMove(new Fish());
makeItMove(new Bird());
makeItMove(new Kangaroo());
}
public static void makeItMove(Animal animal){
//파라미터 animal이 Fish를 상속받은 경우
if (animal instanceof Fish){
Fish fish = (Fish)animal;
fish.swim();
}
//파라미터 animal이 Bird를 상속받은 경우
else if (animal instanceof Bird){
Bird bird = (Bird)animal;
bird.fly();
}
//파라미터 animal이 Kangaroo 상속받은 경우
else if (animal instanceof Kangaroo){
Kangaroo kangaroo = (Kangaroo)animal;
kangaroo.jump();
}
}
}
위처럼 구현하는 경우에는 Fish, Bird, Kangaroo에 대해 각각 다른 액션을 취하고 싶은 경우일 것입니다.
하지만 단순히 Kangaroo 객체라는 것만으로, 어떤 액션을 취하기에 명확한 경우는 실제로는 잘 없습니다.
즉 추상화가 잘못되었다고 할 수 있습니다.
또, 코드를 보면, 각 객체가 무엇이고, 어떤 결과를 리턴해야 하는지 외부의 객체가 불필요하게 알 수 있게 됩니다.
이를 "캡슐화가 깨졌다" 라고 표현합니다.
또, 유지보수 문제 역시 있습니다.
만약 Animal을 상속받는 새로운 클라스가 추가된다면, BadUseOfInstanceOf 내부에도 마찬가지로 행동을 명시해 주어야 합니다. 이를 까먹을 수도 있고, 코드가 길어지면 쉽게 찾을 수 없을 것입니다.
아래와 같이 다형성을 적극 활용하여 코드를 작성한다면 좋을 것입니다.
class Animal {
void move(){
System.out.println(“Move”);
}
}
class Fish extends Animal {
@Override void move(){
System.out.println(“Swim”);
}
}
class Bird extends Animal {
@Override void move(){
System.out.println(“Fly”);
}
}
class Kangaroo extends Animal {
@Override void move(){
System.out.println(“Jump”);
}
}
public final class ProperUseOfInstanceOf {
public static void main(String[] args){
makeItMove(new Fish());
makeItMove(new Bird());
makeItMove(new Kangaroo());
}
/**
* 이렇게 구현하는 경우, maktItMove는 Fish/Bird/Kangaroo 여부를
* 확인하지 않고, 그저 Animal의 move를 호출합니다.
* Fish, Bird, Kangaroo는 각각 다른 버전의 move를
* 가지고 있고, 각자의 move를 실행하게 됩니다.
*/
public static void makeItMove(Animal animal){
animal.move();
}
}
내용 참고
'개발 > 자바, 코틀린' 카테고리의 다른 글
자바의 신 12 ~ 13장 학습 내용 정리 (0) | 2022.09.07 |
---|---|
자바의 신 10 ~ 11장 학습 내용 정리 (0) | 2022.09.07 |
[Java] 자바의 신 8 ~ 9장 학습 내용 정리 (0) | 2022.08.04 |
[Java] 자바의 신 1 ~ 7장 학습 내용 정리 (0) | 2022.07.25 |
[Kotlin] 코틀린 소개 + 입문 (0) | 2022.02.02 |
댓글