상속은 계층화를 통해 부모, 자식이라는 관계를 수직적으로 설계하는 기술이다.
공통된 부분을 Animal 클래스에서 구현하고 자식은 해당 동작을 쉽게 가져다 쓸 수 있도록 extends를 통해서 상속관계를 만들어주는 원리이다.
자바에서 최상위 Root 클래스는 Object 클래스이다.
눈에 보이지 않지만 모든 클래스에는 부모로 명시된 extends Object가 붙어 있다.또한, super()도 눈에 보이지 않는 기본 생성자로 사용되므로 하나의 클래스가 실행될 때 super()는 자식이 아닌 부모부터 생성하기 때문에 Object가 생성되고, Animal이 생성된다. Animal을 생성한 뒤에 Dog를 생성하는 것도 마찬가지이다.
[ Object - Animal - Dog == Cat ] 이런 상속체이닝 구조로 구성되어 있다.
그런데 나와 다른 사람은 아무런 관계가 없기 때문에 다른 사람의 바구니를 사용하지 못한다.
하지만 나의 부모를 생각 해보자 부모에게 바구니가 있으면 자식은 부모의 바구니를 사용 할 수 있다.(허락해 주면,상속해 주면)
→ 자식과 부모는 상속 관계이기 때문에 자식은 부모의 것을 얼마든지 사용 가능하다.
Dog | Cat |
---|---|
eat() | eat() |
(1)
public class Dog {
public void eat() {
System.out.println("개는 먹는다");
}
public class Cat {
public void eat() {
System.out.println("고양이는 먹는다");
}
(2)
ublic class Basic {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
Cat cat = new Cat();
cat.eat();
}
}
Animal | |
---|---|
Dog | Cat |
eat() | eat() |
(1)
public class Animal {
public void eat() {
System.out.println("동물은 먹는다.");
}
}
(2)
public class Dog extends Animal {
}
public class Cat extends Animal {
}
(3)
public class Basic {
public static void main(String[] args) {
자식으로 받음
// Dog dog = new Dog();
// dog.eat();
부모로 받음
// 부모-자식간의 자동 형변환, 업캐스팅이 된다.
Animal ani = new Dog();
ani.eat();
ani = new Cat();
ani.eat(); }
}
만약 Dog, Cat의 중복적인 속성을 밖으로 빼서 하나의 클래스로 만들면 어떨까.
즉, 이들의 부모인 Animal에만 eat()를 만들어서 상속한다면 코드는 복잡해지지만 유지보수 측면에서 훨씬 이점이 많을 것이다.
상속을 할 때는 자식 클래스에서 extends를 붙이는데 '나를 확장해서 부모까지 영향을 행사한다' 정도로 볼 수 있다. 이 클래스를 사용할 때는 보통 자식 클래스에서 생성하여 받는 것보다 부모 클래스에서 생성한다.
상속의 재정의(Override)란
상속받은 자식 클래스가 부모 클래스의 동작을 수정하는 것
=> (2)에서 아쉬운 점을 찾아보면 나는 Dog는 "개는 먹는다." Cat은 "고양이는 먹는다."
라고 표현하고 싶은데 공통점으로 묶는다는 이유로 "동물은 먹는다."로 썼다.
재정의를 하지 않으면 부모가 가진 내용을 변화없이 오직 그대로 쓰게된다.
Dog dog = new Dog();로 객체를 만들면 super()를 통해
부모인 Animal() 객체가 메모리에 먼저 생성된 뒤에 Dog()객체가 생성된다.
Dog() 객체는 상속받아 확장된 상태기 때문에 Animal까지 접근가능한 Dog타입의 객체라 볼 수 있다.
dog가 메모리를 가리키게 될 때 당연히 Animal ~ Dog까지의 범위에 도달할 수 있다.
만약 재정의를 할 경우 같은 메서드라면 Animal이 무시되고 Dog의 메서드가 실행된다.
Animal ani = new Dog();로 객체를 만들면 마찬가지로 super()를 통해 부모인 Animal() 객체가 메모리에 먼저 생성된 뒤에 Dog()객체가 생성된다.
Dog() 객체는 Animal타입이기 때문에 ani가 메모리를 가리키게 될 때 Animal의 범위에만 도달할 수 있다.
만약 재정의를 할 경우 같은 메서드라면 재정의를 했는지 찾아가게 되어있고 재정의가 되어있을 경우에는 Dog의 메서드가 실행된다. (동적 바인딩)
즉, Dog의 기능을 몰라도 부모의 타입으로 Dog의 메서드를 재정의할 수 있다.
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("개는 먹는다.");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("고양이는 먹는다.");
}
}
수직적 설계의 내용과 다 동일하며 Dog를 Animal의 내용을 재정의해주면 된다.
만약 자식의 메서드 중에 재정의 되지 않은 메서드는 부모타입으로 만들 때 어떻게 해야 할까.
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("고양이는 먹는다.");
}
public void night() {
System.out.println("고양이는 야행성이다.");
}
}
이런 경우 ani = new Cat(); ani.night();로 하면 에러가 난다.
이럴 때 다운 캐스팅이 필요하다. 부모의 타입을 자식의 타입으로 형변환 해야 한다.
((Cat)ani).night();
바로 이렇게 다운 캐스팅을 강제적으로 해주면 된다.
출처 : https://yulfsong.tistory.com/51