오늘은 상속에 대해서 배웠다. 상속에 대한 추상과 구상, 그리고 abstract와 함께.
상속(extends)
(자식)클래스를 품을 수 있는 추상적인(덜 구체적) (부모)클래스
예제코드
// 문제 : 아래와 같이 출력되도록 해주세요.
// 조건 : `숨쉬다` 기능을 중복하지 말아주세요.
class Main {
public static void main(String[] args) {
사람 a사람 = new 사람();
a사람.숨쉬다();
}
}
class 동물 {
void 숨쉬다() {
System.out.println("숨쉬다.");
}
}
class 사람 extends 동물{ //동물 클래스 상속
}
예제 코드를 보면 부모 클래스인 동물의 메서드 숨쉬다 라는 기능을 상속(extends)을 받아서 사람 클래스에는 아무런 메서드가 없지만(눈에 보이지 않을 뿐 상속받아서 숨쉬다 메서드가 존재한다.) 숨쉬다라는 메서드가 작동한다. 부모 클래스인 동물은 광범위하고 덜 구체적이고, 따라서 추상적인 개념이고 사람은 동물로 분류되지만 동물보다는 구체적인 자식클래스이기 때문에 상속이 가능하다.
class 고양이 {
void 숨쉬다() { }
void 야옹() { }
void 뛰어넘다() { }
}
class 검은고양이 extends 고양이 {
void 미래를_예지하다() { }
}
class 흰고양이 extends 고양이 {
void 목숨을_늘린다() { }
}
class 점박이고양이 extends 고양이 {
void 춤춘다() { }
} //상속 받으면 코드가 줄어든다.
흰고양이 a = new 흰고양이();
a.숨쉬다();
a.야옹();
a.미래를_예지하다();
검은고양이 b = new 검은고양이();
b.숨쉬다();
b.야옹();
b.목숨을_늘린다();
점박이고양이 c = new 점박이고양이();
c.숨쉬다();
c.야옹();
//상속 받지 못하면 같은 기능의 메서드가 늘어나 코드가 복잡해진다.
상속은 이런 개념이다. 상속을 하면 오버라이딩(overriding)을 할 수 있는데, 이는 같은 이름을 갖는 메서드를 덮어쓰면서 사용하는 개념이다. 정확하게는 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메소드로 다시 정의하는 것이라고 할 수 있다.
// 문제 : 고무오리, 고무2오리, 흰오리, 청둥오리 객체를 만들고 날게 해주세요. 단 고무오리 계열은 날 수 없습니다.
// 조건 : 메서드는 중복될 수 없습니다.
class Main {
public static void main(String[] args) {
고무오리 a고무오리 = new 고무오리();
a고무오리.날다();
고무2오리 a고무2오리 = new 고무2오리();
a고무2오리.날다();
흰오리 a흰오리 = new 흰오리();
a흰오리.날다();
청둥오리 a청둥오리 = new 청둥오리();
a청둥오리.날다();
로봇오리 a로봇오리 = new 로봇오리();
a로봇오리.날다();
}
}
class 오리 {
void 날다 () {
System.out.println("오리날다.");
}
}
class 흰오리 extends 오리 {
}
class 청둥오리 extends 오리 {
}
class 고무오리 extends 오리 {
void 날다 () { //오버라이딩 (다형성)
System.out.println("오리 못날다.");
}
}
class 고무2오리 extends 오리 {
void 날다 () { //오버라이딩 (다형성)
System.out.println("오리 못날다.");
}
}
class 로봇오리 extends 오리 {
}
예제 코드가 '날다'라는 메서드를 고무오리에서 덮어쓴 형태다. 이게 오버라이딩이라고 한다.
그리고 문제를 풀었다.
// 문제 : 매개변수를 사용해서 전사가 매번 다르게 공격하도록 해주세요.
class Main {
public static void main(String[] args) {
전사 a전사 = new 전사();
a전사.공격("브라이언", "칼");
// 브라이언이(가) 칼(으)로 공격합니다.
a전사.재공격();
// 브라이언이(가) 칼(으)로 공격합니다.
a전사.공격("필립", "창");
// 필립이(가) 창(으)로 공격합니다.
a전사.공격("마크", "지팡이");
// 마크(가) 지팡이(으)로 공격합니다.
a전사.재공격();
// 마크(가) 지팡이(으)로 공격합니다.
a전사.재공격();
// 마크(가) 지팡이(으)로 공격합니다.
}
}
class 사람 {
public void 공격() {
}
}
class 전사 extends 사람 {
String 마지막사람;
String 마지막공격;
void 공격(String a, String b) {
System.out.println(a + "이(가) " + b +"(으)로 공격합니다.");
this.마지막사람 = a;
this.마지막공격 = b;
}
void 재공격() {
System.out.println(this.마지막사람 + "이(가) " + this.마지막공격 + "(으)로 재공격합니다.");
}
}
// 문제 : 정수 i가 가지고 있는 10을 double 형 변수에 넣고 해당 변수의 값을 다시 i에 넣는 코드를 작성해주세요.
class Main {
public static void main(String[] args) {
int i = 10;
double d = i; // 여기선 자동형변환 허용
i = (int) d; // 여기선 자동형변환 불가능 (형변환(캐스팅))
System.out.println(i);
}
}
이거는 어렵지 않게 이해가 되는데,
// 문제 : 자동차 리모콘이 페라리 객체를 가리키게 한 후 해당 리모콘이 가리키고 있는 객체를
//다시 페라리 리모콘으로 가리키게(참조하게) 하는 코드를 작성해주세요.
class Main {
public static void main(String[] args) {
// `a페라리` 변수안의 리모콘은 버튼 개수가 3개 여야 한다.
페라리 a페라리 = new 페라리();
// `a자동차` 변수안의 리모콘은 버튼 개수가 2개 여야 한다.
자동차 a자동차;
// 수동형변환, 수동캐스팅
// `a페라리`안에 있던 리모콘은 가지고 있던 버튼 3개 중에서 `뚜껑이_열리다` 버튼이 뽑힌 후 `a자동차`에 저장된다.
a자동차 = (자동차)a페라리;
// 자동형변환, 자동캐스팅
// 자바가 판단하기에 안전한 경우만 자동형변환이 가능하다.
// 리모콘의 버튼 개수를 줄이는 경우는 언제나 안전하다.
a자동차 = a페라리;
// 아래에서는 `a자동차`변수에 들어있던 리모콘에 `뚜껑이_열리다` 버튼이 추가된다.
// 자바에서는 리모콘에 버튼이 빠지는건 상관하지 않지만 추가되는건 굉장히 무서워 한다.
// 왜냐하면 추가된 버튼에 해당된 기능이 연결된 객체에 없을 수도 있기 때문이다.
// 그렇기 때문에 여기서는 무조건 수동형변환을 사용한다.
// 수동형변환을 사용하는 이유는 자바에게 개발자의 의도(이게 실수가 아니라는 뜻)를 명확히 밝히는 것과 같다.
페라리 a페라리2 = (페라리)a자동차;
}
}
class 자동차 {
void 달리다() {}
void 서다() {}
}
class 페라리 extends 자동차 {
void 뚜껑이_열리다() {}
이건 솔직히 봐도 무슨말인지 잘 모르겠다. 어렴풋이 알 것 같다가도 뭔소린가 싶기도 하고 나중에 집 가면 그림으로 한번 그려봐야겠다. 강사님도 지금은 이해가 안될거라고 한번 훑어보라고만 하셨는데 아무리 그래도 당최 모르겠다.
아무튼 내준 숙제 하고 얼른 집 가서 잠을 자야겠다.