오늘은 어떤 주제로 포스팅을 할 까 고민하다가, 언니에게 들은 얘기를 듣고 아이디어가 떠올랐다.
언니가 본 회사의 면접에서 equlals메소드에 관한 질문을 받았다는 얘기를 듣고
' 아! 오늘은 이거에 대해 포스팅을 해보아야 겠다 ' 고 생각을 했다.
toString, equals 등등 Object 클래스의 메소드가 여러가지가 있어서 이것에 대해 정리해보려고 하던 참이었는데, 이것저것 찾아보며 공부를 하다가 Casting
에 관한 부분에서 내가 잘 모르고 있다는 것을 깨달았다.
그래서 오늘은 Casting
에 관해서 글을 써볼까 한다.
Object 클래스에 관한 글은 다음 포스팅에 적으려고 한다.
자바에서 캐스팅은 타입을 변환하는 것 이다. (=형변환 !!!
)
이 때, 상속 관계에 있는 부모와 자식 클래스 간에는 서로 형변환이 가능하다.
캐스팅의 종류는 2가지이다
class Human{
// 생략
}
class Student extends Human{
// 생략
}
public class CastingTest {
public static void main(String[] args) {
Student student = new Student();
Human human = student;
// 자식 클래스(Student)가 부모 클래스(Human)타입으로 캐스팅
}
}
class Human{
// 생략
}
class Student extends Human{
// 생략
}
public class CastingTest {
public static void main(String[] args) {
Human human = new Human();
Student student = (Student) human;
// 부모 클래스(Human)이 자식 클래스(Student)타입으로 캐스팅
// 위의 구문은 오류지만, 이런 식으로 된다는 걸 보여주기 위한 것이니 참고만 하자.
// 실제로 다운캐스팅은 업 캐스팅이 선행된 후 되어야 한다.
}
}
이제부터 각각에 대해 자세히 살펴보자.
시작하기에 앞서, 부모클래스인 상속 관계의 상위 클래스를 수퍼 클래스, 그리고 자식 클래스인 하위 클래스를 서브 클래스라고 정의한다.
Java에서, 서브 클래스는 수퍼 클래스의 모든 특성을 상속받는다.
그렇기 때문에 서브 클래스는 수퍼 클래스로 취급될 수 있다.
ex) 수퍼 클래스 : 생물, 서브 클래스 : 사람 일 때
사람은 생물이다.
라고 할 수 있음 !
여기서 업 캐스팅이란 서브 클래스의 객체가 수퍼 클래스 타입으로 형변환 되는 것을 말한다.
즉, 수퍼 클래스의 참조변수가 서브클래스로 객체화된 인스턴스를 가리킬 수 있게 된다. 이해가 잘 안된다면 아래의 예제를 보고 코드로 이해하자.
package study.test.java;
class Person{
String name;
public Person(String name) {
this.name = name;
}
}
class Student extends Person{
String age;
public Student(String name) {
super(name); // super 키워드에 대해서는 따로 설명하겠다.
}
}
public class CastingTest {
public static void main(String[] args) {
// student 참조변수를 이용하면 age, name에 접근 가능하다.
Student student = new Student("도리도리");
// person 참조변수를 이용하면 Student 객체의 멤버 중에서 Person 클래스의 멤버에만 접근이 가능하다.
Person person = student; // "업 캐스팅"
person.name = "세진세진";
person.age = "24"; // 컴파일 오류 ~!
}
}
위의 코드에서 , person
참조변수는 Student 객체를 가리킨다.
하지만 Person 타입이므로 자신의 클래스에 속한 멤버만 접근 가능하다.
(컴파일 시점에서 오류가 남)
person.age를 했을 때 컴파일 오류가 나는것이 바로 그 예시이다.
이와 같이 업 캐스팅을 하게 되면 객체(여기서는 Student 객체를 말함) 내의 모든 멤버에 접근할 수 없다.
멤버 필드 뿐 아니라 메서드도 마찬가지 이다.
또한, 명시적인 타입 캐스팅 선언을 하지 않아도 된다.
서브 클래스인 Student는 Person 타입 이기도 하기 때문이다.
무슨 말인지 잘 모르겠다면 아래의 코드를 참고하자.
Student student = new Student();
Person person = (Person) student; // 이렇게 앞에 (Person) 붙여주는거 안해도 된다는 것.
[참고]
super 키워드는 뭐지? super, super()의 차이점은?😶
super
- 자식 클래스가 부모 클래스로부터 상속받은 멤버를 참조할 때 사용하는 참조 변수.
- 클래스 내의 멤버변수와 지역변수의 이름이 같을 경우 구분하기 위해 this를 사용하는 것 처럼, 부모 클래스와 자식 클래스의 멤버의 이름이 같을 경우 super를 사용한다.
- 아래의 예제를 참고하자.
class Parent{ int x = 10; } class Child extends Parent{ int x = 20; void childMethod(){ System.out.println("x=" + x); System.out.println("this.x=" + this.x); System.out.println("super.x=" + super.x); // 부모클래스의 멤버변수 x를 말하는것. 출력결과는 10 이다. } } class Main{ public static void main(String[] args) { Child child = new Child(); child.childMethod(); } }
super()
- 부모 클래스의 생성자를 호출하는 메서드.
- 자식 클래스의 인스턴스를 생성하면, 해당 인스턴스에는 자식 클래스의 고유 멤버 뿐 아니라 부모 클래스의 모든 멤버까지 포함되어 있음.
따라서 부모 클래스의 멤버를 초기화하기 위해서는자식 클래스의 생성자에서 부모 클래스의 생성자까지 호출해야 한다.
cf) 상속을 받았더라도 자식조차 접근할 수 없는 private field의 경우는 자식 생성자로 초기화 할 수 없다. 따라서 부모의 생성자를 호출하는 super() 필요.
- 자바 컴파일러는 부모 클래스의 생성자를 명시적으로 호출하지 않는 모든 자식클래스의 생성자 첫줄에 자동으로
super()
를 추가하여, 부모 클래스의 멤버를 초기화 할 수 있도록 해준다.- 하지만 자바 컴파일러는 컴파일시 클래스에
생성자가 하나도 정의되어 있지 않아야만
자동으로 기본 생성자를 추가해준다.- 만약 아래의 예제처럼 부모 클래스에 매개변수를 가지는 생성자를 하나라도 선언했다면, 부모 클래스에는 기본 생성자가 자동으로 추가되지 않을 것이다.
- 따라서 Parent 클래스를 상속받은 자식 클래스에서 super()를 사용하여 부모 클래스의 기본 생성자를 호출하게 되면, 오류가 발생한다.
class Parent{ int a; Parent(int n){ a = n; } } class Child extends Parent{ int b; Child(){ super(); b = 20; } }
Why 오류 발생 ? 😵
- 부모 클래스인 Parent 클래스에 매개변수를 가진 생성자가 선언되어 있어, 자바 컴파일러에서 자동으로 기본 생성자를 추가해주지 않기 때문이다.
- 그래서 자식 클래스에서 super()를 통해 부모 클래스의 기본생성자를 호출하게 되면 오류가 발생하는 것.
그럼 어떻게 해야 오류가 안나지?
- 매개변수를 가지는 생성자를 선언해야 하는 경우
기본 생성자까지 명시적으로 선언
하는게 좋다.
(자식클래스에서 super() 이용해서 부모 클래스의 기본 생성자를 호출했을 때, 부모 클래스에는 기본 생성자가 없어서 오류가 발생할 수 있으니까.)
-> 다형성
과 관련 있음 !!
(좀 더 공통적으로 할 수 있는 부분을 간단하게 만들기 위해.)
이해를 돕기 위해 예제를 참고하자.
class 해장국{
public void 간맞추기(){
// 뭐든 Ok
}
}
class 뼈해장국 extends 해장국{
@Override
public void 간맞추기(){
// 뼈해장국에는 들깨가루 ~!
}
}
class 콩나물해장국 extends 해장국{
@Override
public void 간맞추기(){
// 콩나물해장국에는 고춧가루 ~!
}
}
class 취객 {
public void 해장국먹기(해장국 어떤해장국){
어떤해장국.간맞추기();
}
}
public class CastingTest{
public static void main(String[] args){
취객 취객1 = new 취객();
해장국 해장국한그릇 = new 뼈해장국();
// 해장국(상위 클래스) 타입에 뼈해장국(하위 클래스)를 담음. 업캐스팅 !
취객1.해장국먹기(해장국한그릇);
}
}
위의 예제에서, 취객 클래스의 해장국 먹기
라는 메소드를 호출할 때는
그 해장국이 뼈해장국이든 or 콩나물해장국이든 그냥 해장국 타입의 해장국한그릇 만 넘겨주면 취객은 아무런 걱정없이 (= 타입검사 없이) 먹으면 된다.
하지만 만약 이 코드에서 업캐스팅을 사용하지 않는다면?
아래의 코드를 보자.(해장국 객체 생성)
해장국 해장국한그릇 = new 해장국(); // 업캐스팅 안했을 때는 이런식으로 될 것.
그다음 해장국 먹기 메소드를 보자.
public void 해장국먹기(해장국 어떤해장국){
if(뼈해장국 타입){
뼈해장국.간맞추기();
}
else if(콩나물해장국 타입){
콩나물해장국.간맞추기();
}
// 해장국 메뉴가 더 추가된다면 추가로 더 써주어야 한다.
}
다운캐스팅은 자신의 고유한 특성을 잃은 서브 클래스의 객체를 복구시키는 것 이다.
(= 업캐스팅 된 것을 다시 원상태로 돌리는 것)
// 업 캐스팅 선행
Person person = new Student();
// 그 후 다운 캐스팅 해줘야 함. and (Student) 처럼 명시적으로 타입 지정
Student student = (Student) person;
아래의 코드를 보자.
package study.test.java;
class Person{
String name;
public Person(String name) {
this.name = name;
}
}
class Student extends Person{
String age;
public Student(String name) {
super(name);
}
}
public class CastingTest2 {
public static void main(String[] args) {
// 업 캐스팅 선행
Person person = new Student("박세연");
// 다운 캐스팅
Student student = (Student) person;
// name에 접근. student 타입이니까 전부 접근 가능
student.name = "세욘이";
// age에 접근
student.age = "27";
}
}
위의 코드에서는 다운캐스팅을 하면서 형변환할 대상을 지정했다.
하지만 무분별한 다운캐스팅은 커파일 시점에는 오류가 발생하지 않아도,
런타임 오류를 발생시킬 가능성이 있다.
다음의 예시가 그러하다.
// 오류 발생 (ClassCastException)
Student student2 = (Student) new Person("박세진");
// 형변환 타입 명시해서 컴파일 오류는 사라졌지만
// 실제 코드 수행시 ClassCastException 발생
객체의 타입을 구분하기 위해 instanceof
연산자를 사용할 수 있다.
ex) 업 캐스팅 했을 때 참조변수가 가리키는 객체의 타입이 어떤 것인지 구분하기 어려울 때 -> 유용하다 !
참조 변수의 타입과 참조변수가 가리키는 인스턴스의 타입은 항상 같지는 않으므로,
참조변수가 가리키는 인스턴스의 타입이 어떤 것인지 확인하기 위해 사용하는게 instanceof인것.
다음의 코드를 보자.
class Unit {
// 생략
}
class Zealot extends Unit {
// 생략
}
class Marine extends Unit {
// 생략
}
class Zergling extends Unit {
// 생략
}
public class CastingTest {
public static void main(String[] args) {
Unit unit;
unit = new Unit();
unit = new Zealot(); // 업캐스팅
unit = new Marine(); // 업캐스팅
unit = new Zergling(); // 업캐스팅
}
}
클래스 Zealot, Marine, Zergling은 모두 Unit 클래스를 상속하고 있다.
따라서 위 코드에서의 업 캐스팅 코드는 컴파일 오류 없이 정상적으로 수행 된다.
이때 unit 참조 변수가 어떤 객체를 가리키고 있다고 가정하자.
가리키는 객체의 실제 클래스 타입을 구분하기 위해서는 어떻게 해야 할까?
// 적 공격하기.
public void attackEnemy(Unit unit) {
// unit이 가리키는 객체가 Unit일 수도 있고
// Zealot, Marine, Zergling일 수도 있다.
}
위와 같은 메서드가 있다고 할 때, 파라미터로 넘어오는 객체의 실제 클래스 타입을 구분하려면 어떻게 해야할까?
앞에서 언급한 instanceof
연산자를 사용하면 객체의 타입을 쉽게 구별할 수 있다.
public class CastingTest4 {
public static void main(String[] args) {
Unit unit1 = new Unit();
Unit unit2 = new Zealot(); // 업캐스팅
Unit unit3 = new Marine(); // 업캐스팅
Unit unit4 = new Zergling(); // 업캐스팅
if (unit1 instanceof Unit) { // true
System.out.println("unit1은 Unit 타입 ");
}
if (unit1 instanceof Zealot) { // false
System.out.println("unit1은 Zealot 타입이당.");
}
if (unit2 instanceof Zealot) { // true
System.out.println("unit2는 Zealot 타입이다.");
}
if (unit2 instanceof Zergling) { // false
System.out.println("unit2는 Zergling 타입이다.");
}
}
}
if (unit2 instanceof Zealot )
은, unit2가 Zealot 타입으로 형변환 될 수 있는지를 묻는 것이다.
앞에서 Unit unit2 = new Zealot();
을 보면, Zealot 객체를 Unit 타입에 담아서 업 캐스팅을 했음을 알 수 있다. 그러면 unit2는 다시 Zealot 타입으로 다운 캐스팅을 할 수 있으므로, 위의 if문의 결과는 true
가 된다.
객체가 실제로 어떤 타입인지 비교할 수 있으므로, 실행 시점에 발생할 수 있는 형변환 오류를 줄일 수 있다.
참고
자바의 데이터 타입은 크게 기본형/참조형의 2가지로 나뉜다.
위에서 다룬 것은 참조형 변수의 형변환이고, 기본형 변수의 타입변환은 아래에 간단하게 다뤘다.기본 타입간의 타입변환
- boolean 타입을 제외한 나머지 타입들은 서로 타입변환이 가능하다
- 자동 타입 변환 : 작은 크기를 가지는 타입이 큰 크기를 가지는 타입에 저장될 때 발생. () 생략.
(작은 vs 큰 의 구분 :메모리 크기
)int i = 3; double d = 1.0 + i; // double d = 1.0 + (double)i; 에서 형변환이 생략된 것.
2.강제 타입 변환 : 큰 크기의 타입을 작은 데이터 타입으로 쪼개어 저장할 때. 캐스팅 연산자 ()을 사용한다.
double doubleValue = 3.14; int intValue = (int) doubleValue; // 정수부분인 3만 저장된다.
[주의할 점]
사용자로부터 입력받은 값을 변환할 때 값의 손실이 발생하면 안된다.
강제 타입 변환 하기 전 안전하게 값이 보존될 수 있는지 검사해서 올바른 타입 변환이 되도록 해야함 !
자바에 대해 다시금 공부하면서 개념을 정리하는게 이렇게 재밌는 일인 줄 몰랐다.
이럴 줄 알았으면 배울 때 조금이라도 더 열심히 배울걸, 하는 후회도 남지만. 앞으로 열심히 블로그를 포스팅하면서 만회해보려고 한다.
Wow, no matter how many blogs I read, I struggled to grasp the concept of upcasting. However, the analogy and explanation provided here were so perfect that I immediately understood it. Thank you! delta executor website :)
와 어느 블로그를 봐도 업캐스팅 개념이 이해가 잘 안 됐는데 여기 비유랑 설명이 너무 너무 찰떡이라 바로 이해했네요 감사합니다 ;))