[14일차]다형성,형변환,instanceof,추상화,abstract,final,interface

유태형·2022년 5월 13일
0

코드스테이츠

목록 보기
15/77

오늘의 목표

  1. 다형성
  2. 타입 변환(캐스팅)
  3. instance of
  4. 다형성 활용
  5. 추상화
  6. abstract
  7. 추상 클래스
  8. final
  9. 인터페이스
  10. 인터페이스 활용



내용

다형성

다형성(polymorphism) : 여러개를 의미하는 poly와 형태를 의미하는 morphism을 결합하여 여러가지 형태라는 의미를 가집니다.

자바에서 다혀성은 상위클래스 참조_변수하위클래스 객체를 가리킴으로써 좀더 유연하게 형변환을 할 수 있습니다. 이것을 업 캐스팅이라고도 부르기도 합니다. 반대로 하위클래스 참조_변수상위클래스 객체를 가리킬 순 없습니다. 왜냐하면 하위클래스는 상위클래스로부터 상속도 받지만 자기자신의 멤버도 가지기 때문에 상위 클래스에서는 하위 클래스에서 선언된 멤버들을 가리킬 수 없기 때문입니다.

package JAVA_OOP_Extend;

class Friend{ //상위클래스
    public void friendInfo(){
        System.out.println("나는 당신의 친구입니다.");
    }
}

class BoyFriend extends Friend{ //하위클래스
    @Override
    public void friendInfo() {
        System.out.println("나는 당신의 남자친구입니다.");
    }
}

class GirlFriend extends Friend{ //하위클래스
    @Override
    public void friendInfo() {
        System.out.println("나는 당신의 여자친구입니다.");
    }
}

public class FriendTest {
    public static void main(String[] args) {
        Friend friend = new Friend(); //상위클래스 참조_변수 = 상위클래스 객체
        BoyFriend boyfriend = new BoyFriend(); //하위클래스 참조_변수 = 하위클래스 객체
        Friend girlfriend = new GirlFriend(); //상위클래스 참조_변수 = 하위클래스 객체

        friend.friendInfo();
        boyfriend.friendInfo();
        girlfriend.friendInfo(); //친구? 여자친구?
    }
}

//Console
나는 당신의 친구입니다.
나는 당신의 남자친구입니다.
나는 당신의 여자친구입니다. //동적 바인딩

girlfriend.friendInfo()를 호출 하였을 때 상위클래스의 friendInfo()가 아닌 하위클래스에서 오버라이딩 된 friendInfo()가 출력 되는것을 확인 할 수 있습니다. 이것을 도적 바인딩이라고 합니다. 반대로는 정적 바인딩이 존재합니다.

  • 정적 바인딩 : 컴파일 또는 링크 할때 확정, 오버라이딩 하더라도 상위 클래스의 메서드를 실행
  • 동적 바인딩 : 실행 이후에 확정, 오버라이딩 하면 하위 클래스의 메서드를 실행



타입 변환(캐스팅)

기본 자료형 변수와 마찬가지로 참조 변수도 타입변환이 가능합니다. 참조 변수의 타입변환은 사용할 수 있는 멤버의 개수를 조절하는 것 입니다.

타입변환은 4가지 조건을 만족해야 합니다.

  1. 상속 관계에 있는 하위 클래스 - 상위 클래스 사이에 형변환이 가능 합니다.
  2. 하위 클래스타입에서 상위 클래스 타입으로의 형변환은 괄호()의 생략이 가능합니다.
  3. 상위 클래스타입에서 하위 클래스 타입으로의 형변환은 괄호(타입)을 명시해야 합니다.
  4. 하위클래스 참조_변수 = 상위클래스 객체 와 같이 하위클래스로 상위클래스를 가리킬 수는 없습니다.
package JAVA_OOP_Extend;

class Vehicle2{ //상위 클래스
    String model;

    void startEngine() {
        System.out.println("시동 걸기");
    }
}

class Car2 extends Vehicle2{ //하위 클래스
    void giveRide() {
        System.out.println("다른 사람 태우기");
    }
}

class MotorBike2 extends Vehicle2{ //하위 클래스
    void performance() {
        System.out.println("묘기 부리기");
    }
}

public class VehicleTest {
    public static void main(String[] args) {
        Car2 car = new Car2(); //하위클래스 참조변수 = 하위클래스 객체
        Vehicle2 vehicle = car; //상위클래스로 형변환 ()는 생략 가능, 업캐스팅
        Car2 car2 = (Car2)vehicle; //하위클래스로 형변환 (타입)명시 필수, 다운캐스팅
//        MotorBike2 motorBike = (MotorBike2) car;
// motorBike와 car는 상속관계가 아니므로 형변환 불가능
//        MotorBike2 motorBike = (MotorBike2) vehicle; 
// 다른 하위클래스에서 상위클래스로 업캐스팅된 클래스를 또 다른 하위클래스로 다운캐스팅 불가능

        car.startEngine(); //하위 클래스 참조 변수에서 호출
        car.giveRide(); //하위 클래스 참조 변수에서 호출
//        vehicle.giveRide(); 
// (업캐스팅 된)상위 클래스 참조 변수에서 하위 클래스 메서드 호출 불가능
    }
}

//Console
시동 걸기
다른 사람 태우기

상위 - 하위 클래스 간에 형 변환시 제약 사항도 몇가지 존재합니다.

  1. 둘다 한 상위클래스를 상속받은 하위 클래스여도 형제간에는 형변환이 불가능 합니다.
  2. 다른 하위클래스에서 상위클래스로 업캐스팅된 클래스는 또 다른 하위클래스로 다운캐스팅이 불가능 합니다.
  3. 하위 클래스 -> 상위 클래스로 업 캐스팅 된 객체는 하위클래스의 멤버를 호출할 수 없습니다.(참조 변수 따라감)



instance of

큰 대규모 프로젝트에서 여러사람이 만든 클래스를 캐스팅이 가능한지, 상속관계인지, 해당 클래스인지 확인하기 위해 일일이 찾아보는 것은 시간도 많이 걸릴 뿐더러 에러도 클 것입니다. 이를 해결하기 위해 간단히 알려주는 연산자가 존재합니다.

참조_변수 instanceof 클래스타입

  • return true : 객체의 타입이 해당 클래스 타입의 상속관계 이거나 해당 클래스인 경우 반환합니다.
  • return false : 객체의 타입이 해당 클래스 타입의 상속관계가 아닌 경우,해당 클래스가 아닌경우,null인 경우 반환합니다.
package JAVA_OOP_Extend;

class Animal{}
class Bat extends Animal{}
class Cat extends Animal{}

public class InstanceOfExample {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal instanceof Object); //상위 클래스
        System.out.println(animal instanceof Animal); //해당 클래스
        System.out.println(animal instanceof Bat); //하위 클래스

        Animal cat = new Cat(); //업 캐스팅
        System.out.println(cat instanceof Object); //상위 클래스
        System.out.println(cat instanceof Animal); //상위 클래스
        System.out.println(cat instanceof Cat); //해당 클래스
        System.out.println(cat instanceof Bat); //공통된 상위클래스를 둔 또 다른 하위 클래스
    }
}

//Console
//animal
true
true
false

//cat
true
true
true
false

instanceof 연산시 true가 나오면 해당 클래스로 형 변환이 가능합니다.(해당 클래스이거나, 상위클래스)




다형성 활용

단순히 형변환의 기능과 1~2개의 클래스를 형변환 하는것에 그치는 것이 아닌 실제로 형변환을 수행함으로써 많은 시간과 코드를 절약할 수 있는 방법도 여러가지가 존재합니다.

예를 들어

반환타입 메서드1(하위클래스1 하위객체1){
	멤버 = 멤버 (연산자) 하위객체1.멤버;
}

반환타입 메서드2(하위클래스2 하위객체2){
	멤버 = 멤버 (연산자) 하위객체2.멤버;
}

//3,4,....

하위 객체의 종류가 여러가지라면 해당 하위 객체의 종류 수 만큼 메서드를 만들어야 하는 번걸움이 생깁니다. 모든 하위 객체를 상위 객체 하나로 형변환(업캐스팅) 하면 종류가 수 만개가 되더라도 하나만 작성하면 됩니다.

반환타입 메서드(상위클래스 상위객체){
	멤버 = 멤버(연산자) 상위객체.멤버;
}

하위클래스1, 하위클래스2, 하위클래스3, ...를 인자로 넘기면 자동으로 업 캐스팅 된 상위클래스 참조_변수가 하위 클래스 객체를 가리킴으로 가능합니다.

package JAVA_OOP_Extend;

class Coffee{ //상위 클래스
    int price;

    public Coffee(int price){
        this.price = price; //가격
    }
}

class Americano extends Coffee{ //하위 클래스
    public Americano(){
        super(4000); //가격은 4000원
    }

    public String toString(){return "아메리카노";} //Object.toString() 오버라이딩
}

class CaffeLatte extends Coffee{ //하위 클래스
    public CaffeLatte(){
        super(5000); //가격은 5000원
    }

    public String toString() {return "카페라떼";}; //Object.toString() 오버라이딩
}

class Customer{
    int money = 50000; //초기 잔액

    void buyCoffee(Coffee coffee){ //아메리카노가 인자로 주어지든 카페라떼가 인자로 주어지든
        //Coffee 타입으로 자동으로 형변환(업캐스팅)
        if(money < coffee.price){ //남은 돈 < 커피 가격
            System.out.println("잔액이 부족합니다.");
            return;
        }
        money = money - coffee.price; //커피 가격을 뺌
        //상위 클래스에 price멤버가 존재하므로 하위클래스들은 price멤버에 접근 가능합니다
        System.out.println(coffee + "를 구입했습니다.");
    }
}

public class PolymorphismEx {
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.buyCoffee(new Americano()); //생성한 객체를 인자로 넘깁니다.
        customer.buyCoffee(new CaffeLatte()); //생성한 객체를 인자로 넘깁니다.

        System.out.println("현재 잔액은 " + customer.money + "원 입니다.");
    }0
}

//Console

아메리카노를 구입했습니다. //50000 - 4000 = 46000
카페라떼를 구입했습니다. //46000 - 5000 = 41000
현재 잔액은 41000원 입니다.

Coffee라는 상위 클래스에 price멤버를 선언 하였습니다. 하위 클래스 AmericanoCaffeLatteCoffee로 상속받아 price멤버도 상속 받아 별 다른 선언 없이 사용가능합니다. 따라서 매개변수로 업캐스팅 되었을 때 바로 사용이 가능합니다.




추상화

상속을 통하여 상위 클래스에서 하위클래스로 더욱 구체적이고 세부적으로 다양화 시키는 것을 일반화라고 합니다. 이와 반대로 하위 클래스들에서 공통성, 본직에 착안하여 추출하는 것을 추상화라고 합니다. 추상화가 제대로 되었다면 업캐스팅 시 다형성을 활용하는데 큰 도움이 될 것입니다.




abstract

자바에서는 접근 제어자기타 제어자가 존재하고 기타 제어자에서는 abstract가 가장 빈번하게 사용 됩니다.
abstract는 사전적인 의미로는 추상적인을 가지고 있고 자바에서는 미완성이라는 맥락을 가집니다.
abstract를 클래스 앞에 붙히면 추상 클래스(abstract class), 메서드 앞에 붙히면 추상 메서드(abstract method)가 됩니다.

예시

abstract class 추상클래스{ //추상 메서드가 하나라도 존재하면 추상 클래스로 명시해야 합니다.
	abstract 반환타입 메서드(매개변수); //추상 메서드는 메서드 바디가 없습니다.
}

추상 메서드는 abstract 키워드를 앞에 명시해야만 하며 메서드 바디 없이 메서드 시그니처만 존재합니다. 그리고 추상 메서드르 하나라도 가진 클래스는 반드시 abstract키워드를 class앞에 명시해야 합니다.

또 추상 클래스는 미완성인 메서드(추상메서드)를 가지고 있기 때문에 인스턴스화 시킬 수 없습니다.

추상클래스 참조_변수 = new 추상클래스(); //에러 발생




추상 클래스

abstract 제어자로 메서드 바디는 없고 메서드 시그니처만 있는 추상 메서드를 만들었고, 추상 메서드가 하나라도 존재하면 abstract제어자로 추상 클래스로 만들어야 했습니다. 또 추상클래스는 인스턴스화 하지 못합니다.

하지만 상위 클래스를 추상 클래스로 하위 클래스에서 구현해야할 메서드들의 뼈대를 만들고 하위클래스에서 메서드 오버라이딩을 통해 구체적으로 구현한다면 좀 더 높은 다형성을 구현할 수 있을 것입니다.

package JAVA_OOP_Extend;

abstract class Animal2{ //추상 클래스
    public String kind;
    public abstract void sound(); //추상 메서드
}

class Dog2 extends Animal2{ //하위 클래스
    public Dog2(){
        this.kind = "포유류";
    }

    public void sound(){ //추상 메서드 오버라이딩
        System.out.println("멍멍");
    }
}

class Cat2 extends Animal2{ //하위 클래스
    public Cat2(){
        this.kind = "포유류";
    }

    @Override
    public void sound() { //추상 메서드 오버라이딩
        System.out.println("야옹");
    }
}

abstract class Bat2 extends Animal2{ //추상 클래스
    //추상 클래스를 상속 받은 클래스는 추상메서드를 모두 구현하지 않으면 추상 클래스 입니다.
}

public class DogExample {
    public static void main(String[] args) {
        Animal2 dog = new Dog2(); //업캐스팅
        dog.sound(); //동적 바인딩
	//Bat2 bat = new Bat2(); //추상클래스는 인스턴스화x
        Cat2 cat = new Cat2();
        cat.sound();
    }
}

//Console
멍멍
야옹

Animal2에서 abstract 제어자로 sound()를 추상 메서드로 구현하여 추상 클래스로 선언하였습니다. Animal2 추상 클래스를 상속한 Dog2Cat2sound()추상 메서드를 오버라이딩으로 구현하여 모든 추상메서드를 오버라이딩 함으로써 인스턴스화 할 수 있습니다.
반면에 Animal2Bat2는 모든 추상 메서드를 고현하지 않았음으로 인스턴스화 시키지 못합니다.




final

final 키워드는 선언 위치(클래스, 메서드, 변수)등에 따라 사용 되는 의미가 조금씩 다르지만 해당 대상은 더 이상 변경이 불가능하거나 확정되지 않는 성질을 지니게 됩니다.(상수)

  • 클래스 : 변경,확장,상속 불가능
  • 메서드 : 오버라이딩 불가능
  • 변수 : 변경 불가능한 상수
final class 클래스{ //변경,확장, 상속이 불가능한 클래스
	final int x = 1; //변경되지 않는 상수

	final void getNum(){ //오버라이딩 불가능한 메서드
		final in localVar = x; //변경되지 않는 상수
		return x;
	}
}



인터페이스

인터페이스 선언

인터페이스와 추상 클래스는 추상화를 구현한다는 점에서 비슷한 역할을 수행합니다. 하지만 인터페이스는 보다 더 추상화 경향이 강합니다. 추상클래스는 멤버변수, 상수, 메서드, 추상메서드 등 모든 요소들을 포함할 수 잇지만 인터페이스는 상수와 추상메서드로만 구성될 수 있습니다.

또 인터페이스의 필드는 모두 public static final로 정의되고 메서드는 모두 public abstract로 정의 됩니다.

public interface interfaceEx{
	public static final int rock = 1; //인터페이스 필드 상수
    final int scissors = 2; //public static 생략
    static int paper = 3; //public final 생략
    int none = 0; //public static final 생략
    
    public abstract String getPlayingNum(); //추상 메서드
    abstract double getAvg(); //public 생략
    void call(); //public abstract 생략

인터페이스 안에서 상수 선언은 반드시 public static final로, 메서드는 public abstract로 정의 되어야만 하고 일부 또는 전부 생략이 가능합니다.



인터페이스 구현

어떤 클래스가 인터페이스를 구현하였다는 것은 인터페이스의 모든 추상 메서드를 오버라이딩하여 구현하였다는 것을 의미합니다. 인터페이스를 구현할 때는 implements 키워드를 사용합니다.

class 클래스 implements 인터페이스 {...}

클래스 간 상속은 하나만 가능합니다.(자바는 다중상속을 허용하지 않습니다.), 그러나 인터페이스는 여러개를 받아 와서 구현이 가느합니다. 1개의 클래스에 여러개의 인터페이스를 구현할 수 있습니다.

class 클래스 implements 인터페이스1, 인터페이스2, ..., 인터페이스n {...}

클래스는 상위 클래스로 상속받으면서 동시에 인터페이스도 구현이 가능합니다. 즉 클래스 상속과 인터페이스 구현은 둘 중 하나를 선택하는 것이 아니라 둘 다 가능합니다.(단 클래스는 다중 상속 안됩니다.)

class 클래스 extends 상위 클래스 implements 인터페이스1, 인터페이스2, ...{...}

클래스 간 다중 상속이 불가능 한 이유는 상위 클래스 2개이상에서 이름이 동일한 필드나 메서드가 존재할 경우 충돌이 발생하기 때문에 불가능합니다. 하지만 상수,추상메서드만 가지고 있는 인터페이스는 충돌할 이유가 없기 때문에 다중 구현이 가능합니다.




인터페이스 활용

상위클래스 참조_변수 = 하위클래스 객체로 업 캐스팅 형변환 때와 마찬가지로 인터페이스도 유사하게 형변환이 가능합니다.

반환타입 메서드1(클래스1 객체1){
	멤버 = 멤버 (연산자) 객체1.멤버;
}

반환타입 메서드2(클래스2 객체2) {
	멤버 = 멤버 (연산자) 객체2.멤버;
}

//3,4,,,,,100

인자로 받아들이는 개체의 타입마다 오버로딩 하면 타입의 종류가 늘어날 때마다 추가해야하는 번거로움이 존재합니다. 클래스가 100개가 존재한다면 100개의 클래스마다 메서드를 정의해야만 합니다.

public interface 인터페이스 {
	//상수
    추상 메서드
}
class 클래스1 implements 인터페이스{
	//필드
    //메서드
}
class 클래스2 implements 인터페이스{
	//필드
    //메서드
}
//3,4,,,,,100

하지만 인터페이스로 다형성을 실현할 수 있습니다.

반환타입 메서드(인터페이스 객체){
	멤버 = 멤버 (연산자) 객체.멤버;

인터페이스를 상속받는 클래스 100개를 모두 인터페이스로 형변환 하면 인터페이스를 구현하는 클래스들을 하나의 메서드로 일괄적으로 처리가 가능합니다.




후기

추상클래스, 인터페이스는 그자체로 매우중요하다기 보단 나중에 스프링을 학습할때 매우 중요하다는 것을 수업 도중에 들었습니다. 추상 클래스와 인터페이스를 다형성이 강하도록 활용하는 것에 익숙해 져야 겠습니다.




GitHub

https://github.com/ds02168/CodeStates/tree/main/src/JAVA_OOP_Extend

profile
오늘도 내일도 화이팅!

0개의 댓글