항해 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 |