JAVA

[JAVA] 다형성, 추상 클래스, 인터페이스

shb 2022. 2. 9. 18:28

* 다형성  ( Polymorphism )
  하나의 이름의 클래스나 메소드가 '여러 가지 형태의 동작을 하는 능력'
 
  클래스의 다형성:
  한 타입의 참조변수로 여러타입의 객체를 참조 가능. (참조변수는 주소 값을 저장)
  조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조가능한 것
 
  메소드의 다형성:
  메소드 오버로딩, 메소드 오버라이딩

package com.lec.java.oop01;

public class Vehicle {
	
	private int speed;

	public int getSpeed() {
		return speed;
	}

	public void setSpeed(int speed) {
		this.speed = speed;
	}
	
	public void displayInfo() {
		System.out.println("--- Vehicle 정보 ---");
		System.out.println("speed: " + speed);
	}
	
	

}

Vehicle 클래스

package com.lec.java.oop01;

public class Car extends Vehicle {

	private int oil;

	public int getOil() {
		return oil;
	}

	public void setOil(int oil) {
		this.oil = oil;
	}
	
	@Override
	public void displayInfo() {
		System.out.println("--- Car 정보 ---");
		System.out.println("speed: " + getSpeed());
		System.out.println("oil: " + oil);
	}
	

}

Vehicle 클래스를 상속받는 Car 클래스

package com.lec.java.oop01;

public class HybridCar extends Car {

	private int electricity;

	public int getElectricity() {
		return electricity;
	}

	public void setElectricity(int electricity) {
		this.electricity = electricity;
	}
	
	@Override
	public void displayInfo() {
		System.out.println("--- HybridCar 정보 ---");
		System.out.println("speed: " + getSpeed());
		System.out.println("oil: " + getOil());
		System.out.println("electricity: " + electricity);
	}
	
	
}

Car 클래스를 상속받는 HybridCar 클래스

public class Polymorphism01Main {

	public static void main(String[] args) {
		System.out.println("다형성(Polymorphism)");
		
		System.out.println();
		
		// v1, c1, h1 의 타입이 다르다
		// 각각의 타입에 맞는 인스턴스 생성한뒤 대입
		Vehicle v1 = new Vehicle();
		Car c1 = new Car();
		HybridCar h1 = new HybridCar();
		
		// 각각의 타입에 오버라이딩 된 메소드라 동작함
		v1.displayInfo();
		c1.displayInfo();
		h1.displayInfo();
		
		System.out.println();
		
		// car1, car2 는 동일한 Vehicle 타입변수
		Vehicle car1 = new Car();  // 조상 <- 자손 (가능)
		Vehicle car2 = new HybridCar();
		Car car3 = new HybridCar();
		
//		HybridCar car7 = new Vehicle();  // 자손 <- 조상 (불가)
		
		Object car8 = new Vehicle();
		
		// car1 ~ car3 변수 타입에 관계없이
		// 오버라이딩된 메소드가 '알아서' 동작한다.
		car1.displayInfo();  // Car
		car2.displayInfo();  // HybridCar()
		car3.displayInfo();  // HybridCar()
		
		System.out.println("\n 프로그램 종료");
	} // end main()
	
	// TODO

} // end class

Vehicle car1 = new Vehicle();

Vehicle car2 = new Car();

Vehicle car3 = new HybridCar();

 

클래스의 다형성 -> 한 타입의 참조변수(Vehicle)여러타입의 객체를 참조 가능

: Vehicle 타입의 참조변수(car1, car2, car3)을 통해 Vehicle, Car, HybridCar 클래스를 사용할 수 있다.)

조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조가능

다형성에 의해서, 자식타입 객체가 부모타입으로 자동 형변환 가능

 

인스턴스 : 실제로 사용할 수 있도록 생성된 클래스( Vehicle car1 = new Vehicle(); 를 통해 Vehicle 클래스를 실제 사용할 수 있도록 메모리 공간(힙 메모리)을 할당받는 것이 클래스가 생성된다는 것)

객체 : 객체지향 프로그램의 대상 또는 생생된 클래스의 인스턴스

참조변수 : 인스턴스를 가리키는 클래스형 변수(car1, car2, car3)

 

참조변수.멤버변수
참조변수.메서드

-> 이렇게 사용

 

참조란? 아래 참고

https://gyubgyub.tistory.com/83

https://jenny-daru.tistory.com/31


* 다형성의 유용성
다형성에 의해서, 자식타입 객체가 부모타입으로 자동 형변환 가능!
부모(조상)타입 만으로도 상속된 모~든 자손 타입들을 담을수 있다.

 

다형성의 유용함 1
부모타입으로 모든 자손 타입들을 담을수 있다.
- 다형성이 없었다면?  각 타입별로 변수들을 만들고 따로따로 사용해야 해서 불편

다형성의 유용함 2
- 다형성의 유용함은 매개변수, 혹은 리턴 타입에도 적용된다
- println의 매개변수로 Object의 참조변수가 넘겨지면, 내부적으로 해당 클래스의 toString() 메소드가 불리게 됨

 

코드만 보아도 알아요
A a = new B()   ←  A 는 B 의 조상타입
R doSomethig(K p1){..}  ← K 는 Z 의 조상타입
Y y = doSomething(new Z()) ← Y 는 R 의 조상타입

 

public class Polymorphism02Main {

	public static void main(String[] args) {
		System.out.println("다형성의 사용 (유용성)");

		// 다형성에 의해서, 자식타입 객체가 부모타입으로 자동 형변환 가능!
		Vehicle car1 = new Vehicle();
		Vehicle car2 = new Car();
		Vehicle car3 = new HybridCar();
		
		
		// 다형성의 유용함 1
		// 부모타입으로 모든 자손 타입들을 담을수 있다.
		Vehicle [] car = new Vehicle[3];
		car[0] = new Vehicle();
		car[1] = new Car();
		car[2] = new HybridCar();
		
		for (int i = 0; i < car.length; i++) {
//			car[i].displayInfo();  // 조상타입 한가지로 한꺼번에 동작 가능
		}
		
		// 다형성이 없었다면?  각 타입별로 변수들을 만들고 따로따로 사용해야 하는 왕불편.
		//		Vehicle car1 = new Vehicle();
		//		Car car2 = new Car();
		//		HybridCar car3 = new HybridCar();
		//		car1.displayInfo();		
		//		car2.displayInfo();
		//		car3.displayInfo();		
		
		// 다형성의 유용함 2
		// 다형성의 유용함은 매개변수, 혹은 리턴 타입에도 적용된다
		// println의 매개변수로 Object의 참조변수가 넘겨지면,
		// 내부적으로 해당 클래스의 toString() 메소드가 불리게 됨
		System.out.println(car1);  // println(Vehicle) ????
		System.out.println(car2);
		System.out.println(car3);
		
		System.out.println();
		
		// instanceof 연산자
		// 용법: 변수/값 instanceof 클래스 
		// 결과: true / false
		System.out.println(car1 instanceof Vehicle);
		System.out.println(car1 instanceof Car);
		System.out.println(car2 instanceof Vehicle);
		System.out.println(car2 instanceof HybridCar);
		
		System.out.println();
		Vehicle car4 = new Vehicle();
		Car car5 = new Car();
		HybridCar car6 = new HybridCar();
		
		driverCar(car4);
		driverCar(car5);
		driverCar(car6);
		
		
		System.out.println("\n 프로그램 종료");
	} // end main()
	
	public static void driverCar(Vehicle v) {
		v.setSpeed(100);
		v.displayInfo();
	}

} // end class

 

 

package com.lec.java.oop03;

public class Polymorphism03Main {

	public static void main(String[] args) {
		System.out.println("다형성의 어려움");
		
		Vehicle car1 = new Vehicle();
		Vehicle car2 = new Car();
		Vehicle car3 = new HybridCar();
		
		car2.setSpeed(10);
		// car2는 Vehicle 타입으로 선언되어 있으므로,
		// Vehicle 클래스에 정의된 메소드를 사용할 수 있다.
		
		// 반면..
//		car2.setOil(100); // 에러
		// car2는 Vehicle 타입으로 선언되어 있으므로,
		// Vehicle이 아니 다른 클래스(심지어 자식 클래스이더라도)에 정의된
		// 메소드는 사용할 수 없다.
		// 따라서, 실제로는 Car 타입 인스턴스로 생성되긴 했지만,
		// Vehicle 타입 참조변수로는 Car 클래스에 있는 메소드를 사용할 수 없다.
		
		car2.displayInfo();
		// car2는 Vehicle 타입으로 선언되었으므로,
		// displayInfo()는 Vehicle 클래스에서 정의된 메소드를 찾아가지만,
		// 인스턴스가 생성될 때 Car 타입으로 생성되어서,
		// Car 클래스에서 override된 displayInfo() 메소드가
		// 부모 클래스의 displayInfo()를 덮어써 버리게 됨
		// 결과는 Car의 정보를 출력하게 됨
		
		((Car)car2).setOil(50);
		// 실제로 Car 클래스의 인스턴스로 생성된 car2 변수는
		// 형변환(casting)을 통해서 Car 타입으로 변환할 수 있고,
		// Car 클래스에 정의된 메소드를 사용할 수 있다.
		
		car2.displayInfo();
		
		
		((Car)car1).setOil(10);
		// ClassCastException 발생:
		// 실제로 Vehicle 클래스의 인스턴스로 생성된 car1을 
		// 자식 클래스인 Car로 강제 형변환을 하게 되면 문제가 발생할 수 있다.
		// 예외는 setOil() 을 호출하는 과정이 아니라, 형변환 하는 과정에서 발생된다	
		
		System.out.println("\n 프로그램 종료");
	} // end main()

} // end class

 클래스: 멤버 변수 (+ 생성자) + 메소드 => 데이터 타입


 * 추상 클래스(abstract class): 
    추상 메소드를 가지고 있는 클래스
    클래스 선언할 때 abstract 키워드를 반드시 써 줘야 함
    추상 클래스는 인스턴스를 생성할 수 없다(new 불가능)
 
 * 추상 메소드(abstract method):
   원형(prototype)만 선언돼 있고, 메소드 본체가 정의되지 않은 메소드
   메소드 본체가 없기 때문에 {}부분이 없다.
   메소드 원형 끝에 ;으로 끝냄.
   메소드 이름 앞에 abstract 키워드를 반드시 써 줘야 함

 추상 클래스를 사용하는 목적
   추상 클래스를 상속 받는 자식 클래스에 반드시 구현해야 할 메소드가 있을 경우,
   그 메소드를 추상메소드로 만들어서 반드시 override하도록 하는데 목적이 있다.

 

public class Abstract01Main {

	public static void main(String[] args) {
		System.out.println("추상 클래스(abstract class)");
		
		// 추상클래스의 인스턴스 생성은 불가능!
//		TestAbstract test1 = new TestAbstract();  // 에러
		
		TestClass test2 = new TestClass();
		test2.test = 100;
		System.out.println(test2.testMethod());  // 자식클래스에서 '구현' 되었기 때문에 사용 가능.
		
		// 다형성
		TestAbstract test3 = new TestClass();
		test3.test = 999;
		System.out.println(test3.testMethod());
		
		
		System.out.println("\n 프로그램 종료");
	} // end main()

} // end class


abstract class TestAbstract{
	int test;
	
	public int getTest() {return test;}
	
	// 추상 메소드
	// 수식어 리턴타입 메소드이름(매개변수들, ...);
	// 추상 메소드에는 abstract라는 키워드를 반드시 써 줘야 함.
	public abstract int testMethod();
	
}

//추상 클래스를 상속받는 클래스는 반드시 추상메소드를 구현(implement)해야 함
//추상 메소드의 본체({ ... })를 만들어줘야 함
class TestClass extends TestAbstract {

	@Override
	public int testMethod() {
		return test;
	}
	
}

* 인터페이스(interface):
 1. 모든 메소드가 public abstract으로 선언되고,
 2. 모든 멤버 변수가 public static final로 선언
 특별한 종류의 추상 클래스

 인터페이스는 interface라고 선언
 인터페이스를 구현(상속)하는 클래스에서는 implements 키워드를 사용
 인터페이스를 구현(상속)할 때는 개수 제한이 없다. (다중상속)
 메소드 선언에서 public abstract는 생략 가능
 멤버 변수 선언에서 public static final은 생략 가능

public class Interface01Main {

	public static void main(String[] args) {
		System.out.println("인터페이스(interface)");
		
		TestImpl test1 = new TestImpl();
		test1.testAAA();
		test1.testBBB();
		
		TestImpl2 test2 = new TestImpl2();
		test2.testAAA();
		test2.testBBB();
		test2.testCCC();
		
//		System.out.println(test2.MIN);
		System.out.println(TestInterface.MIN);
		System.out.println(TestInterface2.MIN);

		System.out.println("\n 프로그램 종료");
	} // end main()

} // end class

interface TestInterface {
	// 모든 멤버변수는 public static final
	public static final int MIN = 0;
	int MAX = 100;  // public static final 생략.
	
	// 모든 메소드는 public abstract 로 선언
	public abstract void testAAA();
	void testBBB();  // public abstract 생략
}

interface TestInterface2{
	public static final int MIN = 1;
	public abstract void testAAA();
	public abstract void testCCC();
}

//인터페이스는 인스턴스를 생성할 수 없고,
//다른 클래스에서 구현(implements)해야 함.
class TestImpl implements TestInterface {

	@Override
	public void testAAA() {
		System.out.println("testAAA");
	}

	@Override
	public void testBBB() {
		System.out.println("testBBB");
	}
	
}

// 인터페이스는 다중상속이 가능하다.
class TestImpl2 implements TestInterface, TestInterface2{

	// testAAA() 는 양쪽 인터페이스에 다 있었지만
	// 한번만 implement 하면 된다.
	@Override
	public void testAAA() {
		System.out.println("testAAA");
	}

	@Override
	public void testCCC() {
		System.out.println("testCCC");
	}


	@Override
	public void testBBB() {
		System.out.println("testBBB");
	}
	
}

 

 

* 인터페이스의 다형성

package com.lec.java.oop07;

// 인터페이스에 선언되는 모든 메소드는 public abstract임
// public abstract는 생략 가능
// 인터페이스에 선언되는 메소드는 본체({ ... })가 없음
// 메소드 선언 끝에는 세미콜론(;)으로 끝냄

public interface InterfaceAAA {
	
	public abstract void testAAA();
	
} // end interface InterfaceAAA
package com.lec.java.oop07;

public interface InterfaceBBB {

	public abstract void testBBB();
	
} // end interface InterfaceBBB
package com.lec.java.oop07;

// 인터페이스를 구현(implements)하는 클래스에서
// 인퍼페이스에 선언만 돼있는 메소드들을 구현(정의)해야 함

public class TestImple implements InterfaceAAA, InterfaceBBB{

	@Override
	public void testBBB() {
		System.out.println("InterfaceBBB: test 구현");
	}

	@Override
	public void testAAA() {
		System.out.println("InterfaceAAA: test 구현");
	}

	

} // end class TestImple
package com.lec.java.oop07;

public class Interface03Main {

	public static void main(String[] args) {
		System.out.println("인터페이스와 다형성");
		
		TestImple t1 = new TestImple();
		t1.testAAA();
		t1.testBBB();
		
		System.out.println();
		InterfaceAAA t2 = new TestImple();
		t2.testAAA();
//		t2.testBBB();
		
		// 인스턴스는 TestImple 타입으로 생성됐지만,
		// 참조변수의 선언이 InterfaceAAA 타입으로 선어됐기 때문에
		// testAAA()만 보이고, testBBB()는 보이지 않는다.
		// 형변환(Casting)을 통해서 InterfaceBBB에 선언된 메소드를 사용 가능
		((TestImple)t2).testBBB();
		
		System.out.println();
		InterfaceBBB t3 = new TestImple();
		t3.testBBB();
//		t3.testAAA();
		((TestImple)t3).testAAA();
		
		System.out.println("\n 프로그램 종료");
	} // end main()

} // end class

'JAVA' 카테고리의 다른 글

[JAVA] 정규표현식  (0) 2022.02.17
[JAVA] 예외처리  (0) 2022.02.16
[JAVA] 상속  (0) 2022.02.09
[JAVA] 접근제한자, final, static  (0) 2022.02.09
[JAVA] 클래스  (0) 2022.02.08