<1> Abstraction
클래스는 추상화의 최소 단위이다
속성과 기능을 추상화 하여 클래스로 만들고, 이 클래스를 구체화 하면 프로그램의 객체가 된다
Practice Problem: 아래 클래스들의 공통 분모를 뽑아서 상속 구조를 만들어 보자
public class DiselSuv{
private int curX, curY;
public void reportPosition(){System.out.printf("현재 위치: (%d, %d)%n" ,curX, curY );}
public void addFuel(){System.out.printf("주유소에서 급유");}
}
public class ElectricCar{
private int curX, curY;
public void reportPosition(){
System.out.printf("현재 위치: (%d %d)%n", curX, curY);}
public void addFuel(){System.out.printf("급속 충전");}
}
}
상속 구조로 위 문제를 표현하면 아래와 같은 코드로 작성 해 볼 수 있다
public class Car{
private int curx, curY;
public void reportPosition(){System.out.printf("현재 위치: (%d %d)%n",curX,curY);}}
public void addFuel(){**System.out.pirntf("연료");} //얘 결굴 안쓰넹?**
}
class DiselSuv extends Car {
//@override -> 연료를 지우고 오버라이드를 없앨까?
public void addFuel(){System.out.println("주유소에서 급유");}}
class ElectricCar extends Car{
@override
public void addFuel(){System.out.println("급속충전");}}}
//Test
public class CarTest{
public static void main(String[ ]args){
Car[] c = {new DiselSuv(), new ElectricCar()};
for(Car cx = c){cx.addFuel(); cx.reportPosition();}}}
상속을 통해 코드화 하였더니, 결국 Car class 안에 있는 addFuel() 의 "연료"는 단 한번도 쓰이지 않고 모두 오버라이드 되었다. 하지만 addFuel() 메서드를 지워버린 후, 디젤과 전기차 클래스 안의 override를 없애고 직접 메서드를 만든다면, Test 클래스에서는 부모 클래스에 addFuel이 없기 때문에 사용할 수 없다.
for(Car cx = c){cx.addFuel(); cx.reportPosition();}
if ( cx instance of ElectricCar) {
ElectricCar ev = new (ElectricCar) cx;
cx.addFuel();}
else if (cx instance of DiselSuv) {
DiselSuv ds = new (DiselSuv) cx;
cx.addFuel();
즉, 영원히 쓰이지 않는 Car Class 의 연료 메서드를 없애버린다면, Car Test 클래스 안에서 Car 클래스의 객체가 어떤 자식 클래스에 속해있는지 일일이 확인해봐야 한다.
지금 이 car 객체가 혹시..ev니? 아니면 디젤? 하이브리드...?
→ 엄청 코드가 길어지고 복잡해진다!
이를 해결해 줄 수 있는 클래스가 바로 abstract class 이다
<2> Abstract Class
Abstract Class 란 객체를 생성할 수 없는 클래스라는 의미를 가지며, 클래스 선언부의 abstract 를 추가하여 선언한다.
언제 abstract class 를 써야 할까?
자손 클래스에서 반드시 재정의 되어 사용 → 조상의 구현이 무의미한 메서드일 때 [EX] 조상의 addFuel()은 아무짝에 쓸모 없었다...ㅠ →abstract class 로 구현하자
abstract class Car {
private int curX, curY;
public void reportPosition(){ System.out.prinf("현위치: (%d %d)%n", curX, curY);}
public abstract void addFuel();// abstract method design pattern -> body 없다
}
위의 코드는 부모 클래스로 쓰이던 Car class를 abstract class로 만든 코드이다. 이 때, abstract class란 객체를 생성할 수 없는 클래스라고 했는데, 테스트 클래스에서의 코드는 어떻게 바뀌어야 할까?
//Car cx = new Car();
Car cxx = new DisselSuv(); //자식을 참조한다
비록, abstract class를 객체로 생성할 수는 없지만, 상위 클래스 타입으로 자식인 disselsuv 클래스를 참조할 수 있다.
🤦🏻♀️ 조상 클래스에서 상속받은 abstract 메서드를 재정의 하지 않은 경우: 클래스 내부에 abstract method 가 있는 상황이므로 자식클래스는 abstract 클래스로 선언되어야 한다
왜 abstract class를 사용해야 할까?
abstract class 는 구현의 강제를 통해 프로그램의 안정성을 향상시키며, interface에 있는 메서드 중 구현할 수 있는 메서드를 구현해 개발의 편의를 지원한다.
<3> Interface
인터페이스는 최고 수준의 추상화 단계로, 모든 메서드가 abstract 형태이다 JDK 8에서 default method 와 static method를 추가되어 body를 가지고 있는 형태도 있다.
🧸 static은 항상 메모리에 올라가 있다
형태
클래스와 유사하게 선언한다 맴버 구성:
1. 모든 맴버 변수는 public static final 이며 생략 가능하다
2. 모든 메서드는 public abstract 이며 생략 가능하다
public interface InterfaceName{
public static final int MEMBER1 =1;
int MEMBER2 = 2; //public static final이 자동 생략
public abstract void method(int parameter);
void method2 (int parameter); //public abstract 생략
}
따라서, method overriding 시에 주의해야 할 점은, Method Overriding을 하기 위해선, 부모의 접근제한자보다 넓거나 같아야 하기 때문에 위 코드의 void method2 (int parameter) 가 디폴트 접근 제한자가 아니라 public 접근제한자 인것을 인지해야한다. 오버라이딩의 조건은 상속 페이지에서 확인하도록 하자.
상속
- 클래스와 마찬가지로 인터페이스도 Extends 를 이용해 상속이 가능하다
- 인터페이스는 다중 상속이 가능하다. 이유는 메서드의 구현이 없기 때문에, 클래스의 상속과는 반대로 혼돈을 줄 수 있는 상황이 없다
interface 반죽 {boolean 주물주물(boolean 밀가루); }
interface 속 {boolean 꽉꽉담아(String 속재료); }
interface 빵틀누르기 {void 꾹꾹눌러(int time); }
public interface 붕어빵타이쿤 extends 반죽, 속, 빵틀누르기 {
void 붕어빵만들기();
void 붕어빵팔기();
}
인터페이스 구현과 객체 참조
이렇게 다중 상속 받은 인터페이스를 클래스에서 implements 키워드를 사용하여 interface를 구현할 수 있다. Implements 한 클래스는,
- 모든 abstract methods 를 override 하여 구현하거나
- 구현하지 않을 경우 abstract 클래스로 표시해야 한다
- 여러개의 interface 를 implements 할 수 있다
/*extends 와 implements 동시사용가능*/
public class 팥붕어빵 extends 간식 implements 붕어빵타이쿤{
//인터페이스를 죄다 오버라이드하거나 abstract 클래스로 표시해야한다
String 속재료 = "팥";
boolean 밀가루 = true;
int time = 3;
//붕어빵 타이쿤이 상속한 메서드들을 오버라이딩
@override
public boolean 주물주물(){ if(밀가루){ return true;}
else{System.out.println("반죽할수 없어.."); return false;} }
@override
public boolean 속(String 속재료) { if (속재료 == "붕어") {return false;} return true;}
@override
public void 꾹꾹눌러(int time){ return 30;}
//붕어빵 타이쿤의 메서드 오버라이딩
@override
public void 붕어빵만들기() {if(주물주물() && 속()){ System.out.println("따끈따끈 붕어빵 완성");} }
@override
public void 붕어빵팔기(){}
}
//만약 public void 붕어빵팔기를 오버라이딩 하기 싫다면??: 아예 abstract class 행*/
public abstract class 팥붕어빵 implements 붕어빵타이쿤 {
//인터페이스를 죄다 오버라이드하거나 abstract 클래스로 표시해야한다
String 속재료 = "팥";
boolean 밀가루 = true;
int time = 3;
//붕어빵 타이쿤이 상속한 메서드들을 오버라이딩
@override
public boolean 주물주물(){ if(밀가루){ return true;}
else{System.out.println("반죽할수 없어.."); return false;} }
@override
public boolean 속(String 속재료) { if (속재료 == "붕어") { return false;} return true;}
@override
public void 꾹꾹눌러(int time){ return 30;}
//붕어빵 타이쿤의 메서드 오버라이딩
public void 붕어빵만들기() { if(주물주물() && 속()){ System.out.println("따끈따끈 붕어빵 완성");}}
/*public void 붕어빵팔기(){}*/
}
🤦🏻♀️ Interface
상속 : interface extends interface1, interface 2, interface 3
구현 : class implements interface
인터페이스의 필요성
- 구현의 강제로 표준화 처리 → 손쉬운 모듈 교체를 지원한다 하도 괴식이 유행해서 속재료에 뭐가 들어가는지 확인할 필요가 없어짐 → 그럼 간단하게 속재료 인터페이스만 하나 교체하면 쉽게 유지 보수가 가능하다. 즉, 붕어빵 속에 붕어빵을 넣을 수 있게 표준이 바뀌어도 붕어빵은 붕어빵이여..
- 독립적인 프로그래밍으로 개발 기간을 단축시킬 수 있다.
붕어빵 주인은 트위터에 누가 올때까지 아무것도 안하고 놀다가 손님이 오면 그제서야 반죽을 하고 붕어빵을 튀겨줘야 하는가? 또한, 손님은 새벽 7시에 포장마차가 열자마자 붕어빵을 당장 달라며 주인을 닦달해야 하는가? 이때, 주인과 손님은 서로 합의점을 찾아야 한다. 즉, 반죽 인터페이스를 생성하여 미리 반죽을 해놓고 손님이 왔을때 구울 수 있도록 협의하는 것이다. - 서로 상속의 관계가 없는 클래스들에게 인터페이스를 통한 관계 부여로 다형성 확장
위 그림에서, 붕어빵과 생과일 주스는 안에 들어가는 재료도, 만드는 방법도 다르다. 하지만 둘 다 타이쿤이라는 기능을 가지고 있다. 하지만, 자바는 단일 상속만을 지원하기 때문에, 더이상 상속을 할 수 없다.
인터페이스는 이렇게 서로 상속 관계가 없는 클래스들에게 관계를 부여해 줄 수 있다. 장사 인터페이스를 이용하여, 이미 빵을 상속한 붕어빵과, 음료수를 상속한 생과일 주스 사이에 타이쿤이란 관계를 맺어줄 수 있게 된다. 이는 코드의 재사용률을 높이고 효율적인 관리를 가능하게 해준다.
아래 코드는 위의 관계도를 코드로 작성한 것이다.
public interface Sellable {void 타이쿤();}
public 붕어빵 extends 빵 implements Sellable {
@override
public void 타이쿤(){
System.out.println("따끈따끈 붕어빵사세용");}}
public 생과일주스 extends 음료수 implements Sellable{
@override
public void 타이쿤(){
System.out.println("시원한 생과일주스사세용");}
public 동생 extends 가족{
public void 내동생(){
System.out.println("내동생 짱귀엽지? 가질래?");}}
public class RelationShipTest{
public static void main (String[] args){
Object[] obj = {new 붕어빵(), new 생과일주스(), new 동생()};
for( Object x : obj) {
//이 오브젝트가 Sellable 인터페이스의 인스턴스 인가요?
if(x instanceof Sellable)
Sellable sell = (Sellable) x; //그럼 이 오브젝트는 Sellable 인거야
x.타이쿤(); //Sellable 이니까 팔아버리자
}
}
같은 인터페이스를 implements 하고 잇는 클래스들은 "Sellable" 이란 관계를 나누었기 때문에, 팔 수 있다. 즉, 붕어빵과 생과일 주스는 각각 빵과 음료수를 상속하고 있지만 같은 판매 인터페이스를 구현하고 있기 때문에 둘 다 타이쿤 메서드를 사용할 수 있다. 하지만, 동생은 판매 인터페이스를 구현하고 있지 않기 때문에 팔아버릴수 없다
Default Method
Default Method 란 인터페이스에 선언된 구현부가 있는 일반 메서드를 뜻한다.
1. 메서드 선언부에 default modifier 추가 후 메서드 구현부 작성
interface 속재료{
void 꽉꽉담아();
default void 재료상태확인(){
System.out.println("이 메서드는 기본 메서드 입니다");
System.out.println("일단 속재료가 있으면 상태부터 확인하고 시작합시다");}
}
2.필요성
(1) 기존에 interface 기반으로 동작하는 라이브러리의 interface 에 추가해야하는 기능이 발생 abstract 형태로 기능을 추가하면, 기존 메서드들을 하나하나 다 열어서 오버라이딩을 추가해야한다 → 와 정말 불편...
(2) 기존 방식으로라면 모든 메서드를 override 해야한데 디폴트 메서드는 abstract 가 아니므로 반드시 구현해야 할 필요가 없다!
(3) 디폴트 메서드와 인터페이스 이름의 충돌 발생 가능 :
a. 인터페이스 ↔ 클래스: 클래스가 우선권을 갖는다
b. 인터페이스1 ↔ 인터페이스2: 구현하는 클래스에서 반드시 오버라이드를 해야한다
'취준 > JAVA' 카테고리의 다른 글
다형성 (0) | 2022.05.17 |
---|---|
상속 (0) | 2022.05.17 |
객체 지향 프로그래밍 (0) | 2022.05.17 |
Class Case Exception (0) | 2022.05.17 |
JAVA Basics (0) | 2022.05.12 |