JAVA

Class - 3 (상속~Override~다형성)

황민준 2022. 9. 23. 12:46

* 상속(inheritance)

나에게 없는 것을 상속받아서, 내 것처럼 사용하는 것을 의미한다.

일반화라고도 부르고 실선에 속이 빈 삼각형으로 관계를 나타낸다.

private는 상속받지 못한다.

상속받기 위해서 extends 키워드를 사용한다. (extends는 기능의 확장)

자식클래스를 객체화하더라도 부모의 멤버까지 사용 가능하다.

다중 상속을 허용하지 않는다

-> 여러 개의 클래스를 상속받고 싶다면 수평적이 아닌 수직적으로 상속에 상속을 거쳐야한다.

 

* 상속을 사용하는 이유?

코드의 중복을 피할 수 있고, 능력만으로는 만들지 못하는 기술을 받아오기 위해서

 

package chap05.ex01.inherit;

public class Main {

	public static void main(String[] args) {
		// Mammal에 있는 birth() 메소드를 사용해보자.
		// 일반적인 객체화는 mam이 누구의 객체인지 알 수 있다.
		Mammal mam = new Mammal();
		mam.birth();
		
		//상속 받으면 원래 person의 것처럼 person만 객체화하면 mammal의 멤버도 사용 가능
		Person person = new Person();
		
		//부모 것
		person.birth();
		person.eat();
		
		//내 것
		person.social();
		person.useTool();
		person.talk();
		
	}

}
package chap05.ex01.inherit;

public class Mammal { //포유류의 기능

	public void birth() {
		System.out.println("새끼을 낳다.");
	}
	
	public void eat() {
		System.out.println("젖을 먹다.");
	}
}
package chap05.ex01.inherit;

public class Person extends Mammal { // 사람 고유의 기능

	public void useTool() {
		System.out.println("도구를 사용한다.");
	}

	public void social() {
		System.out.println("사회생활을 한다.");
	}

	public void talk() {
		System.out.println("대화를 한다.");
	}
}

 

자식클래스를 객체화했을 때 부모의 생성자가 먼저 호출된다.

-> 자식클래스의 생성자에 super() 라는 부모 생성자가 생략되어 있기 때문이다.

 

package chap05.ex02.seq;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Child ch = new Child();
		//1. main에서 child를 객체화하려고 하면
		//2. 생략 가능한 super()가 Parent()를 호출하고
		//3. 이후 자신을 객체화한다.
	}

}
package chap05.ex02.seq;

public class Parent {

	public Parent() {
		// TODO Auto-generated constructor stub
		System.out.println("부모생성자 호출");
	}

}
package chap05.ex02.seq;

public class Child extends Parent {

	public Child() {
		// TODO Auto-generated constructor stub
		super(); //부모생성자 (생략 가능)
		System.out.println("자식생성자 호출");
	}

}

 

생성자의 호출 순서를 이용하여 자식을 객체화할 때 부모 클래스의 초기화가 가능하다.

 

자식 객체화할 때 인자를 여러 개 받아오고 super를 통해 부모 클래스에서 사용할 인자를 보내고, 자식이 필요한 인자는 본인이 사용한다.

 

package chap05.ex03.superinit;

public class Main {

	public static void main(String[] args) {
		
		// child 객체화할 때 3개의 인자값을 준다.
		// 1. 이것이 모두 child가 쓰는 것으로 보인다.
		Child child = new Child("1번속성", 2, "자식필드");
		System.out.println(child.attr1); //부모 것
		System.out.println(child.attr2); //부모 것
		System.out.println(child.field); //자식 본인 것
	}

}
package chap05.ex03.superinit;

public class Parent {

	public String attr1;
	public int attr2;
	
	public Parent(String attr1, int attr2) {
		// 3. 자식클래스에서 받아 온 인자값을 가지고 초기화
		this.attr1 = attr1;
		this.attr2 = attr2;
	}

}
package chap05.ex03.superinit;

public class Child extends Parent {

	public String field;
	
	public Child(String arg1,int arg2, String arg3) {
		// 부모가 생성되려면 2개의 인자값이 필요하므로 
		// 자식은 기본으로 2개의 인자값을 받아야한다.
		// 2. 실제로는 2개의 인자값은 부모가 필요한 것이라서
		//부모에게 전달하고 
		super(arg1, arg2);
		// 4. 나에게 필요한 건 내가 사용한다.
		this.field = arg3;
	}

}

 

* override

override란 부모클래스로부터 상속받은 메소드를 자식클래스에서 튜닝하는 것이다.

final은 한 번 값이 들어가면 바꿀 수 없는(읽기 전용) 키워드이기 때문에 override할 수 없다.

부모의 원래 메소드를 이용할 수도 있고 아예 새로운 내용을 작성할 수도 있다.

 

package chap05.ex04.tune;

public class Main {

	public static void main(String[] args) {
		
		MyCar car = new MyCar();
		car.start();
		System.out.println("차가 시속 "+car.run()+"km로 달린다.");
		car.turbo = true;
		System.out.println("차가 시속 "+car.run()+"km로 달린다.");
		car.stop();
		
		//test를 사용할 순 있다.
		car.test();

	}

}
package chap05.ex04.tune;

public class ParentCar {

	public void start() {
		System.out.println("시동을 건다.");
	}
	
	public int run() {
		return 50;
	}
	
	public void stop() {
		System.out.println("멈춘다.");
	}
	
	//final은 읽기 전용으로 수정이 불가능
	//따라서 override 불가능
	public final void test() {
		System.out.println("차량 점검");
	}
}
package chap05.ex04.tune;

public class MyCar extends ParentCar {
/*
	//오버라이드 메소드
	@Override //이 어노테이션은 생략 가능
	public int run() {
		//부모 메소드를 이용하되 조금 고친 내용
		return super.run()*3;
	}
*/
/*
	//부모의 흔적을 아예 없애고 내용을 새롭게 만든다.
	@Override 
	public int run() {	
		return 200;
	}
*/
	//상황에 따라 부모와 자식 내용을 사용할 수 있도록
	boolean turbo = false;
	
	@Override 
	public int run() {	
		if(turbo==true) {
			return 200;
		}else {
			return super.run();
		}
	}
}

 

* overrideoverload 차이는?

overload는 같은 이름의 생성자 혹은 메소드를 매개변수의 개수나 타입의 차이로 구분하여 사용하는 것이고, override는 상속 관계에서 부모의 메소드를 자식이 재정의하여 사용하는 것이다.

 

* 다형성이란?

같은 부모를 상속받은 클래스는 같은 타입으로 들어갈 수 있다는 성질이다.

부모의 메소드들과 자식이 override한 메소드만 사용가능하다 -> 자식의 고유한 기능(메소드)를 사용할 수 없다.

다형성을 이용하여 부모 타입으로 자식 객체를 담을 때, 직계 부모로만 담을 수 있다. ex) BirdsCat 객체를 담을 수 없다.

 

package chap05.ex05.poly;

public class Main {

	public static void main(String[] args) {
		
		// 첫째~다섯째 방 사용하기
		ChildOne ch1 = new ChildOne();
		ch1.useRoom();
		
		ChildTwo ch2 = new ChildTwo();
		ch2.useRoom();
		
		ChildThree ch3 = new ChildThree();
		ch3.useRoom();
		
		ChildFour ch4 = new ChildFour();
		ch4.useRoom();
		
		ChildFive ch5 = new ChildFive();
		ch5.useRoom();
		
		//만약 30명 이라면? -> 30개의 변수가 필요하다.
		//코드 수정 시 일이 많아진다.
		//다형성을 활용한 예시
		
		ParentHouse house; //변수는 하나만 선언
		
		house = new ChildOne();
		house.useRoom();
		
		house = new ChildTwo();
		house.useRoom();
		
		house = new ChildThree();
		house.useRoom();
		
		house = new ChildFour();
		house.useRoom();
		
		house = new ChildFive();
		house.useRoom();
		
		//다형성은 하나의 변수이므로 객체가 선언되고 한 번만 사용될 경우에는 유용하다.
		//하지만 자주 사용될 경우 부적합하다.
		//코드 수정이 적고 메모리 사용에 장점이 있다.
	}

}
package chap05.ex05.poly;

public class ParentHouse {

	public void useRoom() {
		System.out.println("방 하나를 사용하다.");
	}
}
package chap05.ex05.poly;

//한 java 파일에 여러 class가 존재 가능하다.
public class ChildOne extends ParentHouse {

	@Override
	public void useRoom() {
		System.out.println("첫째가 방을 사용한다.");
	}
}

//public 키워드는 자바파일을 대표하는 클래스만 가질 수 있다.
//java 파일명과 클래스명이 동일한 클래스만 public
class ChildTwo extends ParentHouse {

	@Override
	public void useRoom() {
		System.out.println("둘째가 방을 사용한다.");
	}
}

class ChildThree extends ParentHouse {

	@Override
	public void useRoom() {
		System.out.println("셋째가 방을 사용한다.");
	}
}

class ChildFour extends ParentHouse {

	@Override
	public void useRoom() {
		System.out.println("넷째가 방을 사용한다.");
	}
}

class ChildFive extends ParentHouse {

	@Override
	public void useRoom() {
		System.out.println("다섯째가 방을 사용한다.");
	}
}

 

* 다형성의 장점과 단점

장점 : 각각의 자식클래스를 객체화하여 사용하면 변수가 그만큼 늘어나서 코드 수정이 복잡한데, 부모클래스 타입의 변수로 다형성을 이용하면 하나의 변수로 사용가능해서 코드 수정과 메모리 관리에 유용하다.

 

단점 : 하나의 변수에 자식의 객체가 담기기 때문에 여러 번 사용하는 코드일 경우 불리하다. 그리고 자식의 고유한 기능(메소드)를 사용할 수 없다.

 

java파일에 여러 개의 클래스를 사용 가능하다.

, 이 때 publicjava파일명과 클래스명이 같은 클래스에만 사용할 수 있다.

 

자식클래스에서 부모클래스로 변환하는 것을 promotion, 부모클래스에서 자식클래스로 변환하는 것을 casting 이라고 한다.

 

promotion을 통해 부모 타입으로 들어갔을 땐 부모의 메소드, 오버라이드한 메소드를 사용 가능하다. (단, 자식만의 고유한 메소드는 사용 불가)

 

package chap05.ex06.promotion;

/*
 
 			VerteBrate
		  |					|
	Mammal		  Birds
		|	|			|		  |
	Dog	Cat	Duck		Chicken
	
 */
class VerteBrate{ //척추동물
	
}

class Mammal extends VerteBrate{ //포유류
	
}

class Birds extends VerteBrate{ //조류
	
}

class Dog extends Mammal{ //개
	
}

class Cat extends Mammal{ //고양이
	
}

class Duck extends Birds{ //오리
	
}

class Chicken extends Birds{ //닭
	
}

public class Promotion {
	
	public static void main(String[] args) {
		
		//개, 고양이, 닭, 오리 모두 척추동물이다. 그래서 안에 들어갈 수 있다. (직계 부모로만 들어갈 수 있다.)
		VerteBrate 척추동물;
		척추동물 = new Chicken();
		척추동물 = new Duck();
		척추동물 = new Dog();
		척추동물 = new Cat();

		//고양이는 조류에 들어갈 수 없다. (직계가 아니어서)
		//Birds birds= new Cat();
		
		//오리는 포유류에 들어갈 수 없다.
		//Mammal mal = new Duck();
	}

}

 

casting을 통해 자식의 고유한 메소드를 사용하기 위해선 다시 자식타입으로 변환해야한다.

 

부모 밑에 자식이 많을 경우 자식으로 돌아갈 때 잘못 들어가는 때가 있다.

이 때는 instanceof를 사용하여 객체를 확인한다.

 

package chap05.ex07.cast;

public class Cast {

	public static void main(String[] args) {
		// 포유류
		Mammal mal = null;
		mal = new Dog(); // 프로모션을 통해 부모의 형태로 들어갔을 때
		mal.birth(); // 부모의 메소드 사용 가능
		mal.eat(); //부모로부터 받았지만 오버라이드한 메소드도 사용 가능
		
		//mal.bark(); // 고유한 메소드는 사용 불가능
		
		// 고유메소드를 사용하기 위해서는 개로 다시 되돌아 가야한다.
		Dog dog = (Dog) mal; // 큰 것에서 작은 것으로 돌아갈 때는 캐스팅이 필요하다.
		dog.bark();
		
		// 되돌아 갈 때 잘못 되돌아 가는 경우가 있다. (개발자 실수)
		//Cat cat = (Cat) mal;
		//cat.mew();
		// 실수 방지용 코드
		if(mal instanceof Cat) {
			Cat cat = (Cat) mal;
			cat.mew();
		}else {
			System.out.println("이 객체는 고양이가 아닙니다.");
		}
	}

}
package chap05.ex07.cast;

public class Mammal {

	public void birth() {
		System.out.println("새끼를 낳는다.");
	}
	
	public void eat() {
		System.out.println("젖을 먹인다.");
	}
}
package chap05.ex07.cast;

public class Cat extends Mammal {

	//오버라이드
	public void birth() {
		System.out.println("3마리 낳는다.");
	}
	
	public void mew() { //mew()는 고양이의 고유 특성
		System.out.println("야옹");
	}
}
package chap05.ex07.cast;

public class Dog extends Mammal {

	//오버라이드
	public void birth() {
		System.out.println("5마리 낳는다.");
	}
	
	public void bark() { //bark()는 개의 고유 특성
		System.out.println("멍멍");
	}
}

 

* 필드 다형성

원래는 자식 각각의 클래스를 객체화하여 각자의 변수에 담아 사용하지만 필드 다형성을 통해 타입의 변화 없이 여러 객체를 수용 가능하다.

 

package chap05.ex08.field;

public class Mage {

	public static void main(String[] args) {

		//필드 다형성을 활용하지 않은 경우
		//객체를 담기 위해서 각각의 변수가 필요하다.
		Fire fire = new Fire();
		System.out.println(fire.casting());
		
		Ice ice = new Ice();
		System.out.println(ice.casting());
		
		Light light = new Light();
		System.out.println(light.casting());
		
		//필드 다형성을 활용한 경우
		//하나의 변수로 여러 객체를 수용할 수 있다.
		Spell spell = null;
		
		spell = new Fire();
		System.out.println(spell.casting());
		
		spell = new Ice();
		System.out.println(spell.casting());
		
		spell = new Light();
		System.out.println(spell.casting());

	}

}
package chap05.ex08.field;

public class Spell {

	public String casting() {
		return "주문을 외운다.";
	}
}
package chap05.ex08.field;

public class Fire extends Spell {

	@Override
	public String casting() {
		// TODO Auto-generated method stub
		return "화염 " + super.casting();
	}

}
package chap05.ex08.field;

public class Ice extends Spell {

	@Override
	public String casting() {
		// TODO Auto-generated method stub
		return "냉기 " + super.casting();
	}

}
package chap05.ex08.field;

public class Light extends Spell {

	@Override
	public String casting() {
		// TODO Auto-generated method stub
		return "번개 " + super.casting();
	}

}

 

* 하나의 부모 타입으로 사용해서 얻는 이점

같은 타입의 값들을 연속된 공간에 저장하여 사용할 수 있는 배열을 이용할 수 있다.

그래서 각 인덱스에 자식의 객체를 담아놓고 원할 때 꺼내서 사용할 수 있다.

 

package chap05.ex08.field;

public class ArrMage {

	public static void main(String[] args) {
		
		// 하나의 데이터 타입으로 여러 데이터를 담을 수 있어서 생기는 이점
		Spell[] spells = new Spell[3];
		spells[0] = new Fire();
		spells[1] = new Ice();
		spells[2] = new Light();
		
		for(Spell spell : spells) {
			System.out.println(spell.casting());
		}

	}

}

 

* 매개변수 다형성

매개변수 다형성을 사용하지 않을 때, overload를 활용하면 사용자 입장에선 편할 순 있어도 개발자 입장에선 결국 메소드를 여러 개 만들어야 한다.

그래서 매개변수의 다형성을 이용하여 매개변수를 부모타입 변수로 넣는다면 그 변수에 들어가는 자식 객체에 따라 결과가 나오기 때문에 만드는 입장에서 편하다.

 

package chap05.ex09.refer;

public class Main {

	public static void main(String[] args) {
		
		//레이서를 준비시키고
		Racer racer = new Racer();
		//운전할 차량을 준비한다.
		Car car = new Maserati();
		//그리고 차량 제공
		racer.drive(car);
		
		racer.drive(new McLaren());
		racer.drive(new Ferrari());
		
	}

}
package chap05.ex09.refer;

public class Ferrari extends Car {

	@Override
	public void run() {
		System.out.println("페라리 달린다.");
	}

}
package chap05.ex09.refer;

public class Maserati extends Car {

	@Override
	public void run() {
		System.out.println("마세라티 달린다.");
	}

}
package chap05.ex09.refer;

public class McLaren extends Car {

	@Override
	public void run() {
		System.out.println("맥라렌 달린다.");
	}

}
package chap05.ex09.refer;

public class Car {

	public void run() {
		System.out.println("차량이 달린다.");
	}
}
package chap05.ex09.refer;

public class Racer {

	//매개변수 다형성을 이용하지 않을 경우
	//매개변수 데이터 타입에 따라 각각의 메소드를 만들어야 한다.
	//각 차량의 고유 메소드를 사용할 수 있는 장점은 있지만
	//차량이 늘어날수록 메소드도 늘어나게 된다.
/*
	public void drive(Ferrari ferrari) {
		ferrari.run();
	}
	
	public void drive(Maserati mas) {
		mas.run();
	}
	
	public void drive(McLaren mc) {
		mc.run();
	}
*/	
	//매개변수 다형성을 사용하면 차량이 아무리 늘어나도 하나의 메소드로 대응이 가능하다.
	
	public void drive(Car car) {
		car.run();
	}
}

 

* 객체를 변수에 담기 vs new를 통해 객체를 바로 쓰기

객체를 변수에 담을 경우 다른 코드에서 계속 재활용이 가능하지만 변수를 사용한다는 단점이 있다.

객체를 변수에 담지 않고 new를 통해 바로 객체를 사용할 경우엔 변수를 사용하지 않는 것이 장점이지만 값의 재활용이 불가능하다.