2022.09.27 (화요일)

yeonicerely·2022년 9월 27일
0

⭐다형성⭐

  • 다형성이란? : 하나의 객체가 여러가지 자료형 타입을 가질 수 있는 것 동일한 메소드가 다양한 형태로 사용될 수 있는 것을 다형성이라고 볼 수 있다. 만약 getArea()라는 동일한 이름의 함수가 원의 넓이, 삼각형의 넓이, 사각형의 넓이 등을 return할 수 있다면 이도 다형성의 예시이다.
  • 다형성을 구현할 수 있는 방법 (1) 오버로딩& 오버라이딩 (2) 업캐스팅 & 다운캐스팅 (3) 함수형 인터페이스
  • 수업에서는 주로 업캐스팅을 다형성이라고 지칭한다

1. 업캐스팅 & 다운캐스팅

A. 업캐스팅: 부모 클래스가 자식 클래스의 객체를 가리키는 것

1) 업캐스팅을 하면 자식의 메소드를 덮어쓴다. 하지만 부모 클래스에서 존재하지 않는 메소드는 이용할 수 없다.

2) (Circle) circle과 같은 일반적인 형변환을 시킬 필요가 없다.

    static class Cake{

        public void yummy(){
            System.out.println("Yummy Cake");
        }
    }

    static class CheeseCake extends Cake{

        @Override
        public void yummy(){
            System.out.println("Yummy Cheese Cake");
        }
    }

    public static void main(String[] args) {
        Cake c1 = new CheeseCake();
        CheeseCake c2 = new CheeseCake();

        c1.yummy();
        c2.yummy();
    }

// 둘다 Yummy Cheese Cake가 출력됨

3) 업캐스팅 시 메모리의 변화


class A{
	private int a;

	public getA(){
	...
	}
}

class B extends A{
	private int b;

    public getB(){
    ...
    }

}

A a = new B(); // 1번
B b = new A(); // 2번, 에러

(1) A a = new B()

: a가 B를 참조하였으므로 부모 클래스인 A와 B 모두 존재함 → 따라서 주소값 2000을 통해서 들어가면 b를 확인할 수 있으므로 컴파일 에러가 발생하지 않음

(2) B b = new A()

: b를 A를 참조하였으므로 B 부분은 제거됨 → 주소값 2000을 통해서 들어가면 b를 확인할 수 없으므로 컴파일 에러가 발생함

cf) 현업에서는 업캐스팅이라는 용어를 잘 쓰지 않는다.

cf) 수업에서 주로 다형성이라고 부른다.

💡 업캐스팅: **부모 = 자식**

B. 다운캐스팅: 업캐스팅을 원래대로 돌리는 것

    static class Cake{

        public void yummy(){
            System.out.println("Yummy Cake");
        }
    }

    static class CheeseCake extends Cake{

        @Override
        public void yummy(){
            System.out.println("Yummy Cheese Cake");
        }
    }

    public static void main(String[] args) {
        Cake c1 = new CheeseCake();
        CheeseCake c2 = new CheeseCake();

        c1.yummy();
        c2.yummy();
    }

// 둘다 Yummy Cheese Cake가 출력됨

→ 위의 코드에서 c1은 CheeseCake라는 객체를 가리키지만 클래스 타입은 Cake이다. 만약 CheeseCake의 클래스 타입을 원래대로인 CheeseCake로 돌려주고 싶으면 다운 캐스팅을 사용할 수 있다.

CheeseCake c2 = (CheeseCake) c1; 
  • 일반적인 형변환의 형태를 보인다.
  • 업캐스팅을 하지 않은 상태에서 다운 캐스팅을 할 수 없다.
    • 만약 Cake c1 = new CheeseCake();에서처럼 new CheeseCake()를 담고 있는 변수 c1이 정의되지 않으면 메모리에서 B에 대한 주소값을 찾을 수 없기 때문에 다운 캐스팅을 할 수 없다

C. 다형성(업캐스팅)의 활용

1) 다양한 형태의 input 받기: 부모 클래스를 메소드의 파라미터로 설정하면 자식 클래스를 인자로 받을 수 있다.

class Shape{
	...
	public double getArea(){
		...
	}
}

class Rectangle extends Shape{
	...
	@Override
	public double getArea(){
		...
	}
}

class Triangle extends Shape{
	...
	@Override
	public double getArea(){
		...
	}
}

public class ShapeTest{
	
	public static void print(Shape s){
		System.out.println("넓이는 "+s.getArea());
	}

	public static void main (String arg[]){
		Rectangle s1 = new Rectangle(10,10);
		Triangle s2 = new Triangle(10,10);

		print(s1);
		print(s2);

	}

→ Rectangle s1 = new Rectangle(10,10); 이므로 print(s1)에서 print(Shape s = new Rectangle(10,10)의 형태가 되므로 이 부분에서 업캐스팅이 발생했다고 볼 수 있다.

2) instanceof 연산자

  • [객체명(변수명)] instanceof [클래스 타입]
💡 객체를 찾아서 타입을 확인해라!
  • 변수명이 가지는 객체가 해당 클래스 타입이거나 해당 클래스를 상속받았으면 true, 그렇지 않으면 false → 다운캐스팅이 가능한지를 확인할 수 있다
    • [클래스] instanceof Object는 항상 true: Object는 모든 클래스의 부모 클래스
    • [클래스] instanceof null은 항상 false
  • 클래스와 같은 참조형에서는 사용할 수 있지만, primitive type에서는 사용할 수 없다
  • if 문과 함께 사용됨
public class Poly{

    //instance of 연산자
    public static void print(Shape shape){
        // instance of 연산자는 기본적으로 true와 false를 return 함 --> if 문 사용
        // 객체명(변수명) instanceof 클래스 타입: 해당하면 true, 아니면 false
        // 객체를 찾아서 타입을 확인해라
        if(shape instanceof Rectangle){
            System.out.println("실제 타입은 Rectangle 입니다.");
        }else if (shape instanceof Circle){
            System.out.println("실제 타입은 Circle 입니다.");
        } else{
            System.out.println("알 수 없는 타입입니다.");
        }
    }

    public static void main(String[] args) {
        Shape[] shape = {new Triangle(10,10), new Rectangle(10,10), new Circle(10)};

        print(shape[0]);
        print(shape[1]);
        print(shape[2]);
		}
}

2. 추상클래스와 인터페이스

A. 추상 클래스

  • 현업에서는 잘 사용하지 않고 유사한 목적인 인터페이스를 더 많이 사용함
💡 추상 = Abstract(미완성)

1) 추상 클래스는 한 개 이상의 미완성 메소드를 가진다 → 미완성 메소드는 반드시 자손이 구현해주어야 한다.

2) 인터페이스와는 달리 상속관계에서 사용된다.

3) 객체 생성이 안된다.

abstract class Animal{
    public abstract void move();
} 

class Lion extends Animal{
    @Override
    public void move() {
        System.out.println("사자의 move 메소드입니다.");
    }
}

public class HelloWorld{
    public static void main(String[] args) {

        // Animal animal = new Animal(); abstract 클래스는 객체 생성이 안 됨
        Lion lion = new Lion();
        lion.move();

    }
}

4) 활용

  • 자식 클래스에서 반드시 구현해야 할 내용을 표시할 수 있다: 오버라이드 하지 않으면 컴파일 에러가 발생함
    • 일반 메소드로 정의하면 오버라이드 하지 않아도 컴파일러가 체크할 수 없다.
  • 추상 클래스를 만들어주면 필요없는 코드를 작성할 필요가 없다.

B. ⭐인터페이스 ⭐

💡 인터페이스 = 약속 = 강제 = 표준
  • 상속 관계가 아닌 클래스 사이의 유사성을 인코딩할 수 있다. 협업 관계에서 매우 중요하게 사용된다.
    • 프린터 소프트웨어 같은 경우는 인터페이스를 os를 개발하는 곳에서 제공하고 각각의 class는 프린터 소프트웨어 개발 회사(삼성, LG 등)에서 작성함
  • 상수와 추상 메소드만 올 수 있다. → 자식 클래스에서 메소드를 구현해야 함
    • 단, Java 1.8 이상의 버전에서는 디폴트 메소드도 인터페이스에 넣을 수 있다.
  • 객체 생성은 불가, 참조변수 선언은 가능
  • 인터페이스의 access modifier는 public abstract로 정해져 있으므로 입력해주지 않아도 컴파일러가 알아서 넣어준다
  • 인터페이스는 implements로 상속받을 수 있다
// interface는 상수와 추상함수만 올 수 있음
interface Printable{
    public abstract void print(String doc);
}

class SPrinterDriver implements Printable{
    @Override
    public void print(String doc) {
        System.out.println("삼성 프린터입니다.");
        System.out.println(doc);
    }
}

class LPrinterDriver implements Printable{
    @Override
    public void print(String doc) {
        System.out.println("LG 프린터입니다.");
        System.out.println(doc);
    }
}

public class DriveTest {
    public static void main(String[] args) {
        // 인터페이스도 class와 유사하게 다형성 적용 가능

        // 추상 함수가 있으므로 객체 생성 불가, 참조 변수 선언 가능
        // Printable print = new Printable();
        Printable print = new SPrinterDriver();
        print.print("출력해주세요.");

        print = new LPrinterDriver();
        print.print("출력해주세요.");
    }
}
  • 인터페이스로 클래스를 구현한 후 메소드의 추가가 필요할 때 디폴트 메소드를 통해 추가시킬 수 있다.
interface MyInterface {
  default void printHello() {
    System.out.println("Hello World");
  }
}

class MyClass implements MyInterface { /* empty */ }

public class DefaultMethod {
  public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.printHello();//실행결과 Hello World 출력
   }
}

1) 다중상속

  • 자바에서는 다중 상속이 지원되지 않는다

    • 프로그램의 복잡도가 높아지고 문법적으로 애매모호해진다
  • 인터페이스를 이용하면 다중상속의 효과를 낼 수 있다.

interface Drivable{
    void drive();
}

interface Flyable{
    void fly();
}

class FlyingCar implements Drivable, Flyable{
    @Override
    public void drive() {
        System.out.println("드라이브가 가능합니다.");
    }

    @Override
    public void fly() {
        System.out.println("날 수 있습니다.");
    }
}

public class Drive {
    public static void main(String[] args) {
        FlyingCar flyingCar = new FlyingCar();
        flyingCar.drive();
        flyingCar.fly();
    }
}

0개의 댓글