[Java] 객체지향 - 2. 다형성과 객체배열, 매개변수에 쓰이는 다형성, 리턴타입에 쓰이는 다형성, 추상클래스, 추상메소드
다형성과 객체배열
- 다형성과 객체배열을 이용하면 여러 인스턴스를 하나의 레퍼런스 변수로 연속 처리할 수 있음
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