[자바] java 객체지향핵심
01. 객체 간의 상속은 어떤 의미일까?
클래스 상속
새로운 클래스를 정의 할 때 이미 구현된 클래스를 상속(inheritance) 받아서 속성이나 기능을 확장하여 클래스를 구현함
이미 구현된 클래스보다 더 구체적인 기능을 가진 클래스를 구현해야 할때 기존 클래스를 상속함
상속하는 클래스 : 상위 클래스, parent class, base class, super class
상속받는 클래스 : 하위 클래스, child class, derived class, subclass
- 상속의 문법
class B extends A{
}
extends 키워드 뒤에는 단 하나의 클래스만 올 수 있음
자바는 단일 상속(single inheritance)만을 지원함
상속을 구현 하는 경우
상위 클래스는 하위 클래스 보다 더 일반적인 개념과 기능을 가짐
하위 클래스는 상위 클래스 보다 더 구체적인 개념과 기능을 가짐
하위 클래스가 상위 클래스의 속성과 기능을 확장 (extends)한다는 의미
02. 상속을 활용한 멤버십 클래스 구현하기
멤버십 시나리오
회사에서 고객 정보를 활용한 맞춤 서비스를 하기 위해 일반고객(Customer)과
이보다 충성도가 높은 우수고객(VIPCustomer)에 따른 서비스를 제공하고자 함
물품을 구매 할때 적용되는 할인율과 적립되는 보너스 포인트의 비율이 다름
여러 멤버십에 대한 각각 다양한 서비스를 제공할 수 있음
멤버십에 대한 구현을 클래스 상속을 활용하여 구현해보기
일반 고객(Customer) 클래스 구현
고객의 속성 : 고객 아이디, 고객 이름, 고객 등급, 보너스 포인트, 보너스 포인트 적립비율
일반 고객의 경우 물품 구매시 1%의 보너스 포인트 적립
package ch01;
public class Customer {
private int customerID;
private String customerName;
private String customerGrade;
int bonusPoint;
double bonusRatio;
public Customer() {
customerGrade = "SILVER";
bonusRatio = 0.01;
}
public int calcPrice(int price) {
bonusPoint += price * bonusRatio;
return price;
}
public String showCustomerInfo() {
return customerName + "님의 등급은 " + customerGrade +
"이며, 보너스 포인트는" + bonusPoint + "입니다";
}
}
우수 고객(VIPCustomer) 구현
매출에 더 많은 기여를 하는 단골 고객
제품을 살때 10%를 할인해 줌
보너스 포인트는 제품 가격의 5%를 적립해 줌
담당 전문 상담원이 배정됨
Customer 클래스에 추가해서 구현하는 것은 좋지 않음
VIPCustomer 클래스를 따로 구현
이미 Customer에 구현된 내용이 중복되므로 Customer를 확장하여 구현함(상속)
public class VIPCustomer extends Customer{
private int agentID;
double salesRatio;
public VIPCustomer() {
customerGrade = "VIP"; //오류 발생
bonusRatio = 0.05;
salesRatio = 0.1;
}
public int getAgentID() {
return agentID;
}
}
protected 접근 제어자
상위 클래스에 선언된 private 멤버 변수는 하위 클래스에서 접근 할 수 없음
외부 클래스는 접근 할 수 없지만, 하위 클래스는 접근 할 수 있도록 protected 접근 제어자를 사용
Customer.java
protected int customerID;
protected String customerName;
protected String customerGrade;
//getter, setter 구현
...
public int getCustomerID() {
return customerID;
}
public void setCustomerID(int customerID) {
this.customerID = customerID;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getCustomerGrade() {
return customerGrade;
}
public void setCustomerGrade(String customerGrade) {
this.customerGrade = customerGrade;
}
Customer와 VIPCustomer 테스트하기
public class CustomerTest {
public static void main(String[] args) {
Customer customerLee = new Customer();
customerLee.setCustomerName("이순신");
customerLee.setCustomerID(10010);
customerLee.bonusPoint = 1000;
System.out.println(customerLee.showCustomerInfo());
VIPCustomer customerKim = new VIPCustomer();
customerKim.setCustomerName("김유신");
customerKim.setCustomerID(10020);
customerKim.bonusPoint = 10000;
System.out.println(customerKim.showCustomerInfo());
}
}
03. 상속에서 클래스 생성 과정과 형 변환
하위 클래스가 생성 되는 과정
하위 클래스를 생성하면 상위 클래스가 먼저 생성 됨
new VIPCustomer()를 호출하면 Customer()가 먼저 호출 됨
클래스가 상속 받은 경우 하위 클래스의 생성자에서는 반드시 상위 클래스의 생성자를 호출 함
Customer.java
public Customer() {
customerGrade = "SILVER";
bonusRatio = 0.01;
System.out.println("Customer() 생성자 호출");
}
VIPCustomer.java
public VIPCustomer() {
customerGrade = "VIP";
bonusRatio = 0.05;
salesRatio = 0.1;
System.out.println("VIPCustomer() 생성자 호출");
}
super 키워드
하위 클래스에서 가지는 상위 클래스에 대한 참조 값
super()는 상위 클래스의 기본 생성자를 호출 함
하위 클래스에서 명시적으로 상위 클래스의 생성자를 호출하지 않으면 super()가 호출 됨
( 이때 반드시 상위 클래스의 기본 생성자가 존재 해야 함)상위 클래스의 기본 생성자가 없는 경우 ( 다른 생성자가 있는 경우 ) 하위 클래스에서는 생성자에서는 super를 이용하여 명시적으로 상위 클래스의 생성자를 호출 함
super는 생성된 상위 클래스 인스턴스의 참조 값을 가지므로 super를 이용하여 상위 클래스의 메서드나 멤버 변수에 접근할 수 있음
Customer.java
// 디폴트 생성자 없애고 매개 변수가 있는 생성자 추가
public Customer(int customerID, String customerName) {
this.customerID = customerID;
this.customerName = customerName;
customerGrade = "SILVER";
bonusRatio = 0.01;
System.out.println("Customer(int, String) 생성자 호출");
}
VIPCustomer.java
// super를 이용하여 상위 클래스의 생성자 명시적으로 호출
public VIPCustomer(int customerID, String customerName) {
super(customerID, customerName);
customerGrade = "VIP";
bonusRatio = 0.05;
salesRatio = 0.1;
System.out.println("VIPCustomer(int, String) 생성자 호출");
}
CustomerTest.java
public class CustomerTest {
public static void main(String[] args) {
Customer customerLee = new Customer(10010, "이순신");
customerLee.bonusPoint = 1000;
System.out.println(customerLee.showCustomerInfo());
VIPCustomer customerKim = new VIPCustomer(10020, "김유신");
customerKim.bonusPoint = 10000;
System.out.println(customerKim.showCustomerInfo());
}
}
- 출력 결과
상속에서 인스턴스 메모리의 상태
- 항상 상위 클래스의 인스턴스가 먼저 생성되고, 하위 클래스의 인스턴스가 생성 됨
형 변환(업캐스팅)
상위 클래스로 변수를 선언하고 하위 클래스의 생성자로 인스턴스를 생성
Customer customerLee = new VIPCustomer();
상위 클래스 타입의 변수에 하위 클래스 변수가 대입;
VIPCustomer vCustomer = new VIPCustomer(); addCustomer(vCustomer);
int addCustomer(Customer customer){
}
하위 클래스는 상위 클래스의 타입을 내포하고 있으므로 상위 클래스로의 묵시적 형 변환이 가능함
상속 관계에서 모든 하위 클래스는 상위 클래스로 형 변환(업캐스팅)이 됨
( 그 역은 성립하지 않음)
형 변환과 메모리
Customer vc = new VIPCustomer(); 에서 vc가 가리키는 것은?
VIPCustomer() 생성자에 의해 VIPCustomer 클래스의 모든 멤버 변수에 대한 메모리는 생성되었지만,
변수의 타입이 Customer 이므로 실제 접근 가능한 변수나 메서드는 Customer의 변수와 메서드임
클래스의 계층구조가 여러 단계인 경우
Human은 내부적으로 Promate와 mammal의 타입을 모두 내포하고 있음
Primate pHumman = new Humman();
Mammal mHumman = new Humman();
# 04. 메서드 재정의하기(overring)
하위 클래스에서 메서드 재정의 하기
오버라이딩(overriding) : 상위 클래스에 정의된 메서드의 구현 내용이 하위 클래스에서 구현할 내용과 맞지 않는 경우 하위 클래스에서 동일한 이름의 메서드를 재정의 할 수 있음
VIPCustomer 클래스의 calcPrice()는 할인율이 적용되지 않음
재정의 하여 구현해야 함
VIPCustomer.java
@Override
public int calcPrice(int price) {
bonusPoint += price * bonusRatio;
return price - (int)(price * salesRatio);
}
@overriding 애노테이션 (annotation)
애노테이션은 원래 주석이라는 의미
컴파일러에게 특별한 정보를 제공해주는 역할
- @overriding 애노테이션은 재정의 된 메서드라는 의미로 선언부가 기존의 메서드와 다른 경우 에러가 남
형 변환과 오버라이딩 메서드 호출
Customer vc = new VIPCustomer();
vc 변수의 타입은 Customer지만 인스턴스의 타입은 VIPCustomer 임
자바에서는 항상 인스턴스의 메서드가 호출 됨 (가상메서드의 원리)
자바의 모든 메서드는 가상 메서드(virtual method) 임
CustomerTest.java
public class CustomerTest {
public static void main(String[] args) {
Customer customerLee = new Customer(10010, "이순신");
customerLee.bonusPoint = 1000;
System.out.println(customerLee.showCustomerInfo());
VIPCustomer customerKim = new VIPCustomer(10020, "김유신");
customerKim.bonusPoint = 10000;
System.out.println(customerKim.showCustomerInfo());
int priceLee = customerLee.calcPrice(10000);
int priceKim = customerKim.calcPrice(10000);
System.out.println(customerLee.showCustomerInfo() + " 지불금액은 " + priceLee + "원 입니다.");
System.out.println(customerKim.showCustomerInfo() + " 지불금액은 " + priceKim + "원 입니다.");
Customer customerNo = new VIPCustomer(10030, "나몰라");
customerNo.bonusPoint = 10000;
int priceNo = customerNo.calcPrice(10000);
System.out.println(customerNo.showCustomerInfo() + " 지불금액은 " + priceNo + "원 입니다.");
}
}
Customer customerNo = new VIPCustomer(10030, “나몰라”); customerNo.bonusPoint = 10000; int priceNo = customerNo.calcPrice(10000); System.out.println(customerNo.showCustomerInfo() + “ 지불금액은 “ + priceNo + “원 입니다.”);
이 부분은 형변환으로 상위 클래스로 변수를 선언하고 하위 클래스의 생성자로 인스턴스를 생성했다. 실행하면 상위 클래스인 customer께 아닌 하위클래스의 VIPCustomer의 내용이 불려온다.
05. 메서드 재정의와 가상 메서드 원리
메서드는 어떻게 호출되고 실행 되는가?
메서드(함수)의 이름은 주소값을 나타냄
메서드는 명령어의 set 이고 프로그램이 로드되면 메서드 영역(코드 영역)에 명령어 set이 위치
해당 메서드가 호출 되면 명령어 set 이 있는 주소를 찾아 명령어가 실행됨
이때 메서드에서 사용하는 변수들은 스택 메모리에 위치 하게됨
따라서 다른 인스턴스라도 같은 메서드의 코드는 같으므로 같은 메서드가 호출됨
인스턴스가 생성되면 변수는 힙 메모리에 따로 생성되지만, 메서드 명령어 set은 처음 한번만 로드 됨
public class TestMethod {
int num;
void aaa() {
System.out.println("aaa() 호출");
}
public static void main(String[] args) {
TestMethod a1 = new TestMethod();
a1.aaa();
TestMethod a2 = new TestMethod();
a2.aaa();
}
}
가상 메서드의 원리
가상 메서드 테이블(vitual method table)에서 해당 메서드에 대한 address를 가지고 있음
재정의된 경우는 재정의 된 메서드의 주소를 가리킴
06. 다형성과 다형성을 사용하는 이유
다형성(polymorphism) 이란?
하나의 코드가 여러 자료형으로 구현되어 실행되는 것
같은 코드에서 여러 다른 실행 결과가 나옴
정보은닉, 상속과 더불어 객체지향 프로그래밍의 가장 큰 특징 중 하나임
다형성을 잘 활용하면 유연하고 확장성있고, 유지보수가 편리한 프로그램을 만들수 있음
다형성의 예
class Animal{
public void move() {
System.out.println("동물이 움직입니다.");
}
public void eating() {
}
}
class Human extends Animal{
public void move() {
System.out.println("사람이 두발로 걷습니다.");
}
public void readBooks() {
System.out.println("사람이 책을 읽습니다.");
}
}
class Tiger extends Animal{
public void move() {
System.out.println("호랑이가 네 발로 뜁니다.");
}
public void hunting() {
System.out.println("호랑이가 사냥을 합니다.");
}
}
class Eagle extends Animal{
public void move() {
System.out.println("독수리가 하늘을 날아갑니다.");
}
public void flying() {
System.out.println("독수리가 날개를 쭉 펴고 멀리 날아갑니다");
}
}
public class AnimalTest {
public static void main(String[] args) {
Animal hAnimal = new Human();
Animal tAnimal = new Tiger();
Animal eAnimal = new Eagle();
AnimalTest test = new AnimalTest();
test.moveAnimal(hAnimal);
test.moveAnimal(tAnimal);
test.moveAnimal(eAnimal);
ArrayList<Animal> animalList = new ArrayList<Animal>();
animalList.add(hAnimal);
animalList.add(tAnimal);
animalList.add(eAnimal);
for(Animal animal : animalList) {
animal.move();
}
}
public void moveAnimal(Animal animal) {
animal.move();
}
}
public void moveAnimal(Animal animal) { animal.move(); 에서 코드는 한줄인데 어떤 자료형이 들어가냐에 따라 move의 작동 내용이 달라진다.
다형성을 사용하는 이유?
다른 동물을 추가하는 경우
상속과 메서드 재정의를 활용하여 확장성 있는 프로그램을 만들 수 있음
그렇지 않는 경우 많은 if-else if문이 구현되고 코드의 유지보수가 어려워짐
상위 클래스에서는 공통적인 부분을 제공하고 하위 클래스에서는 각 클래스에 맞는 기능 구현
여러 클래스를 하나의 타입(상위 클래스)으로 핸들링 할 수 있음
다형성을 활용한 멤버십 프로그램 확장
일반 고객과 VIP 고객 중간 멤버십 만들기
고객이 늘어 일반 고객보다는 많이 구매하고 VIP보다는 적게 구매하는 고객에게도 해택을 주기로 했다.
GOLD 고객 등급을 만들고 혜택은 다음과 같다
- 제품을 살때는 10프로를 할인해준다
- 보너스 포인트는 2%를 적립해준다
GoldCustomer.java
public class GoldCustomer extends Customer{
double saleRatio;
public GoldCustomer(int customerID, String customerName){
super(customerID, customerName);
customerGrade = "GOLD";
bonusRatio = 0.02;
saleRatio = 0.1;
}
public int calcPrice(int price){
bonusPoint += price * bonusRatio;
return price - (int)(price * saleRatio);
}
}
VIPCustomer.java
//showCustomerInfo() 재정의
public String showCustomerInfo(){
return super.showCustomerInfo() + " 담당 상담원 번호는 " + agentID + "입니다";
}
CustomerTest.java
public class CustomerTest {
public static void main(String[] args) {
ArrayList<Customer> customerList = new ArrayList<Customer>();
Customer customerLee = new Customer(10010, "이순신");
Customer customerShin = new Customer(10020, "신사임당");
Customer customerHong = new GoldCustomer(10030, "홍길동");
Customer customerYul = new GoldCustomer(10040, "이율곡");
Customer customerKim = new VIPCustomer(10050, "김유신", 12345);
customerList.add(customerLee);
customerList.add(customerShin);
customerList.add(customerHong);
customerList.add(customerYul);
customerList.add(customerKim);
System.out.println("====== 고객 정보 출력 =======");
for( Customer customer : customerList){
System.out.println(customer.showCustomerInfo());
}
System.out.println("====== 할인율과 보너스 포인트 계산 =======");
int price = 10000;
for( Customer customer : customerList){
int cost = customer.calcPrice(price);
System.out.println(customer.getCustomerName() +" 님이 " + + cost + "원 지불하셨습니다.");
System.out.println(customer.getCustomerName() +" 님의 현재 보너스 포인트는 " + customer.bonusPoint + "점입니다.");
}
}
}
ArrayList<Customer> customerList = new 여기서 어레이리스트로 Cutstomer 객체를 넣어서 배열 리스트 선언했다.
추가적인 요구사항때 상속이 있으면 기존 코드 고치는 거도 있겠지만 추가 몇 부분만 할 수있어서 다형성 및 상속을 사용한다.
07. 상속은 언제 사용 할까?
IS-A 관계(is a relationship : inheritance)
일반적인(general) 개념과 구체적인(specific) 개념과의 관계
- 상위 클래스 : 하위 클래스보다 일반적인 개념 ( 예: Employee )
하위 클래스 : 상위 클래스보다 구체적인 개념들이 더해짐 ( 예: Engineer, Manager…)
상속은 클래스간의 결합도가 높은 설계
상위 클래스의 수정이 많은 하위 클래스에 영향을 미칠 수 있음
- 계층구조가 복잡하거나 hierarchy가 높으면 좋지 않음
HAS-A 관계(composition)
클래스가 다른 클래스를 포함하는 관계 ( 변수로 선언 )
코드 재사용의 가장 일반적인 방법
Student가 Subject를 포함하는
Library를 구현할 때 ArrayList 생성하여 사용
상속하지 않음
Has a 클래스는 이미 클래스가 있는데 무조건 재사용이 가능한 건 아님. 어떤 학생이 수업을 들으면 과목코드 있는데 학생 클래스가 과목을 상속 받는 건 아님.
08. 다운 캐스팅과 instanceof
다운 캐스팅(downcasting)
업캐스팅된 클래스를 다시 원래의 타입으로 형 변환
하위 클래스로의 형 변환은 명시적으로 해야 함
Customer vc = new VIPCustomer(); //묵시적
VIPCustomer vCustomer = (VIPCustomer)vc; //명시적
instanceof를 이용하여 인스턴스의 형 체크
- 원래 인스턴스의 형이 맞는지 여부를 체크하는 키워드 맞으면 true 아니면 false를 반환 함
AnimalTest.java
public void testDownCasting(ArrayList<Animal> list) {
for(int i =0; i<list.size(); i++) {
Animal animal = list.get(i);
if ( animal instanceof Human) {
Human human = (Human)animal;
human.readBooks();
}
else if( animal instanceof Tiger) {
Tiger tiger = (Tiger)animal;
tiger.hunting();
}
else if( animal instanceof Eagle) {
Eagle eagle = (Eagle)animal;
eagle.flying();
}
else {
System.out.println("error");
}
}
}
09. 추상 클래스(abstract class) 구현하기
추상 클래스란?
구현 코드 없이 메서드의 선언만 있는 추상 메서드(abstract method)를 포함한 클래스
메서드 선언(declaration) : 반환타입, 메서드 이름, 매개변수로 구성
메서드 정의(definition) : 메서드 구현(implementation)과 동일한 의미 구현부(body) 를 가짐 ({ })
예) int add(int x, int y); // 선언
int add(int x, int y){ } // 구현부가 있음, 추상 메서드 아님abstract 예약어를 사용
추상 클래스는 new 할 수 없음 ( 인스턴스화 할 수 없음 )
추상 클래스 구현하기
메서드에 구현 코드가 없으면 abstract 로 선언
abstract로 선언된 메서드를 가진 클래스는 abstract로 선언
모든 메서드가 구현 된 클래스라도 abstract로 선언되면 추상 클래스로 인스턴스화 할 수 없음
추상 클래스의 추상 메서드는 하위 클래스가 상속 하여 구현
추상 클래스 내의 추상 메서드 : 하위 클래스가 구현해야 하는 메서드
추상 클래스 내의 구현 된 메서드 : 하위 클래스가 공통으로 사용하는 메서드 ( 필요에 따라 하위 클래스에서 재정의 함 )
예제 구현하기
Computer.java
public abstract class Computer {
abstract void display();
abstract void typing();
public void turnOn() {
System.out.println("전원을 켭니다.");
}
public void turnOff() {
System.out.println("전원을 끕니다.");
}
}
DeskTop.java
public class DeskTop extends Computer{
@Override
void display() {
System.out.println("DeskTop display");
}
@Override
void typing() {
System.out.println("DeskTop typing");
}
@Override
public void turnOff() {
System.out.println("Desktop turnoff");
}
}
NoteBook.java
public abstract class NoteBook extends Computer{
@Override
public void typing() {
System.out.println("NoteBook typing");
}
}
MyNoteBook.java
public class MyNoteBook extends NoteBook{
@Override
void display() {
System.out.println("MyNoteBook display");
}
}
ComputerTest.java
public class ComputerTest {
public static void main(String[] args) {
Computer computer = new DeskTop();
computer.display();
computer.turnOff();
NoteBook myNote = new MyNoteBook();
}
}
10. 추상 클래스의 응용 - 템플릿 메서드 패턴
템플릿 메서드
추상 메서드나 구현 된 메서드를 활용하여 코드의 흐름(시나리오)를 정의하는 메서드
final로 선언하여 하위 클래스에서 재정의 할 수 없게 함
프레임워크에서 많이 사용되는 설계 패턴
추상 클래스로 선언된 상위 클래스에서 템플릿 메서드를 활용하여 전체적인 흐름을 정의 하고 하위 클래스에서
다르게 구현되어야 하는 부분은 추상 메서드로 선언하여 하위 클래스에서 구현 하도록 함
템플릿 메서드 예제
Car.java
public abstract class Car {
public abstract void drive();
public abstract void stop();
public void startCar() {
System.out.println("시동을 켭니다.");
}
public void turnOff() {
System.out.println("시동을 끕니다.");
}
final public void run() {
startCar();
drive();
stop();
turnOff();
}
}
ManualCar.java
public class ManualCar extends Car{
@Override
public void drive() {
System.out.println("사람이 운전합니다.");
System.out.println("사람이 핸들을 조작합니다.");
}
@Override
public void stop() {
System.out.println("브레이크를 밟아서 정지합니다.");
}
}
AICar.java
public class AICar extends Car{
@Override
public void drive() {
System.out.println("자율 주행합니다.");
System.out.println("자동차가 스스로 방향을 바꿉니다.");
}
@Override
public void stop() {
System.out.println("스스로 멈춥니다.");
}
}
CarTest.java
public class CarTest {
public static void main(String[] args) {
Car aiCar = new AICar();
aiCar.run();
System.out.println("=================");
Car manualCar = new ManualCar();
manualCar.run();
}
}
final 예약어
- final 변수 : 값이 변경될 수 없는 상수
public static final double PI = 3.14;
final 메서드 : 하위 클래스에서 재정의 할 수 없는 메서드
final 클래스 : 상속할 수 없는 클래스
여러 자바 파일에서 사용하는 상수 값 정의
Define.java
public class Define {
public static final int MIN = 1;
public static final int MAX = 999999;
public static final double PI = 3.14;
public static final String GREETING = "Good Morning!";
public static final int MATH_CODE = 1001;
public static final int CHEMISTRY_CODE = 1002;
}
UsingDefine.java
public class UsingDefine {
public static void main(String[] args) {
System.out.println(Define.GREETING);
System.out.println(Define.MIN);
System.out.println(Define.MAX);
System.out.println(Define.MATH_CODE);
System.out.println(Define.CHEMISTRY_CODE);
System.out.println("원주률은" + Define.PI + "입니다.");
}
}
11. 인터페이스(interface)
인터페이스란?
모든 메서드가 추상 메서드로 선언됨 public abstract
모든 변수는 상수로 선언됨 public static final
interface 인터페이스 이름{
public static final float pi = 3.14F;
public void makeSomething();
}
- 자바 8 부터 디폴트 메서드(default method)와 정적 메서드(static method) 기능의 제공으로 일부 구현 코드가 있음
인터페이스 정의와 구현
Calc.java
public interface Calc {
double PI = 3.14;
int ERROR = -99999999;
int add(int num1, int num2);
int substract(int num1, int num2);
int times(int num1, int num2);
int divide(int num1, int num2);
}
Calculator.java
public abstract class Calculator implements Calc{
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int substract(int num1, int num2) {
return num1 - num2;
}
}
CompleteCalc.java
public class CompleteCalc extends Calculator{
@Override
public int times(int num1, int num2) {
return num1 * num2;
}
@Override
public int divide(int num1, int num2) {
if( num2 == 0 )
return ERROR;
else
return num1 / num2;
}
public void showInfo() {
System.out.println("모두 구현하였습니다.");
}
}
CalculatorTest.java
public class CalculatorTest {
public static void main(String[] args) {
Calc calc = new CompleteCalc();
int num1 = 10;
int num2 = 2;
System.out.println(num1 + "+" + num2 + "=" + calc.add(num1, num2));
System.out.println(num1 + "-" + num2 + "=" +calc.substract(num1, num2));
System.out.println(num1 + "*" + num2 + "=" +calc.times(num1, num2));
System.out.println(num1 + "/" + num2 + "=" +calc.divide(num1, num2));
}
}
인터페이스 구현과 형 변환
- 인터페이스를 구현한 클래스는 인터페이스 형으로 선언한 변수로 형 변환 할 수 있음
Calc calc = new CompleteCalc();
상속에서의 형 변환과 동일한 의미
클래스 상속과 달리 구현 코드가 없으므로 여러 인터페이스를 구현할 수 있음 ( cf. extends)
형 변환되는 경우 인터페이스에 선언된 메서드만을 사용가능함
12. 인터페이스는 왜 쓰는가?
인터페이스가 하는 일
클래스나 프로그램이 제공하는 기능을 명시적으로 선언
일종의 클라이언트 코드와의 약속이며 클래스나 프로그램이 제공하는 명세(specification)
클라이언트 프로그램은 인터페이스에 선언된 메서드 명세만 보고 이를 구현한 클래스를 사용할 수 있음
어떤 객체가 하나의 인터페이스 타입이라는 것은 그 인터페이스가 제공하는 모든 메서드를 구현했다는 의미임
인터페이스를 구현한 다양한 객체를 사용함 - 다형성
예) JDBC 인터페이스
13 인터페이스를 활용한 다형성 구현 (dao 구현하기)
(디비를 직접 짜진 않고 자바로 비슷하게 구현) mysql 쓰다 오라클 쓰면 오류 나는데 이런 버전 관리를 하나의 소스 안에서 관리하면 좋을텐데 이걸 인테페이스로 구현하고 그걸 오라클버젼, mysql, mssql 버전으로 구현한 버전을 각각 내면 어떨까? 그게 밑의 그림.
똑같은 인터페이스지만 어떻게 다형성으로 활용 되는지 알아보자.
인터페이스와 다형성
하나의 인터페이스를 여러 객체가 구현하게 되면 클라이언트 프로그램은 인터페이스의 메서드를 활용하여 여러 객체의 구현을 사용할 수 있음 ( 다형성)
여러가지 예
인터페이스를 활용한 dao 구현하기
DB에 회원 정보를 넣는 dao(data access object)를 여러 DB 제품이 지원될 수 있게 구현함
환경파일(db.properties) 에서 database의 종류에 대한 정보를 읽고 그 정보에 맞게 dao 인스턴스를 생성하여 실행될 수 있게 함
source hierachy
여기 db.properies를 넣어줘야한다(프로젝트에서 바로 넣어주자) 직접.
그리고 UserInfoClient 에서
Properties prop = new Properties(); prop.load(fis);
String dbType = prop.getProperty("DBTYPE");
UserInfo userInfo = new UserInfo();
userInfo.setUserId("12345");
userInfo.setPasswd("!@#$%");
userInfo.setUserName("이순신");
이 부분 중
String dbType = prop.getProperty("DBTYPE");
여기가 아까 properties에서 설정한 내용 갖오는거 mysql 넣었으니까 mysql정보가 나온다.
그 아래 부분은 유저 생성하는거. 사실 유저 생성은 웹에서 하고 넘어오지만 일단 여기서 생성해보자,.
UserInfo.java (사용자 정보 클래스)
public class UserInfo {
private String userId;
private String passwd;
private String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
UserInfoDao.java ( dao 에서 제공되어야 할 메서드를 선언한 인터페이스 )
public interface UserInfoDao {
void insertUserInfo(UserInfo userInfo);
void updateUserInfo(UserInfo userInfo);
void deleteUserInf(UserInfo userInfo);
}
UserInfoMySqlDao.java (UserInfoDao 인터페이스를 구현한 MySql 버전 dao)
public class UserInfoMySqlDao implements UserInfoDao{
@Override
public void insertUserInfo(UserInfo userInfo) {
System.out.println("insert into MYSQL DB userId =" + userInfo.getUserId() );
}
@Override
public void updateUserInfo(UserInfo userInfo) {
System.out.println("update into MYSQL DB userId = " + userInfo.getUserId());
}
@Override
public void deleteUserInf(UserInfo userInfo) {
System.out.println("delete from MYSQL DB userId = " + userInfo.getUserId());
}
}
UserInfoOracleDao.java (UserInfoDao 인터페이스를 구현한 Oracle 버전 dao)
public class UserInfoOracleDao implements UserInfoDao{
public void insertUserInfo(UserInfo userInfo){
System.out.println("insert into ORACLE DB userId =" + userInfo.getUserId() );
}
public void updateUserInfo(UserInfo userInfo){
System.out.println("update into ORACLE DB userId = " + userInfo.getUserId());
}
public void deleteUserInf(UserInfo userInfo){
System.out.println("delete from ORACLE DB userId = " + userInfo.getUserId());
}
}
UserInfoClient.java (UserInfoDao 인터페이스를 활용하는 클라이언트 프로그램)
public class UserInfoClient {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("db.properties");
Properties prop = new Properties();
prop.load(fis);
String dbType = prop.getProperty("DBTYPE");
UserInfo userInfo = new UserInfo();
userInfo.setUserId("12345");
userInfo.setPasswd("!@#$%");
userInfo.setUserName("이순신");
UserInfoDao userInfoDao = null;
if(dbType.equals("ORACLE")){
userInfoDao = new UserInfoOracleDao();
}
else if(dbType.endsWith("MYSQL")){
userInfoDao = new UserInfoMySqlDao();
}
else{
System.out.println("db support error");
return;
}
userInfoDao.insertUserInfo(userInfo);
userInfoDao.updateUserInfo(userInfo);
userInfoDao.deleteUserInf(userInfo);
}
}
db.properties 환경파일이 MYSQL 일때
DBTYPE=MYSQL
실행결과
db.properties 환경파일이 ORACLE 일때
DBTYPE=ORACLE
실행결과
말한대로 저 부분만 바꾸면 오라클이 나온다. 즉 저 부분만 바꾸면 원하는 디비를 사용 가능하며 이게 다형성이다. 실제 구현코드 상관 없는 부분은 안 바뀌고 바뀌어야 할 부분만 바뀜.
14. 인터페이스의 여러가지 요소
상수
- 모든 변수는 상수로 변환 됨 public static final ``` double PI = 3.14; int ERROR = -999999999;
## 추상 메서드
- 모든 선언된 메서드는 추상 메서드 public abstract
## 디폴트 메서드 (자바 8이후)
- 구현을 가지는 메서드, 인터페이스를 구현하는 클래스들에서 공통으로 사용할 수 있는 기본 메서드
- default 키워드 사용
default void description() { System.out.println(“정수 계산기를 구현합니다.”); myMethod(); }
- 구현 하는 클래스에서 재정의 할 수 있음
@Override public void description() { System.out.println(“CompleteCalc에서 재정의한 default 메서드”); //super.description(); }
- 인터페이스를 구현한 클래스의 인스턴스가 생성 되어야 사용 가능함
## 정적 메서드 (자바 8이후)
- 인스턴스 생성과 상관 없이 인터페이스 타입으로 사용할 수 있는 메서드
static int total(int[] arr) { int total = 0;
for(int i: arr) {
total += i;
}
mystaticMethod();
return total; } ```
private 메서드 (자바 9이후)
인터페이스를 구현한 클래스에서 사용하거나 재정의 할 수 없음
인터페이스 내부에서만 사용하기 위해 구현하는 메서드
default 메서드나 static 메서드에서 사용함
이미 제공하고 있는 디폴트메서드를 재정의 할 수도 있음.
private void myMethod() {
System.out.println("private method");
}
private static void mystaticMethod() {
System.out.println("private static method");
}
프라이빗 메서드는 인터페이스 내에 구현. 구현하는 쪽에서 사용하는 것도 아니고 재정의 할 수 있는 거도 아니고 단지 인터페이스 내에서 사용.
- 근데 프라이빗 메서드는 자바 9버전 부터 지원한다. 8버전으로 하면 안되니 참고.
15. 여러 인터페이스 구현하기, 인터페이스의 상속
여러 인터페이스 구현
자바의 인터페이스는 구현 코드가 없으므로 하나의 클래스가 여러 인터페이스는 구현 할 수 있음
디폴트 메서드가 중복 되는 경우는 구현 하는 클래스에서 재정의 하여야 함
여러 인터페이스를 구현한 클래스는 인터페이스 타입으로 형 변환 되는 경우 해당 인터페이스에 선언된 메서드만 사용 가능 함
Sell.java
public interface Sell {
void sell();
}
Buy.java
public interface Buy {
void buy();
}
Customer.java
public class Customer implements Buy, Sell{
//여기서 buy,sell 구현
@Override
public void sell() {
System.out.println("customer sell");
}
@Override
public void buy() {
System.out.println("customer buy");
}
@Override
public void order() {
System.out.println("customer order");
}
public void sayHello() {
System.out.println("Hello");
}
}
여기서 부분에서
@Override public void order() { System.out.println(“customer order”); Buy.super.order(); Sell.super.order(); }
이렇게 썼으면 각각 Buy와 SEll의 메서드를 쓰겠다는 뜻.
CustomerTest.java
public class CustomerTest {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buy();
customer.sell();
customer.sayHello();
Buy buyer = customer;
buyer.buy();
Sell seller = customer;
seller.sell();
}
}
디폴트 메서드가 중복 되는 경우
- 구현 코드를 가지고 인스턴스 생성된 경우만 호출되는 디폴트 메서드의 경우 두 개의 인터페이스에서 중복되면 구현하는 클래스에서 반드시 재정의를 해야 함
Sell.java
public interface Sell {
void sell();
default void order() {
System.out.println("판매 주문");
}
}
Buy.java
public interface Buy {
void buy();
default void order() {
System.out.println("구매 주문");
}
}
Customer.java
public class Customer implements Buy, Sell{
@Override
public void sell() {
System.out.println("customer sell");
}
@Override
public void buy() {
System.out.println("customer buy");
}
public void sayHello() {
System.out.println("Hello");
}
@Override
public void order() {
System.out.println("customer order");
}
}
CustomerTest.java
public class CustomerTest {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buy();
customer.sell();
customer.sayHello();
Buy buyer = customer;
buyer.buy();
Sell seller = customer;
seller.sell();
buyer.order();
seller.order();
}
}
인터페이스의 상속
인터페이스 사이에도 상속을 사용할 수 있음
extends 키워드를 사용
인터페이스는 다중 상속이 가능하고 구현 코드의 상속이 아니므로 타입 상속 이라고 함
X.java
public interface X {
void x();
}
Y.java
public interface Y {
void y();
}
MyInterface.java
public interface MyInterface extends X, Y{
void myMethod();
}
MyClass.java
public class MyClass implements MyInterface{
@Override
public void x() {
System.out.println("x()");
}
@Override
public void y() {
System.out.println("y()");
}
@Override
public void myMethod() {
System.out.println("myMethod()");
}
}
MyClassTest.java
public class MyClassTest {
public static void main(String[] args) {
MyClass mClass = new MyClass();
X xClass = mClass;
xClass.x();
Y yClass = mClass;
yClass.y();
MyClass iClass = mClass;
iClass.x();
iClass.y();
iClass.myMethod();
}
}
클래스 상속과 인터페이스 구현 함께 쓰기
- 실무에서 프레임워크나 오픈소스와 함께 연동되는 구현을 하게 되면 클래스 상속과 인터페이스의 구현을 같이 사용하는 경우가 많음
책이 순서대로 대여가 되는 도서관 구현
책을 보관하는 자료 구조가 Shelf에 구현됨 (ArrayList)
Queue 인터페이스를 구현함
Shelf 클래스를 상속 받고 Queue를 구현한다.
Shelf.java
public class Shelf {
protected ArrayList<String> shelf;
public Shelf() {
shelf = new ArrayList<String>();
}
public ArrayList<String> getShelf(){
return shelf;
}
public int getCount() {
return shelf.size();
}
}
Queue.java
public interface Queue {
void enQueue(String title);
String deQueue();
int getSize();
}
BookShelf.java
public class BookShelf extends Shelf implements Queue{
@Override
public void enQueue(String title) {
shelf.add(title);
}
@Override
public String deQueue() {
return shelf.remove(0);
}
@Override
public int getSize() {
return getCount();
}
}
public class BookShelf extends Shelf implements Queue{
이부분 보면 bookshelf 가 shelf를 상속하고 그 shelf가 큐를 구현하고 있다.
BookShelfTest.java
public class BookShelfTest {
public static void main(String[] args) {
Queue bookQueue = new BookShelf();
bookQueue.enQueue("태백산맥1");
bookQueue.enQueue("태백산맥2");
bookQueue.enQueue("태백산맥3");
System.out.println(bookQueue.deQueue());
System.out.println(bookQueue.deQueue());
System.out.println(bookQueue.deQueue());
}
}
16. 복습해보세요
추상 클래스와 템플릿 메서드
Player가 있고 Player는 GameLevel 속성을 가집니다. 각 GameLevel 단계 마다 run(), jump(), turn() 세 가지 기능이 업그레이드 됩니다.
초보자 레벨 : 천천히 달립니다. run() 만 가능
중급자 레벨 : 빠르게 달리고, 점프할 수 있습니다. run(), jump() 가능
고급자 레벨 : 엄청 빠르게 달리고, 높게 점프하고, 턴할 수 있습니다. run(), jump(), turn() 가능
Player는 한번에 하나의 레벨 상태만을 가질 수 있습니다.
Player가 play() 중에 레벨에 있는 go(int count) 라는 메서드를 호출하면 run() 하고 count 횟수 만큼 jump() 하고 turn() 합니다. 다음 클래스 다이어그램을 참고하여 각 레벨에서 go() 가 호출 될때 다음과 같이 출력 되도록 하세요
인터페이스를 활용한 정책 프로그래밍
고객 센터에 전화 상담을 하는 상담원들이 있습니다. 일단 고객에게서 전화가 오면 대기열에 저장되고 각 상담원에게 배분이 됩니다.
배분이 되는 정책은 크게 세 가지가 있습니다.
모든 상담원이 동일한 상담 건수를 처리하도록 상담원 순서대로 배분합니다.
쉬고 있거나 상담원에게 할당된 통화 수가 가장 적은 상담원에게 배분합니다.
고객의 등급에 따라 등급이 높은 고객은 업무능력이 우수한 상담원에게 배분합니다.
세 가지 정책은 필요에 따라 바뀌어 운영될 수 있습니다. 다음 클래스 다이어그램을 참고하여 각 배분 규칙이 적용되도록 구현해 보세요
테스트 프로그램은 다음과 같습니다. 문자를 입력 받아 입력되는 문자에 따라 배분 규칙을 수행하도록 합니다.
public class SchedulerTest {
public static void main(String[] args) throws IOException {
System.out.println("전화 상담원 할당 방식을 선택하세요");
System.out.println("R : 한명씩 차례대로");
System.out.println("L : 대기가 적은 상담원 우선");
System.out.println("P : 우선순위가 높은 고객우선 숙련도 높은 상담원");
int ch = System.in.read();
Scheduler scheduler = null;
if ( ch == 'R' || ch =='r') {
scheduler = new RoundRobin();
}
else if ( ch == 'L' || ch =='l') {
scheduler = new LeastJob();
}
else if ( ch == 'P' || ch =='p') {
scheduler = new PriorityAllocation();
}
else {
System.out.println("지원되지 않는 기능입니다.");
return;
}
scheduler.getNextCall();
scheduler.sendCallToAgent();
}
}