2023. 12. 4. 18:28ㆍJava
이번 포스팅에서는 instanceof 키워드란 무엇인지 그리고 해당 키워드 사용을 지양해야 하는 이유에 대해 알아보도록 하겠습니다.
핵심 정리
instanceof 키워드를 자주 사용하는 것은 캡슐화, 단일 책임 원칙, 개방-폐쇄 원칙을 위배할 수 있기 때문에 사용을 지양하고, 대신 다형성을 이용하는 것을 권장합니다.
instanceof 란?
object instanceof type은 객체가 특정 타입의 인스턴스인지를 확인하기 위한 연산자입니다. 이 연산자를 사용하면 런타임에 객체의 타입을 확인할 수 있습니다. 만약 어떤 타입에 대한 instanceof연산의 결과가 true라면 검사한 타입으로 형변환이 가능하다는 것을 뜻합니다.
class Animal {}
class Dog extends Animal {}
public class InstanceOfTest {
public static void main(String[] args) {
Animal animal = new Dog();
if (animal instanceof Dog) {
System.out.println("It's a Dog!");
} else {
System.out.println("It's not a Dog!");
}
}
}
// [ 출력 ]
// It's a Dog!
animal instanceof Dog는 animal이 Dog 클래스의 인스턴스인지를 확인합니다. 위의 예시에서 animal은 Dog 클래스로 생성되었으므로 해당 조건은 참이 되어 "이것은 개다!"가 출력됩니다. (다형성)
그럼 언제 instanceof가 true/false를 리턴할까?
instanceof 결과로 true를 반환하는 예시
①특정 클래스의 인스턴스이거나 ②해당 클래스를 상속하거나 ③해당 인터페이스를 구현하면 true를 반환합니다.
1. 클래스의 인스턴스 일 경우
class Animal {}
class Dog extends Animal {}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
if (animal instanceof Animal) {
System.out.println("It's an Animal!");
}
if (animal instanceof Dog) {
System.out.println("It's a Dog!");
}
}
}
2. 해당 클래스를 상속한 경우
class Animal {}
class Dog extends Animal {}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
if (dog instanceof Animal) {
System.out.println("It's an Animal!");
}
}
}
3. 해당 인터페이스 구현한 경우
interface Eater {}
class Animal implements Eater {}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
if (animal instanceof Eater) {
System.out.println("It can eat!");
}
}
}
instanceof 결과로 false를 반환하는 예시
객체가 ①특정 클래스의 인스턴스가 아니거나 ②해당 클래스를 상속하지 않거나 ③해당 인터페이스를 구현하지 않으면 instanceof 연산자는 false를 반환합니다.
1. 클래스의 인스턴스가 아닌 경우
class Animal {}
class Dog {}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
if (dog instanceof Animal) {
System.out.println("It's an Animal!");
} else {
System.out.println("It's not an Animal.");
}
}
}
2. 상속관계가 아닌 경우
class Animal {}
class Cat {}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
if (cat instanceof Animal) {
System.out.println("It's an Animal!");
} else {
System.out.println("It's not an Animal.");
}
}
}
3. 해당 인터페이스를 구현하지 않은 경우
interface Eater {}
class Plant {}
public class Main {
public static void main(String[] args) {
Plant plant = new Plant();
if (plant instanceof Eater) {
System.out.println("It can eat!");
} else {
System.out.println("It can't eat.");
}
}
}
instanceof 사용 이유
instanceof를 사용하면 프로그램 실행 중에 객체의 실제 타입을 확인할 수 있습니다. 특정 작업을 수행하기 전에 올바른 타입의 객체를 다루고 있는지 확인하는 데 유용합니다.
class Animal {
void makeSound() {
System.out.println("동물은 제각각 울음소리가 다릅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("멍멍!");
}
}
public class InstanceOfTest {
static void makeSound(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.makeSound();
} else {
animal.makeSound();
}
}
public static void main(String[] args) {
Animal animal = new Dog();
makeSound(animal);
}
}
makeSound 메서드가 호출될 때 매개변수로 Animal클래스 또는 그 자손 클래스의 인스턴스를 넘겨받겠지만 메서드 내에서는 정확히 어떤 인스턴스인지 알 길이 없습니다. 이때 instanceof연산자를 이용해서 참조변수 animal가 가리키고 있는 인스턴스의 타입을 체크하고 적절히 형변환한 다음에 작업을 해야 합니다.
그럼에도 instanceof 사용을 지양해야 한다. 왜일까?
코드 작성을 할 때 instanceof 키워드 사용을 지양해야합니다. 그 대신 다형성을 사용하는 것을 권장하는데요.
코드 예시를 보면서 이유에 대해 알아보도록하겠습니다.
package com.example.instanceOf;
import org.springframework.util.StopWatch;
class Animal {
void makeSound() {
System.out.println("동물은 제각각 울음소리가 다릅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog 멍멍!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Cat 미야야야옹!");
}
}
class Bird extends Animal {
@Override
void makeSound() {
System.out.println("Bird 짹짹!");
}
}
public class InstanceOfExample {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
Animal myBird = new Bird();
checkAnimalType(myDog);
checkAnimalType(myCat);
checkAnimalType(myBird);
}
static void checkAnimalType(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.makeSound();
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.makeSound();
} else if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.makeSound();
} else {
System.out.println("확인되지 않은 동물타입입니다.");
}
}
}
코드를 보면 불필요한 정보들을 노출하고 있고 인스턴스를 생성할 때마다 조건문이 추가됨과 동시에 메서드 코드를 수정해야 함을 알 수 있습니다. 이를 객체지향프로그램 특징/원칙에 대입해 보자면 3가지 원칙에 위배됩니다.
캡슐화 위배
객체지향 프로그래밍에서 캡슐화란 클래스 안에 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 말합니다. 하지만 instanceof를 사용하면 각 객체가 무엇인지, 어떤 행동을 반환해야 하는지 외부의 객체에 노출되게 됩니다. 캡슐화를 위반하면 객체의 상태에 직접 접근이 가능해지므로, 외부에서 무분별한 수정이 가능해집니다. 이는 보안 상의 문제로 이어질 수 있습니다.
단일책임원칙(SRP) 위배
checkAnimalType 메서드는 다양한 동물 타입에 대한 처리를 모두 담당하고 있습니다. SRP는 클래스나 메서드가 단일의 책임만을 가져야 한다는 원칙인데, 이 메서드는 여러 동물 타입에 대한 처리와 출력까지 모두 수행하고 있습니다.
개방-폐쇄원칙(OCP) 위배
새로운 동물 타입이 추가될 때마다 checkAnimalType 메서드를 수정해야 합니다. OCP는 확장에는 열려있고 변경에는 닫혀 있어야 한다는 원칙인데, 이 메서드는 새로운 동물 타입을 처리하기 위해 수정이 필요하므로 OCP를 위배하고 있습니다.
그럼 어떤 걸 사용해야 할까? 다형성!
다형성을 이용하여 구현하면 if분기문이 많이 필요 없기에 코드가 간결해집니다. 또한 상속 관계가 바뀌는 경우에도 바뀐 타입의 메서드만 수정해 주면 되므로 유지보수가 용이합니다.
package com.example.instanceOf;
import org.springframework.util.StopWatch;
class Animal {
void makeSound() {
System.out.println("동물은 제각각 울음소리가 다릅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog 멍멍!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Cat 미야야야옹!");
}
}
class Bird extends Animal {
@Override
void makeSound() {
System.out.println("Bird 짹짹!");
}
}
public class polymorphismExample {
public static void main(String[] args) {
checkAnimalType(new Dog());
checkAnimalType(new Cat());
checkAnimalType(new Bird());
}
static void checkAnimalType(Animal animal){
animal.makeSound();
}
}
checkAnimalType는 Dog/Cat/Bird 여부를 확인하지 않고 Animal의 makeSound()를 호출합니다.
마지막으로 성능 체크
instanceof를 사용할 때와 다형성을 사용할 때의 성능에도 차이가 있을지 확인해 보겠습니다.
Case 1. instanceof
package com.example.instanceOf;
import org.springframework.util.StopWatch;
class Animal {
void makeSound() {
System.out.println("동물은 제각각 울음소리가 다릅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog 멍멍!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Cat 미야야야옹!");
}
}
class Bird extends Animal {
@Override
void makeSound() {
System.out.println("Bird 짹짹!");
}
}
public class InstanceOfExample {
public static void main(String[] args) {
// StopWatch 객체 생성
StopWatch stopWatch = new StopWatch();
// 측정 시작
stopWatch.start();
for (int i = 0; i < 1_000_000; i++) {
Animal myDog = new Dog();
Animal myCat = new Cat();
Animal myBird = new Bird();
checkAnimalType(myDog);
checkAnimalType(myCat);
checkAnimalType(myBird);
}
// 측정 종료
stopWatch.stop();
// 결과 출력
System.out.println("totalTimeSeconds : " + stopWatch.getTotalTimeSeconds() + "초");
}
static void checkAnimalType(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.makeSound();
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.makeSound();
} else if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.makeSound();
} else {
System.out.println("확인되지 않은 동물타입입니다.");
}
}
}
Case 2. 다형성
package com.example.instanceOf;
import org.springframework.util.StopWatch;
class Animal {
void makeSound() {
System.out.println("동물은 제각각 울음소리가 다릅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog 멍멍!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Cat 미야야야옹!");
}
}
class Bird extends Animal {
@Override
void makeSound() {
System.out.println("Bird 짹짹!");
}
}
public class polymorphismExample {
public static void main(String[] args) {
// StopWatch 객체 생성
StopWatch stopWatch = new StopWatch();
// 측정 시작
stopWatch.start();
for (int i = 0; i < 100000; i++) {
checkAnimalType(new Dog());
checkAnimalType(new Cat());
checkAnimalType(new Bird());
}
// 측정 종료
stopWatch.stop();
// 결과 출력
System.out.println("totalTimeSeconds : " + stopWatch.getTotalTimeSeconds() + "초");
}
static void checkAnimalType(Animal animal){
animal.makeSound();
}
}
100,000개의 객체 생성시 성능을 비교해 보았을 때, instanaceof는 2.5425808초, 다형성은 2.430432101초가 소요됨을 확인했습니다.
instanceof 연산자를 사용하면 타입 체크 작업이 추가되므로 상대적으로 더 많은 시간이 소요됨을 알 수 있었습니다.
참고
자바의 정석
https://tecoble.techcourse.co.kr/post/2021-04-26-instanceof/
'Java' 카테고리의 다른 글
[동시성 톺아보기] Java Thread 주요 관리 API (sleep(), join(), interrupt()) (0) | 2024.03.25 |
---|---|
[동시성 톺아보기] Runnable 익명 클래스로 Thread 생성 (0) | 2024.03.17 |
[Java] equals()와 hashCode()를 같이 재정의 해야하는 이유 (0) | 2023.11.20 |
[Java] String을 초기화하는 방법 - 성능 비교 (0) | 2023.11.13 |
[Java] 자바 변수 타입별 메모리 저장 위치 및 GC 동작원리 (0) | 2023.11.06 |