[java] 클래스의 타입 변환, 다형성
이전 내용
[java] 접근 제한자, 클래스 상속
이전 내용 [java] 정적 멤버와 Static, 싱글톤이전 내용 [java] java 예제 (메소드 오버로딩)이전 내용 [java] 메소드 선언 : 가변 인수 모를 때 (feat. enhanced for문)이전 내용 [java] java - 클래스, this() 코드
puppy-foot-it.tistory.com
클래스의 타입 변환
클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생하며, 자식은 부모 타입으로 자동 타입 변환이 가능하다.
자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말하며, 아래와 같은 조건에서 발생한다.
부모 타입 변수 = 자식타입;
자동 타입 변환의 개념은 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다는 것이다.
또한, 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수있다.
class A {}
class B extends A {}
class C extends A {}
class D extends B {}
class E extends C {}
public class PromotionExample {
public static void main(String[] args) {
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b;
A a2 = c;
A a3 = d;
A a4 = e;
B b1 = d;
C c1 = e;
// B b3 = e; 컴파일 에러 (상속 관계 X)
// C c2 = d; 컴파일 에러 (상속 관계 X)
}
}
위의 코드를 그림으로 표현하면

D 객체는 B와 A 타입으로 자동 타입 변환이 될 수 있고, E 객체는 C와 A 타입으로 자동 타입 변환이 될 수 있다. 그러나 상속 관계가 아닌 D 객체는 C 타입으로, E 객체는 B 타입으로 변환될 수 없다.
부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
public class Parent {
public void method1() {
System.out.println("Parent-method1()");
}
public void method2() {
System.out.println("Parent-method2()");
}
}
public class Child extends Parent {
@Override
public void method2() {
System.out.println("Child-method2()"); // 메소드 재정의
}
public void method3() {
System.out.println("Child-method3()");
}
}
public class ChildExample {
public static void main(String[] args) {
Child child = new Child();
Parent parent = child; // 자동 타입 변환
parent.method1();
parent.method2(); // 재정의된 메소드 호출
// parent.method3(); 호출 불가
}
}
Child 객체는 method3() 메소드를 가지고 있지만, Parent 타입으로 변환된 이후에는 method3()을 호출할 수 없다. method2() 메소드는 부모와 자식 모두에게 있는데, 재정의된 메소드는 타입 변환 이후에도 자식 메소드가 호출된다.

이를 쉽게 요약하면,
만약 "개"와 "동물" 이라는 두 가지 클래스를 가지고 있다고 가정할 경우, "개" 는 "동물"의 한 종류이기 때문에, "개" 객체는 자동으로 "동물"로 변환될 수 있다.
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
void sound() {
System.out.println("개가 멍멍합니다.");
}
}
위의 코드에서 Dog는 Animal이라는 동물의 한 종류다. 그래서 Dog 인스턴스를 Animal 타입으로 사용할 수 있다. 이것이 자동 타입 변환이다. 자동 타입 변환은 자바에서 자식 클래스(하위 클래스)가 부모 클래스(상위 클래스)로 자연스럽게 변환되는 것이다. 즉, 개는 동물이다 라고 말할 수 있지만 동물은 반드시 개는 아니다.
[클래스의 자동 타입 변환을 사용하는 이유]
1. 코드의 재사용성 증가
자동 타입 변환을 통해 상위 클래스의 메서드나 속성을 자식 클래스에서도 사용할 수 있다. 이렇게 함으로써 코드 중복을 줄이고, 유지보수를 쉽게 할 수 있다.
2. 다형성 지원
다형성(Polymorphism)은 같은 메서드 호출이 객체의 타입에 따라 다르게 동작할 수 있게 해주는 기능이다. 자동 타입 변환을 사용하면, 여러 종류의 객체를 같은 방식으로 처리할 수 있어 유연한 프로그래밍이 가능하다.
3. 형식 안전성 제공
자바에서는 자동 타입 변환이 이루어질 때, 항상 형식이 일관되게 유지된다. 즉, 변환이 가능한 타입 간에만 자동 변환이 이루어지므로, 예기치 않은 오류를 방지할 수 있다.
4. 간결한 코드 작성
타입 변환을 수동으로 하게 되면 코드가 복잡해질 수 있다. 자동 타입 변환을 통해 개발자는 코드가 더 간결해지고, 이해하기 쉬운 구조를 만들 수 있다.
5. 향후 확장성
새로운 클래스를 추가할 때, 자동 타입 변환을 통해 기존 코드를 수정하지 않고도 새로운 기능을 쉽게 통합할 수 있다. 이를 통해 유지보수와 확장이 용이해진다.
◆ 요약
자동 타입 변환은 코드의 재사용성, 다형성, 형식 안전성, 간결성 및 확장성을 높이는 데 도움을 준다. 이로 인해 자바 프로그래밍이 더욱 효율적이고, 시스템을 개발하고 유지보수하는 과정에서 많은 이점을 제공한다.
필드의 다형성
앞서 클래스의 자동 타입 변환을 사용하는 이유 중 다형성을 지원하기 때문이라고 했는데, 필드의 다형성은 상위 타입의 참조 변수를 사용하여 하위 타입의 객체를 저장할 수 있는 기능을 의미한다. 이를 통해 하나의 필드로 여러 종류의 객체를 다룰 수 있게 해준다.
필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있다.
public class Tire {
// 필드
public int maxRotation; // 최대 회전수(타이어 수명)
public int accumulatedRotation; // 누적 회전수
public String location; // 타이어 위치
// 생성자
public Tire(String location, int maxRotation) {
this.location = location; // 초기화
this.maxRotation = maxRotation; // 초기화
}
// 메소드
public boolean roll() {
++accumulatedRotation; // 누적 회전수 1씩 증가
if (accumulatedRotation < maxRotation) { // 누적 회전수가 최대 회전수보다 작으면
System.out.println(location + " Tire 수명: " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " Tire 펑크 ***");
return false;
}
}
}
public class HankookTire extends Tire {
// 필드
// 생성자
public HankookTire(String location, int maxRotation) {
super(location, maxRotation); // 부모의 생성자 호출
}
// 메소드
@Override
public boolean roll() {
++accumulatedRotation;
if (accumulatedRotation < maxRotation) {
System.out.println(location + " HankookTire 수명: " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " HankookTire 펑크 ***");
return false;
}
}
}
public class KumhoTire extends Tire {
// 필드
// 생성자
public KumhoTire(String location, int maxRotation) {
super(location, maxRotation); // 부모의 생성자 호출
}
// 메소드
@Override
public boolean roll() {
++accumulatedRotation;
if (accumulatedRotation < maxRotation) {
System.out.println(location + " KumhoTire 수명: " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " KumhoTire 펑크 ***");
return false;
}
}
}
public class CarExample {
public static void main(String[] args) {
Drive drive = new Drive(); // Drive 객체 생성
// Drive 객체의 run() 메소드 5번 반복 실행
for (int i = 1; i <= 5; i++) {
int problemLocation = drive.run();
switch (problemLocation) {
case 1:
System.out.println("앞왼쪽 HankookTire로 교체");
drive.frontLeftTire = new HankookTire("앞왼쪽", 15);
break; // 앞왼쪽 타이어 펑크 시 HankookTire로 교체
case 2:
System.out.println("앞오른쪽 KumhoTire로 교체");
drive.frontRightTire = new KumhoTire("앞으론쪽", 13);
break; // 앞오른쪽 타이어 펑크 시 KumhoTire로 교체
case 3:
System.out.println("뒤왼쪽 HankookTire로 교체");
drive.backLeftTire = new HankookTire("뒤왼쪽", 14);
break; // 뒤왼쪽 타이어 펑크 시 HankookTire로 교체
case 4:
System.out.println("뒤오른쪽 KumhoTire로 교체");
drive.backRightTire = new KumhoTire("뒤오른쪽", 17);
break; // 뒤오른쪽 타이어 펑크 시 KumhoTire로 교체
}
// 1회전 시 출력되는 내용 구분
System.out.println("----------------------");
}
}
}

예제1
Q. 동물 클래스와 다형성(배열 저장)
1. "동물 (Animal)" 이라는 부모 클래스를 만들고, 이를 상속받는 "고양이(Cat)"와 "강아지(Dog)" 라는 자식 클래스 작성
2. 각 동물은 "소리를 낸다(makeSound)"라는 메소드를 가지며, 이 메소드는 동물마다 다른 소리를 출력해야 한다.
3. 상속을 활용하여 코드 중복을 최소화하고, 다형성을 이용해 동물 객체들을 배열에 저장한 뒤, 각각의 소리를 출력한다.
Animal 클래스
public class Animal {
// 필드 생성
public String name; // 동물 이름
// 생성자
public Animal(String name) {
this.name = name;
}
// 메소드
void makeSound() {
System.out.println(name + "(이)가 소리를 냅니다.");
}
}
Cat 클래스 (부모 클래스 상속)
public class Cat extends Animal {
// 생성자
public Cat(String name) {
super(name);
}
// 메소드
@Override
void makeSound() {
System.out.println(name + "(이)가 야옹 소리를 냅니다.");
}
}
Dog 클래스 (부모 클래스 상속)
public class Dog extends Animal {
// 생성자
public Dog(String name) {
super(name);
}
// 메소드
@Override
void makeSound() {
System.out.println(name + "(이)가 멍멍 소리를 냅니다.");
}
}
AnimalExample 클래스 (출력)
public class AnimalExample {
public static void main(String[] args) {
Animal[] animals = new Animal[3]; // 배열 생성 (타입이 같은 것만 넣을 수 있음)
animals[0] = new Cat("나비");
animals[1] = new Dog("바둑이");
animals[2] = new Cat("치즈");
// Enhanced for문으로 배열 순회
for (Animal animal : animals) {
animal.makeSound();
}
}
}

예제2
Q. 도형 계산기와 다형성(메서드 호출 방식)
1. "도형(Shape)" 이라는 클래스를 만들고, 이를 상속받는 "사각형(Rectangle)" 과 "원(Circle)" 클래스 작성
2. 각 도형은 면적을 계산하는 calculateArea() 메소드를 오버라이드
3. ShapePainter 라는 클래스를 만들어 printArea(Shape shape) 메서드를 정의하고, 이 메서드를 통해 다양한 도형 객체의 면적을 출력. 다형성을 활용하여 Shape 타입의 매개변수로 각 도형 객체를 전달하고 결과 확인
총 5개의 클래스를 생성한다.
- Shape 클래스: 도형의 기본 클래스
- Rectangle 클래스: 사각형(Rectangle) 클래스에서 면적을 계산하는 메소드를 오버라이드
- Circle 클래스: 원(Circle) 클래스에서 면적을 계산하는 메소드를 오버라이드
- ShapePainter 클래스: ShapePainter 클래스는 다형성을 활용하여 다양한 도형의 면적을 출력하는 메서드를 제공
- ShapeTest 클래스: Circle과 Rectangle 객체를 생성하여 면적을 출력하는 Main 클래스
public class Shape {
// 필드
String type;
// 생성자
public Shape(String type) {
this.type = type;
}
public double calculateArea() {
return 0.0;
}
}
public class Rectangle extends Shape {
double width;
double height;
// 생성자
public Rectangle(double width, double height) {
super("사각형");
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
}
public class Circle extends Shape {
// 필드
double radius;
// 생성자
public Circle(double radius) {
super("원");
this.radius = radius;
}
@Override
public double calculateArea() {
return (radius * radius) * Math.PI;
}
}
public class ShapePainter {
public static void printArea(Shape shape) { // Shape 타입의 shape 매개변수
System.out.println(shape.type + "의 면적: " + shape.calculateArea());
} // 타입(필드), 메소드 호출
}
public class ShapeTest {
public static void main(String[] args) {
Circle circle1 = new Circle(4);
Rectangle rectangle1 = new Rectangle(5, 3);
Rectangle rectangle2 = new Rectangle(4, 3);
ShapePainter.printArea(circle1);
ShapePainter.printArea(rectangle1);
ShapePainter.printArea(rectangle2);
}
}
▶
Circle 객체 생성: circle1이라는 원 객체를 생성하며 반지름을 4로 설정. 이 객체는 Circle 클래스의 생성자를 호출하여 초기화.
생성자 내부 동작: Circle 클래스의 생성자는 상위 클래스인 Shape의 생성자를 호출하여 도형의 타입을 "원"으로 설정.
Rectangle 객체 생성: 두 개의 사각형 객체(rectangle1과 rectangle2)를 생성.
rectangle1: 너비를 5, 높이를 3으로 설정.
rectangle2: 너비를 4, 높이를 3으로 설정.
각각의 객체는 Rectangle 클래스의 생성자를 통해 초기화되며, 생성자 내부에서 상위 클래스인 Shape의 생성자 또한 호출됨.
ShapePainter 클래스의 printArea 메서드를 호출하여 각 도형의 면적을 출력.
다형성 활용: ShapePainter.printArea(shape) 메서드는 Shape 클래스 타입의 매개변수를 받아들인다. 여기서 circle1, rectangle1, rectangle2는 모두 Shape 타입의 자식 클래스 객체이므로 간단히 해당 메서드에 전달될 수 있다.
printArea() 메서드는 매개변수로 전달된 도형 객체의 calculateArea() 메서드를 호출하여 면적을 계산하고, 그 결과를 콘솔에 출력.
예제3
Q. 차량 관리 시스템
1. "차량(Vehicle)" 이라는 부모 클래스를 만들고, 이를 상속받는 자동차("Car")와 "오토바이(Motorcycle)" 자식 클래스 생성
2. 각 차량은 "주행한다(drive)" 라는 메서드를 가지며, 차량마다 다른 방식으로 주행 메시지를 출력해야 함.
public class Vehicle {
// 필드 생성
String name;
// 생성자
Vehicle(String name) {
this.name = name;
}
// 메소드
public String drive() {
return "이 주행 중입니다.";
}
}
public class Cars extends Vehicle {
// 필드
// 생성자
public Cars() {
super("자동차");
}
@Override
public String drive() {
return "가 도로를 달립니다.";
}
}
public class Motorcycle extends Vehicle {
// 필드
// 생성자
Motorcycle() {
super("오토바이");
}
// 메소드
@Override
public String drive() {
return "가 빠르게 달립니다.";
}
}
public class VehicleService {
public static void serviceVehicle(Vehicle vehicle) {
System.out.println(vehicle.name + vehicle.drive());
}
}
public class printVehicle {
public static void main(String[] args) {
Vehicle vehicle1 = new Vehicle("차량");
Cars car1 = new Cars();
Motorcycle motor1 = new Motorcycle();
VehicleService.serviceVehicle(vehicle1);
VehicleService.serviceVehicle(motor1);
VehicleService.serviceVehicle(car1);
}
}

예제4
Q. 차량 관리 시스템 (업그레이드)
1. 기존 요청 내용 +
2. "차량 관리소(VehicleService)" 라는 클래스를 만들어 serviceVehicle(Vehicle vehicle) 메서드를 정의하고, 이 메서드를 통해 다양한 차량 객체를 받아 주행 동작 호출. 매개변수 다형성을 활용하여 Vehicle 타입 매개변수로 모든 차량 객체를 처리할 수 있도록 구현
[Vehicle 클래스]
public class Vehicle {
// 필드 생성
String brand;
// 생성자
Vehicle(String brand) {
this.brand = brand;
}
// 메소드
public void drive() {
System.out.println(brand + "차량이 주행 중입니다.");
}
}
[Cars 클래스: Vehicle의 자식 클래스]
public class Cars extends Vehicle {
// 필드
int seats;
// 생성자
public Cars(String brand, int seats) {
super(brand); // 부모 생성자 호출
this.seats = seats;
}
@Override
public void drive() {
System.out.println(brand + "자동차가 " + seats + "명의 승객을 태우고 도로를 달립니다.");
}
}
▶ 필드에 정수 타입인 seats 멤버변수를 추가하고, 생성자에도 매개변수로 받도록 했다. 또한 오버라이딩한 drive() 메소드를 재정의하여 brand와 seats를 추가하도록 하였다.
[Motorcycle 클래스: Vehicle 의 자식 클래스]
public class Motorcycle extends Vehicle {
// 필드
boolean hasSidecar;
// 생성자
Motorcycle(String brand, boolean hasSidecar) {
super("오토바이");
this.hasSidecar = hasSidecar;
}
// 메소드
@Override
public void drive() {
if (hasSidecar) {
System.out.println(brand + "오토바이가 사이드카와 함께 멋지게 달립니다.");
} else {
System.out.println(brand + "오토바이가 빠르게 주행합니다.");
}
}
}
▶ 필드에 boolean 타입의 hasSidecar 멤버변수를 추가하고, 생성자에도 매개변수로 받도록 하였다.
오버라이딩된 drive() 메소드는 hasSidecar가 true | false에 따라 다른 출력값을 나타내도록 조건문으로 재정의하였다.
[VehicleService 클래스]
public class VehicleService {
public void serviceVehicle(Vehicle vehicle) {
System.out.println("차량 점검 시작..");
vehicle.drive(); // 매개변수 다형성으로 자식 클래스의 오버라이딩 된 메서드 호출
System.out.println("차량 점검 완료");
}
}
▶ Vehicle 타입의 vehicle 매개변수를 받도록 하고, vehicle의 drive() 메소드가 호출되도록 하여 매개변수 다형성을 지원하였다.
[VehicleTest: 메인 클래스]
public class VehicleTest { // 메인 클래스
public static void main(String[] args) {
VehicleService service = new VehicleService();
// 다양한 차량 객체 생성
Vehicle generalVehicle = new Vehicle("기본");
Vehicle car = new Cars("현대", 4);
Vehicle motorcycle = new Motorcycle("할리", true);
Vehicle motorcycle2 = new Motorcycle("할리", false);
// 매개변수 다형성으로 차량 점검
service.serviceVehicle(generalVehicle);
System.out.println();
service.serviceVehicle(car);
System.out.println();
service.serviceVehicle(motorcycle);
System.out.println();
service.serviceVehicle(motorcycle2);
}
}

[참고]
혼자 공부하는 자바
다음 내용
[java] 추상 클래스, 추상 메소드
이전 내용 [java] 클래스의 타입 변환, 다형성이전 내용 [java] 접근 제한자, 클래스 상속이전 내용 [java] 정적 멤버와 Static, 싱글톤이전 내용 [java] java 예제 (메소드 오버로딩)이전 내용 [java] 메
puppy-foot-it.tistory.com