본문 바로가기

항해 99/Java

객체 지향, 상속, 다형성

항해 99 : TIL - 3

학습 참조 : 강의 - Java 문법 종합반 3주차

 

학습 내용 정리

객체지향 프로그래밍

객체(인스턴스)들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법

  • 모든 사물은 속성과 기능을 가진 객체임, 프로그래밍에서 객체는 클래스(설계도)에 의해 메모리에 생성된 것을 의미함.

특징

  • 캡슐화 
    • 속성과 기능을 하나로 묶어서 필요한 기능을 메서드로 외부에 제공하는 것 (외부에서 사용하지 않는 데이터는 숨긴다)
  • 상속
    • 기존 클래스의 속성과 기능을 새로운 클래스에서 재사용할 수 있도록 물려 받는 것.
    • extends 키워드로 상속 가능 (다중 상속은 불가능)
    • 부모 클래스 : 상속을 통해 자신의 필드 및 메서드를 다른 클래스로 제공
    • 자식 클래스 : 부모 클래스로부터 필드와 메서드를 상속받는 클래스
  • 다형성
    • 한 객체가 여러 타입의 객체로 취급될 수 있는 능력
    • 다형적 참조 : 부모 타입의 변수가 자식의 인스턴스를 참조하는 것(부모는 자식을 담을 수 있음). 단 자식 타입의 메서드 호출은 불가능 (다운 캐스팅을 통해 자식의 메서드 호출 가능)
      • 다운 캐스팅은 다형적 참조로 부모 타입의 변수를 자식의 생성자로 생성한 경우에만 가능
    • instanceof : 다형성에서 참조형 변수는 다양한 자식을 대상으로 참조할 수 있어 참조하는 대상이 어떤 인스턴스를 참조하는지 확인하기 위해 사용
    • overriding : 부모 타입의 메서드를 자식 타입의 재정의해서 사용
  • 추상화
    • 객체의 공통 부분을 상위 개념으로 새롭게 선언하는 것
    • 추상 클래스 : 부모 클래스는 제공하지만, 생생할 수 없는 클래스로 상속을 목적으로만 사용(인스턴스 존재 x)
    • 추상 메서드 : 실체가 존재하지 않고 메서드 바디가 없는 메서드로 부모 클래스를 상속받는 자식 클래스는 반드시 추상 메서드를 오버라이딩해야 함.

 

클래스

객체(인스턴스)를 만들기(생성) 위한 설계도

클래스는 멤버 변수, 필드, 메서드로 구성

  • 멤버 변수, 필드 : 클래스에 정의 한 변수들
  • 클래스 메서드 : 클래스에 정의된 메서드
  • 생성자 : 클래스 인스턴스틀 생성하고 인스턴스 변수를 원하는 값으로 초기화 할 수 있는 메서드, 생성자를 정의하지 않으면 자바에서 기본 생성자를 제공(기본 생성자는 생략 가능, 생성자도 오버로드해서 사용할 수 있음)

클래스 선언 → 클래스 필드(멤버 변수) 정의 → 클래스 생성자 정의(기본 생성자 생략 가능) → 클래스 메서드 정의

package week03;

// 클래스 정의
public class Car {
    // <클래스 필드 영역>
    // 필드(멤버 변수) 정의
    String company; // 기본 값으로 초기화
    String model;
    String color;
    double price;
    double speed;
    char gear;
    boolean lights;

    //<생성자 정의> - 기본 생성자는 생략 가능
    public Car() { // ( ) 안에 객체 생성 시 필요한 값 입력 가능
        // 객체 생성 시 수행할 로직
    }

    //<메서드 영역> - void를 제외한 메서드는 return으로 반환 타입을 돌려줘야 함
    double gasPedal(double kmh) { // double -> gasPedal의 반환 타입
        speed = kmh;
        return speed;
    }

    double breakPedal() {
        speed = 0;
        return speed;
    }

    char changeGear(char type) {
        gear = type;
        return gear;
    }

    boolean onOffLight() {
        lights = !lights;
        return lights;
    }

    void horn() {
        System.out.println("빵빵");
    }
}

 

 

객체 생성

클래스를 기반으로 new 키워드를 사용해서 인스턴스 생성 가능

// 인스턴스 생성
//클래스명 변수이름 = new 클래스명(); 형태로 생성
Car car1 = new Car();

// 인스턴스 배열 생성
Car[] carArray = new Car[3]; // 배열 생성
carArray[0] = car1; // 배열에 인스턴스 저장
  • 인스턴스 생성 시 해당 인스턴스의 주소(참조값)가 반환 됨, 해당 클래스의 참조형 변수로 저장할 수 있음
  • 인스턴스 배열을 생성해서 인스턴스를 저장하여 관리하는 것도 가능

 

필드

클래스와 객체의 데이터를 저장하는 역할

public class Car {
    // <클래스 필드 영역>
    // 필드(멤버 변수) 정의
    String company; // 값을 지정하지 않으면 초기 값으로 초기화 예) int 타입은 초기 값이 0
    String model;
    String color;
    double price;
    double speed;
    char gear;
    boolean lights;
    
    // 필드 값 내부 접근
    double breakPedal() {
        speed = 0;
        return speed;
    }
}

 

필드 사용 : 필드의 값을 변경하거나 읽는 것(인스턴스 생성 후 필드 사용 가능)

package week03;

public class Main {
    public static void main(String[] args) {
        //객체 생성
        Car car = new Car();

        // 초기값 확인
        System.out.println("car.model = " + car.model);
        System.out.println("car.color = " + car.color);
        System.out.println();

        System.out.println("car.speed = " + car.speed);
        System.out.println("car.gear = " + car.gear);
        System.out.println("car.lights = " + car.lights);
        System.out.println();

        System.out.println("car.tire = " + car.tire);
        System.out.println("car.door = " + car.door);
        System.out.println();

        // 필드값 외부 접근 .(dot) 사용해서 접근
        car.color = "blue";
        car.speed = 100;
        car.lights = false;

        System.out.println("car.color = " + car.color);
        System.out.println("car.speed = " + car.speed);
        System.out.println("car.lights = " + car.lights);
    }
}

 

메서드

특정 기능을 수행하기 위한 코드 집합체(함수), 코드의 중복을 줄이고 한 번 정의 시 재사용 가능.

 

클래스 메서드와 인스턴스 메서드

  • static 키워드를 가지는 메서드를 클래스 메서드, static 키워드를 가지지 않는 메서드는 인스턴스 메서드
  • 클래스 메서드는 객체 생성 없이 사용 가능, 인스턴스 변수는 사용 못 함

메서드 선언 : 메서드를 사용하기 위해 정의하는 것

// 클래스 메서드 선언
static void classMethod() {
	System.out.println("클래스 메서드");
}

// 인스턴스 메서드 선언
double gasPedal(double kmh, char type) {
    speed = kmh;
    gear = type;
    return speed;
}

 

메서드 호출 : 메서드를 사용하기 위해 부르는 것

public class Main {
    public static void main(String[] args) {
      // 메서드 호출
      // 클래스 메서드
      System.out.println(Car.classMethod)
      
      // 인스턴스 메서드
      Car car = new Car();
      double speed = car.gasPedal(100, 'D'); // 반환 타입이 있는 메서드는 반환 값을 저장할 수 있음
}

 

가변길이 매개변수

메서드 선언 시 (반환 타입 ... 파라미터) 형식으로  가변길이의 매개변수로 선언 가능.

// 가변길이 매개변수 메서드 선언
void carSpeed(double ... speeds) {
    for (double v : speeds) {
        System.out.println("v = " + v);
    }
}

// 메서드 호출
carSpeed(100, 80);
carSpeed(110, 120, 150);

 

오버로딩 : 한 메서드 이름으로 여러 기능을 구현할 수 있도록 하는 기능

조건

  • 메서드 이름은 같고, 매개변수의 수, 타입, 순서가 달라야 함
  • 접근 제어자만 다르면 안 됨(public, default, protected, private 등)
  • 반환 타입만 다른 경우도 안 됨

메서드 오버로딩은 매개변수의 차이로만 구분할 수 있음

package method;

public class OverLoading1 {
    public static void main(String[] args) {
        System.out.println("1: " + add(1, 2));
        System.out.println("2: " + add(1, 2, 3));
    }

    public static int add(int a, int b) {
        System.out.println("1번 호출");
        return a + b;
    }

    public static int add(int a, int b, int c) {
        System.out.println("2번 호출");
        return a + b + c;
    }
}

 

 

매개변수 종류

자바는 변수의 값을 복사해서 대입하기 때문에 원본의 값이 변경되지 않는다.

  • 기본형 : 전달할 매개값으로 지정한 값을 메서드 매개변수에 복사해 전달
  • 참조형 : 전달할 매개값으로 지정한 값의 주소(참조값)를 매개변수에 복사해서 전달

 

멤버

필드 + 메서드를 멤버라고 함

  • 인스턴스 멤버 : 인스턴스 필드 + 인스턴스 메서드
  • 클래스 멤버 : 클래스 필드 + 클래스 메서드 (static 키워드를 사용해서 선언)

인스턴스 멤버는 인스턴스(객체)를 생성해야 사용할 수 있고, 클래스 멤버는 인스턴스 없이도 사용할 수 있음

//클래스 필드와 메서드 생성
static String company = "GENESIS"; // 자동차 회사 : GENESIS

static String setCompany(String companyName) {
    // System.out.println("자동차 모델 확인: " + model); // 인스턴스 필드 사용 불가
    company = companyName;
    return company;
}
package week03.staticFolder;

public class Main {
    public static void main(String[] args) {
        // 클래스 필드 company 확인
        System.out.println(Car.company + "\n");
        // 클래스 필드 변경 및 확인
        Car.company = "Audi";
        System.out.println(Car.company + "\n");

        // 클래스 메서드 호출
        String companyName = Car.setCompany("Benz");
        System.out.println("companyName = " + companyName);

        System.out.println();
        // 참조형 변수 사용 - 인스턴스 멤버 사용
        Car car = new Car(); // 객체 생성

        car.company = "Ferrari"; // 인스턴스 필드 사용
        System.out.println(car.company + "\n");

        String companyName2 = car.setCompany("Lamborghini"); // 인스턴스 메서드 사용
        System.out.println("companyName2 = " + companyName2);
    }
}

 

 

지역변수, 상수

지역변수 : 메서드 내에서 선언된 변수로 메서드가 종료되면 메모리에서 삭제되는 변수(메서드 외에 특정 블록 내에서 선언 된 후 특정 블록 종료 시에도 사라진다).

상수 : final 키워드를 사용해 한 번 초기화 후 값을 변경할 수 없음(대문자로 표기).

package week03.sample;

public class Main {
    public static void main(String[] args) {
        Main main = new Main(); // 기본 생성자
        System.out.println(main.getNumber());
        System.out.println(main.getNumber());
        
        //상수
        final int number = 1; // 값 변경 불가
        //number = 2; // 컴파일 오류 발생
    }

    // 메서드
    public int getNumber() {
        int number = 1; // 지역변수
        number += 1;
        return number;
    }
}

 

 

생성자

클래스로부터 객체(인스턴스)를 생성할 때 사용(기본 생성자는 생략 가능, new 키워드를 사용해서 생성자 호출)

public Car() {} // 선언 - private를 사용하면 생성자를 생성할 수 없도록 설정 가능

Car car = new Car() // 호출

 

this : 클래스의 필드에 접근할 수 있다(자바는 기본적으로 변수 이름이 같을 경우 가까운 곳의 변수로 인식)

this( ) : 생성자 내부에서 자신의 생성자를 호출할 수 있음(생성자 코드 첫줄에만 작성 가능), 생성자 오버로딩과 같이 사용

public class Car {
    String model;
    String color;
    double price;
    
    
//   	public Car(String model, String color, double price) {
//        model = model; // this. 가 없을 경우 매개 변수 자신에 값을 대입하게 됨
//        color = color;
//        price = price;
//    }

    public Car(String model, String color, double price) {
        this.model = model; // 클래스의 필드 매개변수에 접근
        this.color = color;
        this.price = price;
    }
    
    //this() 사용
    public Car(String model) {
        this(model, "Blue", 500000000);
    }
    
    public Car(String model, String color) {
        this(model, color, 500000000);
    }
}

 

 

접근 제어자

클래스와 패키지의 외부에서 접근을 허용하거나 제한하는 기능을 제공(필드, 메서드, 생성자에서 사용되며, 클래스 레벨에도 일부 접근 제어자 사용 가능)

종류

  • private : 클래스 외부에서의 접근을 제한
  • default : 같은 패키지 안에서 호출은 허용, 클래스 레벨에도 사용 가능
  • protected : 같은 패키지와 상속 관계의 호출은 허용
  • public : 모든 외부 호출을 허용, 클래스 레벨에도 사용 가능

Getter / Setter

private 접근 제어자를 사용한 필드 값을 읽고, 수정 및 저장할 때 사용하며 private 접근 제어자를 사용한 필드 값이 있는 클래스 내부에서 메서드를 선언하고 외부에서 호출해서 사용(public 메서드가 클래스 내부에 있기 때문에 private 필드에 접근 가능)

//Getter : get + 필드이름(첫 글자 대문자)
// private 필드 값을 읽거나 가져올 때 사용
public String getModel() {
    return model;
}

//Setter : set + 필드이름(첫 글자 대문자)
// priavte 필드 값을 수정하거나 저장할 때 사용
public void setModel(String model) {
    this.model = model;
}

 

 

import / package

package : 컴퓨터에서 파일을 분류하기 위해 폴더 및 디렉토리 개념을 제공하는 자바에서는 패키지 형태로 기능을 제공

  • 같은 이름의 클래스가 패키지 마다 있을 경우 패키지 경로를 통해 구분

import : 다른 패키지에 있는 클래스를 가져와서 사용할 수 있게 하는 기능(패키지 전체 경로를 매번 입력하는 것이 불편하기 때문에 import를 통해 패키지 경로명을 생략하고 사용 가능).

  • 특정 패키지에 포함된 모든 클래스를 불러 올 때는 package 이름 뒤에 *을 사용
package pack;

import pack.a.*; // pack.a 패키지에서 모든 클래스를 import
//import pack.a.User; // pack.a 패키지에서 User 클래스만 import

public class PackageMain2 {
    public static void main(String[] args) {
        Data data = new Data();
        User user = new User(); // import 사용으로 패키지 명 생략 가능
        User2 user2 = new User2();
        //import 사용 안 할 경우
        //pack.a.User user = new pack.a.User(); // 패키지 전체 경로를 입력해야 함
    }
}

 

 

상속

기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용할 수 있게 하는 기능(extends 키워드로 사용)

// 부모 클래스
package extends1.ex2;

public class Car {

    public void move() {
        System.out.println("차를 이동합니다.");
    }
}

// 부모 클래스를 상속받는 자식 클래스
package extends1.ex2;

public class ElectricCar extends Car {

    public void charge() {
        System.out.println("충전합니다.");
    }
}

 

  • 상속 대상(부모 클래스)은 하나만 선택할 수 있음(다중 상속 불가)
  • 한 부모 클래스에 여러 자식 클래스가 존재할 수 있음
  • 자식 클래스에서 부모 클래스의 메서드를 사용할 수 있음(자식 클래스에 호출하는 메서드가 없으면 부모 클래스로 올라가서 메서드를 찾는다, 못 찾을 경우 컴파일 오류 발생)
  • 부모 클래스는 자식 클래스의 메서드를 사용할 수 없다

 

포함관계

클래스의 멤버로 다른 클래스 타입의 참조 변수를 선언하는 것(클래스의 속성 값에 다른 클래스를 선언해서 사용하는 것)

//포함 관계 예시
//Tire 클래스
public class Tire {
    String company; // 타이어 회사
    double price; // 타이어 가격

    public Tire(String company, double price) {
        this.company = company;
        this.price = price;
    }
}

//Car 클래스
public class Car {

    static final String company = "GENESIS"; // 자동차 회사
    String model; // 자동차 모델
    String color; // 자동차 색상
    double price; // 자동차 가격

    Tire[] tire;
    
     public Car(String model, String color, double price) {
        this.model = model;
        this.color = color;
        this.price = price;
    }

    public void setTire(Tire ... tire) {
        this.tire = tire;
    }
}

 

 

final 클래스와 final 메서드 : 클래스와 메서드에 final 키워드를 사용하면 상속할 수 없는 클래스가 되고, 메서드는 오버라이딩 할 수 없음.

 

최상위 클래스(Object)

모든 클래스들의 최상위 부모 클래스, 모든 클래스는 Object의 메서드를 사용할 수 있음

기능

  • Object clone( ) : 객체의 복제본 생성하여 반환
  • boolean equals(Object object) : 객체와 전달 받은 객체가 같은지 여부를 반환
  • Class getClass( ) : 객체의 클래스 타입을 반환
  • String toString( ) : 객체의 정보를 문자열로 반환
  • ...

 

overriding

부모에게 상속 받은 기능을 자식이 재정의 하는 것(메서드 오버라이딩)

// 부모 클래스
package extends1.ex3;

public class Car {

    public void move() {
        System.out.println("차를 이동합니다.");
    }

    // 추가
    public void openDoor() {
        System.out.println("문을 엽니다.");
    }
}
// 자식 클래스
package extends1.overriding;

public class ElectricCar extends Car {

    // 메서드 오버라이딩
    @Override
    public void move() {
        System.out.println("전기차를 빠르게 이동합니다.");
    }

    public void charge() {
        System.out.println("충전합니다.");
    }
}

 

오버라이딩 조건

  • 선언부가 부모클래스 메서드와 일치
  • 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경 불가
  • 예외는 부모 클래스의 메서드 보다 많이 선언 불가

super

부모 클래스의 멤버를 참조할 수 있는 키워드

// 부모 클래스
package extends1.super1;

public class Parent {

    public String value = "parent";

    public void hello() {
        System.out.println("Parent.hello");
    }
}

// 자식 클래스
package extends1.super1;

public class Child extends Parent {

    public String value = "child";

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

    public void call() {
        System.out.println("this value = " + this.value); // this. 생략 가능
        System.out.println("this value = " + super.value);

        this.hello(); // this. 생략 가능
        super.hello(); // super 사용으로 부모 클래스 참조
    }
}

 

super( ... ) : 부모 클래스의 생성자를 호출

  • 상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다(규칙)
    • 부모 클래스의 생성자가 기본 생성자일 경우 super( ... ) 생략 가능
    • 예외로 생성자 첫줄에 this(...) 사용 가능, super( ... )는 자식의 생성자 안에서 언젠가 반드시 호출 필요
// 부모 클래스 Car 생성자
public Car(String model, String color, double price) {
    this.model = model;
    this.color = color;
    this.price = price;
}

// 자식 클래스 SportsCar 생성자
public SportsCar(String model, String color, double price, String engine) {
     // this.engine = engine; // 오류 발생
    super(model, color, price);
    this.engine = engine;
}

 

 

다형성

한 객체가 여러 타입의 객체로 취급될 수 있는 능력

핵심 이론

  • 다형적 참조 : 부모 타입의 변수는 자식 타입을 참조할 수 있음(부모 타입은 자신을 기준으로 모든 자식 타입을 참조할 수 있음).
    • 다형적 참조를 사용해 자식 타입을 참조해도 자식 타입의 메서드를 호출할 수 없음
    • 다운 캐스팅(자식 타입으로 형변환)을 통해 자식 메서드의 기능을 사용할 수 있음(다형적 참조를 사용한 경우에만 다운 캐스팅 가능).
  • 메서드 오버라이딩 : 부모에게 상속받은 메서드를 자식이 새로 정의하는 것.

instanceof : 다형적 참조를 사용할 때 참조하는 대상이 어떤 클래스의 인스턴스를 참조하는지 확인하기 위해 사용

// 다형적 참조/ 다운 캐스팅
package poly.basic;

public class CastingMain2 {
    public static void main(String[] args) {
        // 부모 변수가 자식 인스턴스 참조(다형적 참조)
        Parent poly = new Child();
        // 단 자식의 기능은 호출할 수 없음.
        //poly.childMethod();

        // 일시적 다운 캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
        ((Child) poly).childMethod();
    }
}

//instanceof 사용
package poly.basic;

public class CastingMain5 {
    public static void main(String[] args) {
        Parent parent1 = new Parent();
        System.out.println("parent1 호출");
        call(parent1);
        Parent parent2 = new Child();
        System.out.println("parent2 호출");
        call(parent2);
    }

    private static void call(Parent parent) {
        parent.parentMethod();
        if (parent instanceof Child) {
            System.out.println("Child 인스턴스 맞음");
            Child child = (Child) parent;
            child.childMethod();
        }
    }
}

 

 

추상 클래스

부모클래스의 역할을 제공하지만, 실제 생성되면 안 되는 클래스(상속을 목적으로 사용)

  • 생성 시 abstract 키워드를 붙인다
  • 직접 인스턴스 생성을 못하는 제약을 제외하면 일반 클래스와 같음
  • 추상 메서드가 하나라도 있을 경우 추상 클래스로 선언해야 함

추상 메서드 : 상속 받는 자식 클래스가 반드시 오버라이딩해서 사용해야 하는 메서드(실체가 없고 메서드 바디가 없음).

 

순수 추상 클래스 : 모든 메서드가 추상 메서드인 추상 클래스로 다형성을 위한 부모 타입으로써 껍데기 역할만 제공(추상 클래스의 메서드는 반드시 추상 메서드일 필요는 없음)

특징

  • 인스턴스 생성 불가
  • 상속 시 자식은 모든 메서드를 오버라이딩
  • 주로 다형성을 위해 사용
// 부모 클래스
package poly.ex4;
// 추상 클래스
public abstract class AbstractAnimal {
    // 추상 메서드
    public abstract void sound();
    public abstract void move();
}

// 자식 클래스
package poly.ex4;

public class Cat extends AbstractAnimal {
    // 추상 메서드 오버라이딩
    @Override
    public void sound() {
        System.out.println("냐옹");
    }
    // 추상 메서드 오버라이딩
    @Override
    public void move() {
        System.out.println("고양이 이동");
    }
}

// 메인 클래스
package poly.ex4;

public class AbstractMain {
    public static void main(String[] args) {
        // 추상 클래스 생성 불가
        //AbstractAnimal animal = new AbstractAnimal();

        Cat cat = new Cat();

        soundAnimal(cat);

        moveAnimal(cat);
    }
    // 변하지 않는 부분
    private static void soundAnimal(AbstractAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }

    // 변하지 않는 부분
    private static void moveAnimal(AbstractAnimal animal) {
        System.out.println("동물 이동 테스트 시작");
        animal.move();
        System.out.println("동물 이동 테스트 종료");
    }
}

 

 

인터페이스

순수 추상 클래스를 더 편리하게 사용할 수 있도록 제공하는 기능 interface 키워드 사용

기능

  • 인터페이스의 메서드는 모두 public abstract 임(메서드의 public abstract 키워드 생략 가능)
  • 인터페이스의 멤버 변수는 모두 public static fianl 임(public static final 키워드 생략 가능)
  • 인터페이스는 다중 구현(다중 상속)을 지원

선언

Public interface 인터페이스명 {}
// public, default 접근 제어자 지정 가능

 

구성

public interface interfaceAnimal {
    int MY_PI = 3.14; // public static final 생략 가능
    
    void sound(); // public abstract 생략 가능
}

 

구현 : 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 함.

  • 추상 메서드를 일부만 구현할 때는 해당 클래스를 추상 클래스로 변경
public class Dog implements InterfaceAnimal { 
// abstract 키워드로 추상 클래스로 바꾸면 일부 메서드만 구현 가능
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

 

상속 : 인터페이스 간 상속 가능(extends 키워드 사용), 구현과 상속은 함께 사용할 수 있음

public class Main implements C {

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

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

interface A {
    void a();
}
interface B {
    void b();
}
interface C extends A, B { }

 

default 메서드 : 추상 메서드의 기본적인 구현을 제공(추상 메서드가 아님, 인터페이스의 구현 시 필수로 재정의할 필요가 없음)

public class Main implements A {

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


    public static void main(String[] args) {
        Main main = new Main();
        main.a();

        // 디폴트 메서드 재정의 없이 바로 사용가능합니다.
        main.aa();
    }
}

interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
}

 

static 메서드 : 인터페이스의 static 메서드도 객체 없이 호출 가능(접근 제어자 생략 시 컴파일러에서 public 추가)

public class Main implements A {

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

    public static void main(String[] args) {
        Main main = new Main();
        main.a();
        main.aa();
        System.out.println();

        // static 메서드 aaa() 호출
        A.aaa();
    }
}

interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
    static void aaa() {
        System.out.println("static method");
    }
}

 

타입 변환

자동 타입 변환 : 인터페이스 변수 = 구현객체; 는 자동으로 타입 변환

public class Main {
    public static void main(String[] args) {
        
        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        
        // A 인터페이스에 구편체 B를 상속받은 C 대입
        A a2 = new C();
        
    }
}

interface A { }
class B implements A {}
class C extends B {}

 

강제 타입 변환 : 구현객체 타입 변수 = (구현객체 타입) 인터페이스 변수;

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

        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        a1.a();
        // a1.b(); // 불가능

        System.out.println("\nB 강제 타입변환");
        B b = (B) a1;
        b.a();
        b.b(); // 강제 타입변환으로 사용 가능
        System.out.println();

        // A 인터페이스에 구편체 B를 상속받은 C 대입
        A a2 = new C();
        a2.a();
        //a2.b(); // 불가능
        //a2.c(); // 불가능

        System.out.println("\nC 강제 타입변환");
        C c = (C) a2;
        c.a();
        c.b(); // 강제 타입변환으로 사용 가능
        c.c(); // 강제 타입변환으로 사용 가능


    }
}

interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }

    public void b() {
        System.out.println("B.b()");
    }
}
class C extends B {
    public void c() {
        System.out.println("C.c()");
    }
}

 

인터페이스의 다형성

  • 인터페이스도 매개변수와 반환 타입에서 다형성이 적용될 수 있음

 

3주차 강의 과제 : 계산기 제작

  • Calculator 클래스 제작 (사칙 연산 및 나머지 연산 수행)
    • 클래스 안에 calculate 메서드 제작
    • 반환 타입 double, 매개변수 3개 String operator, int firstNumber, int secondNumber
    • operator에 의해 연산자 매개값 받음( +, - , * , /)
    • int 타입의 매개변수로 피연산자 값을 받음
    • calculate 메서드는 전달 받은 매개변수를 통해 연산 수행
  • calculate 메서드에 % 나머지 연산 추가
  • AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 클래스 생성
    • Calculator 메서드와 포함 관계
    • 나머지 연산 기능 제외
  • AbstractOperation(추상 클래스)를 생성하고 위에 만든 연산 클래스를 추상화 Calculator 내부 코드 변경
    • 각 연산 클래스는 추상 클래스를 상속 받고 메서드를 오버라이딩
    • 생성자 혹은 Setter를 사용하여 연산을 수행할 연산 클래스의 객체를 AbstractOperation 클래스 타입의 필드에 주입(다형성)
    • calculate 메서드에서는 더 이상 연산자 타입을 받아 구분할 필요없이 주입 받은 연산 클래스의 operate 메서드를 통해 바로 연산을 수행

Calculator

package week03.homework;

public class Calculator {

	 // 1단계
//    public double calculate(String operator, int firstNumber, int secondNumber) {
//        if (operator.equals("add")) {
//            return firstNumber + secondNumber;
//        } else if (operator.equals("sub")) {
//            return firstNumber - secondNumber;
//        } else if (operator.equals("multiply")) {
//            return firstNumber * secondNumber;
//        } else if (operator.equals("divide")) {
//            return firstNumber / secondNumber;
//        } else {
//            return firstNumber % secondNumber;
//        }
//    }

       // 2단계
//    public double calculate(String operator, int firstNumber, int secondNumber) {
//        double result;
//        switch(operator) {
//            case "add":
//                result = AddOperation.operate(firstNumber, secondNumber);
//                break;
//            case "substract":
//                result = SubstractOperation.operate(firstNumber, secondNumber);
//                break;
//            case "multiply":
//                result = MultiplyOperation.operate(firstNumber, secondNumber);
//                break;
//            case "divide":
//                result = DivideOperation.operate(firstNumber, secondNumber);
//                break;
//            default:
//                result = firstNumber % secondNumber;
//        }
//        return result;
//    }

	// 4단계
    private AbstractOperation operation;

    public Calculator(AbstractOperation operation) {
        this.operation = operation;
    }

    public void setOperation(AbstractOperation operation) {
        this.operation = operation;
    }

    public double calculate(int firstNumber, int secondNumber) {
        return operation.operate(firstNumber, secondNumber);
    }
}

 

AbstractOperation

package week03.homework;

public abstract class AbstractOperation {
    abstract double operate(int firstNumber, int secondNumber);
}

 

AddOperation

package week03.homework;

public class AddOperation extends AbstractOperation {
    // 3단계
//    public static double operate(int firstNumber, int secondNumber) {
//        return (double) firstNumber + secondNumber;
//    }

    // 4단계
    @Override
    public double operate(int firstNumber, int secondNumber) {
        return firstNumber + secondNumber;
    }
}

 

SubstractOperation

package week03.homework;

public class SubstractOperation extends AbstractOperation {
    // 3단계
//    public static double operate(int firstNumber, int secondNumber) {
//        return (double) firstNumber - secondNumber;
//    }

    // 4단계
    @Override
    public double operate(int firstNumber, int secondNumber) {
        return firstNumber - secondNumber;
    }
}

 

MultiplyOperation

package week03.homework;

public class MultiplyOperation extends AbstractOperation {
    // 3단계
//    public static double operate(int firstNumber, int secondNumber) {
//        return (double) firstNumber * secondNumber;
//    }

    // 4단계
    @Override
    public double operate(int firstNumber, int secondNumber) {
        return firstNumber * secondNumber;
    }
}

 

DivideOperation

package week03.homework;

public class DivideOperation extends AbstractOperation {
    // 3단계
//    public static double operate(int firstNumber, int secondNumber) {
//        return (double) firstNumber / secondNumber;
//    }

    // 4단계
    @Override
    public double operate(int firstNumber, int secondNumber) {
        return (double) firstNumber / secondNumber;
    }
}

 

Main

package week03.homework;

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator(new AddOperation());
        System.out.println(calculator.calculate(4, 5));
        calculator.setOperation(new SubstractOperation());
        System.out.println(calculator.calculate(5, 2));
        calculator.setOperation(new MultiplyOperation());
        System.out.println(calculator.calculate(3, 5));
        calculator.setOperation(new DivideOperation());
        System.out.println(calculator.calculate(7, 3));
    }
}

 

프로그램 설계 과정에서 어려웠던 점

  • AbstractOperation을 생성자나 Setter를 사용하여 연산을 수행할 연산 클래스의 객체를 Ab tractOperation 클래스 타입의 필드에 주입하라는 제약 사항에 대해 이해하는 과정
    • 기존 설계 코드에서는 다형적 참조를 사용해 Main 메서드에서 AbstractOperation 타입의 객체를 각각 만들고 해당 객체를 static 메서드인 Calculator.calculate(AbstractOperation operator, int firstNumber, secondNumber) 형태로 사용
    • 구글링을 통해 클래스 타입 필드 주입에 대해 찾아보고 현재 코드 형태로 수정

'항해 99 > Java' 카테고리의 다른 글

역할과 구현, OCP  (0) 2024.02.08
Java 기초 12 - 다형성  (1) 2024.02.08
연산자, 조건문, 반복문, 배열  (1) 2024.02.06
Java 기초 개념  (0) 2024.02.05
Java 기초 12 - final , 상속  (1) 2024.01.30