TIL/Java

[Java] 객체지향 - 2. 다형성과 객체배열, 매개변수에 쓰이는 다형성, 리턴타입에 쓰이는 다형성, 추상클래스, 추상메소드

yndev 2022. 1. 13. 23:17

다형성과 객체배열

- 다형성과 객체배열을 이용하면 여러 인스턴스를 하나의 레퍼런스 변수로 연속 처리할 수 있음

Car[] car = new car[5];

car[0] = new Sonata[];
car[1] = new Avante();
car[2] = new Grandure();

for(int i = 0; i < car.length; i++){
	car[i].move();		//실행 후엔 동적바인딩 실행. Sonata, Avante, Grandure에있는 move메소드 출력
    }

 

참고코드

		Animal[] animals = new Animal[5];
		
		animals[0] = new Rabbit();
		animals[1] = new Tiger();
		animals[2] = new Rabbit();
		animals[3] = new Tiger();
		animals[4] = new Rabbit();
		
		for(int i = 0 ; i < animals.length; i++) {
			animals[i].cry();
		}

Rabbit의 cry, Tiger의 cry 메소드들을 따로 실행하지 않아도

Animal 클래스가 가지는 메소드를 오버라이딩한 메소드 호출 시 동적바인딩을 이용할 수 있다.


각 클래스별 고유한 메소드를 동작시키기 위해서는 down-casting을 명시적으로 해줘야 하는데 

ClassCastException을 방지하기 위해서 instanceof 연산자를 이용해야 한다.

		for(int i = 0; i < animals.length; i++) {
			
			if(animals[i] instanceof Rabbit) {
				((Rabbit)animals[i]).jump();	
			}else if(animals[i] instanceof Tiger) {
				((Tiger)animals[i]).bite();
			}else {
				System.out.println("호랑이나 토끼가 아닙니다.");
			}
		}

매개변수에 쓰이는 다형성

- 묵시적 형변환을 이용해서 매개변수가 다양한 자식클래스 자료형을 받아줄 수 있다.

Application app = new Application();

app.buy(new Sonata());
app.buy(new Avante());
app.buy(new Grandure());

public void buy(Car c){
		c.pay();
}

리턴 타입에 쓰이는 다형성

- 묵시적 형변환을 이용해서 메소드에서 리턴되는 다양한 자식클래스 자료형을 받아줄 수 있음

Car car = app.randomCar();
Car.move();

	public Car randomCar(){
    int random = (int)(Math.random() * 2);
    return random == 0? new Sonata() : new Avante();
    }

추상클래스 (미완성)

- 추상메소드를 0개 이상 포함하는 클래스

- 추상클래스로는 인스턴스를 생성할 수 없다. ex) Animal animal = new Animal();  (X)

- 추상클래스를 사용하려면 추상클래스를 상속받은 하위 클래스를 이용해서 인스턴스를 생성(다형성 활용)

- 추상클래스를 사용하는 이유추상클래스의 추상메소드는 오버라이딩에 대한 강제성이 부여됨

  -> 여러 클래스들을 그룹화하여 필수 기능을 정의하여 강제성을 부여해 개발 시 일관된 인터페이스를 제공 할 수 있다.

  (상속받은 하위 클래스가 반드시 미완성된 추상메소드를 사용해야 함)

 

추상메소드

- 메소드의 선언부만 있고 구현부가 없는 메소드

- 반드시 abstract 키워드를 메소드 헤드에 작성해야 한다.

public abstract void method();     <-메소드 구현부가 존재하지 않음

 

Product Class (추상클래스)

- 추상클래스는 필드를 가질 수 있다.

- 추상클래스는 생성자도 가질 수 있다. 하지만 직접적으로 인스턴스를 생성할 수 없다.

- 추상클래스는 일반적인 메소드를 가질 수 있다.

- 추가로 미완성메소드(추상메소드)를 만들 수 있다.

  추상메소드가 0개인 경우 선택적으로 클래스에 abstract 키워드 작성하여 인스턴스 생성을 막을 수 있다.

public abstract class Product {
	//필드
	private int nonStaticField;
	private static int staticField;

	//생성자
 	public Product() {}
	
    	//일반적인 메소드
	public void nonStaticMethod() {
		System.out.println("Product 클래스의 nonStaticMethod 호출함...");
	}
	
	public static void staticMethod() {
		System.out.println("Product 클래스의 staticMethod 호출함...");
	}
   	 //미완성메소드(추상메소드)
	public abstract void abstMethod();

 

상단의 Product Class를 상속 받는 SmartPhone Class

- extends 키워드로 클래스를 상속 받을 때 두 개 이상의 클래스는 상속하지 못한다. (모호성 제거)
- SmartPhone 클래스는 Product 클래스를 상속 받아서 구현한다.
- 추상 클래스가 가지는 추상메소드를 반드시 오버라이딩 해야한다. (강제성)

- 추가적으로 메소드를 작성할 수 있다.

package section02.abstractclass;
	//Product클래스 상속받음, 두 개 이상의 클래스는 상속X
public class SmartPhone extends Product /*, java.util.Scanner */ {
	
	//추상메소드 오버라이딩
	@Override
	public void abstMethod() {
		System.out.println("Product 클래스의 abstMethod를 오버라이딩 한 메소드 호출함...");
		
	}

	//추가 메소드 작성
	public void printSmartPhone() {
		System.out.println("SmartPhone 클래스의 printSmartPhone 메소드 호출함...");
	}
}

 

Application Class (Product, SmartPhone 출력 클래스)

- 추상 클래스는 인스턴스 생성이 불가하다 -> 추상클래스를 상속받은 객체(SmartPhone) 이용

- SmartPhone은 SmartPhone타입이기도 하지만 Product 타입이기도 함

- Product 인스턴스는 생성할 수 없지만 자식 인스턴스를 참조하게끔

  추상클래스를 레퍼런스(참조)변수로 사용할 수 있다. => 다형성성립

- 타입 자체가 Product이기 때문에 ctrl + 클릭하면 Product클래스의 메소드로 확인됨.

  하지만 실행하면 SmartPhone의 메소드가 호출됨 => 동적바인딩

package section02.abstractclass;

public class Application {

	public static void main(String[] args) {

		//추상 클래스는 인스턴스 생성X
		//Product product = new Product();
		
		//추상클래스를 상속 받은 객체
		SmartPhone smartPhone = new SmartPhone();
		
		System.out.println(smartPhone instanceof SmartPhone);	//true
		System.out.println(smartPhone instanceof Product);	//true
		
		//추상클래스를 레퍼런스(참조)변수로 사용할 수 있다. => 다형성성립
		Product product = new SmartPhone();
		
		//동적 바인딩에 의해 SmartPhone의 메소드가 호출된다. 
		product.abstMethod();	
		
		//추상 클래스가 가진 메소드도 호출할 수 있다.
		product.nonStaticMethod();
		
		//static 메소드는 그냥 사용 가능함 (인스턴스 생성 불필요)
		product.staticMethod();
		
	}

}

인터페이스

- 추상메소드와 상수 필드(public static final __ = __;)만 가질 수 있는 클래스의 변형체

- 사용 목적

 1. 추상클래스와 비슷하게 필요한 기능을 공통화해서 강제성을 부여할 목적 (표준화)

 2. 자바의 단일상속의 단점을 극복 (다중상속)

    ex) public class Application implements InterOne, InterTwo{ } - 인터페이스를 상속할 땐 점선으로 표현

 

InterProduct Class (Interface Class)

- 인터페이스가 인터페이스를 상속 받을 때 extends 키워드를 이용함. 여러 인터페이스를 다중 상속 받을 수 있다.

- 인터페이스는 상수 필드만 작성 가능하다. (public static final 제어자 조합) 반드시 선언과 동시에 초기화 해야함

- 상수필드만을 가질수 있기 때문에 모든 필드는 묵시적으로 public static final이다. (public static 생략가능)

- 인터페이스는 생성자를 가질 수 없다.

- 인터페이스는 구현부가 있는 non-static 메소드를 가질 수 없다.

- 몸체(구현부)가 없는 abstract 메소드 작성 가능

- 인터페이스 안에 작성한 메소드는 묵시적으로 public abstract의 의미를 가진다.
  따라서 인터페이스의 메소드를 오버라이딩 하는 경우 반드시 접근제한자를 public으로 해야 오버라이딩이 가능하다. 

- 몸체(구현부)가 있는 static메소드는 작성이 가능하다. (JDK 1.8 추가된 기능)

- default 키워드를 사용하면 non-static 메소드도 작성 가능하다. (JDK 1.8 추가된 기능)

package section03.interfaceimplements;

public interface InterProduct extends java.io.Serializable, java.util.Comparator{

	//인터페이스는 상수 필드만 작성 가능하다.
	 public static final int MAX_NUM = 100;
	 
	 //public static 생략
	 int MIN_NUM = 10;
	 
	 //public InterProduct() {}
	 
	 //public void nonStaticMethod() {}

	 //abstract 메소드 작성 가능
	 public abstract void nonStaticMethod();
	 
	//public abstract 생략
	 void abstMethod();
	 
	 //static메소드는 작성 가능
	 public static void staticMethod() {
		 System.out.println("InterProduct 클래스의 staticMethod 호출됨...");
	 }
	 
	 //default 키워드 사용(non-static 메소드도 작성 가능)
	 public default void defaultMethod() {
		 System.out.println("InterProduct 클래스의 defaultMethod 호출됨...");
	 }
	 
	 
	 
}

 

InterProduct(Interface)를 상속받는 Product Class

- 클래스에서 인터페이스를 상속 받을 때에는 implements 키워드를 사용한다.
- 인터페이스는 여러 개를 상속 받을 수 있다.

- extends로 다른 클래스를 상속 받는 경우에도 인터페이스도 추가 상속이 가능 (extends - implements 순서로 쓰기)

- InterPorduct를 상속 받으면 오버라이딩 해야 하는 메소드의 강제성이 부여됨

  인터페이스에 작성한 추상메소드를 전부 오버라이딩 해야 한다.

- static 메소드는 오버라이딩 불가

- default 메소드는 인터페이스에서만 작성 가능(오버라이딩 불가)

- default 키워드를 제외하면 오버라이딩 가능

package section03.interfaceimplements;

public class Product extends java.lang.Object implements InterProduct, java.io.Serializable {

	@Override
	public void nonStaticMethod() {
		System.out.println("InterPorduct의 nonStaticMethod 오버라이딩한 메소드 호출됨...");
	}

	@Override
	public void abstMethod() {
		System.out.println("InterProduct의 abstMethod 오버라이딩한 메소드 호출됨...");
	}

	//static 메소드는 오버라이딩 할 수 없다.
	//@Override
	// public static void staticMethod() {}
	
	 //default메소드는 인터페이스에서만 작성 가능하다.
	//@Override
	//public default void defaultMethod() {}
	
	//default 키워드를 제외하면 오버라이딩 가능
	@Override
	public void defaultMethod() {
		System.out.println("Product 클래스의 defaultMethod 호출됨...");
	}
}

 

Application Class (출력 클래스)

- 인터페이스 인스턴스를 생성하지 못하고 생성자 자체가 존재하지 않는다.

- 인터페이스 클래스는 레퍼런스 타입으로만 사용 가능

- 정적으로는 인터페이스 메소드랑 연결이 되는 것 같지만 실행하면 Product 안에 오버라이딩한 메소드가 출력됨

  =>동적바인딩

- 오버라이딩하지 않으면 인터페이스의 default 메소드로 호출됨

- static 메소드는 인터페이스명,메소드명(); 으로 호출함

- 상수 필드 접근도 인터페이스명.필드명 으로 접근함

package section03.interfaceimplements;

public class Application {

	public static void main(String[] args) {

		//인터페이스 생성자X
		//InterProduct interProduct = new InterProduct();
		
		//레퍼런스 타입으로만 사용 가능
		InterProduct interProduct = new Product();
		
		//인터페이스의 추상메소드 오버라이딩한 메소드로 동적바인딩에 의해 호출됨
		interProduct.nonStaticMethod();
		interProduct.abstMethod();		
		
		//default 메소드로 호출
		interProduct.defaultMethod();
		
		//static 메소드 호출
		InterProduct.staticMethod();
		
		//상수 필드 접근
		System.out.println(InterProduct.MAX_NUM);
		System.out.println(InterProduct.MIN_NUM);
	}

}

 

추상클래스 인터페이스 구분

상속 규칙 
- 클래스가 인스페이스를 상속받을 땐 implements
- 클래스가 클래스를 상속 받을 땐 extends
- 인터페이스가 인터페이스를 상속 받을 땐 extends