abstract, interface
abstract class - 추상 클래스
추상 클래스는 미완성 클래스이며 상속을 통해 완성해주어야 한다.
추상 클래스는 추상메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 다른게 없다.
abstract method - 추상 메서드
추상 메서드는는 메서드의 선언부만 작성해서 사용하는 것을 추상 메서드라고 한다.
abstract를 붙혀주는 이유는 자손 클래스에서 추상메서드의 상속을 강요하기 위해서이다.
Interface
인터페이스 또한 추상클래스와 비슷하다 인터페이스는 추상화클래스 처럼 추상메서드를 같이만 abstract보다 추상화 정도가 높기 때문에 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.
상수와 추상메서드만을 구성원으로 가지고 있을 수 있다.
interface 인터페이스이름{
}
이런 식으로 작성
모든 멤버 변수는 public static final(상수)이여만 하며 이것을 생략 가능하다.
모든 메소드는 public abstract이여만 하며 이것을 생략 가능하다.
Interface와 상속과 구현
인터페이스는 클래스의 상속과 다르게 다중상속을 지원한다. 이때 다중 상속은 클래스가 아닌 인터페이스만을 허용하는 것이다.
인터페이스의 구현은 클래스를 확장한다는 의미의 extends가 아닌 implements 구현을 사용한다.
class FighterTest {
public static void main(String[] args) {
Fighter f = new Fighter();
if (f instanceof Unit) {
System.out.println("f는 Unit클래스의 자손입니다.");
}
if (f instanceof Fightable) {
System.out.println("f는 Fightable인터페이스를 구현했습니다.");
}
if (f instanceof Movable) {
System.out.println("f는 Movable인터페이스를 구현했습니다.");
}
if (f instanceof Attackable) {
System.out.println("f는 Attackable인터페이스를 구현했습니다.");
}
if (f instanceof Object) {
System.out.println("f는 Object클래스의 자손입니다.");
}
}
}
class Fighter extends Unit implements Fightable { // 이런식으로 사용하면 구현과 상속을 모두 사용가능
public void move(int x, int y) { /* 내용 생략 */ }
public void attack(Unit u) { /* 내용 생략 */ }
}
class Unit {
int currentHP; // 유닛의 체력
int x; // 유닛의 위치(x좌표)
int y; // 유닛의 위치(y좌표)
}
interface Fightable extends Movable, Attackable { }
interface Movable { void move(int x, int y); }
interface Attackable { void attack(Unit u); }
인터페이스와 다형성
우리는 전에 다형성에 대해 배웠을때 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다. 했었다. 아래 예시를 보자
Fightable fightable = (Fightable) new Fighter();
Fightable fightable = new Fighter();
Fighter 객체는 Fightable을 구현하고 있다. Fightable을 구현한 Figher객체는 자식이라고 볼 수 있다.
그럼으로 참조변수를 인스턴스로 참조할 수 있다.
이런식으로 인터페이스를 매개변수 타입으로 사용할 수 있다.
class Fighter extends Unit implements Fightable { // 이런식으로 사용하면 구현과 상속을 모두 사용가능
public void move(int x, int y) { /* 내용 생략 */ }
public void attack(Fightable f) { /* 내용 생략 */ }
}
인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다. 위의 코드를 보면 Fightable f == (new Fighter)와 같다
리턴타입의 인터페이스는 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것이다. 결국 (return new 구현 클래스 이름) 과 같다.
interface Parseable {
public abstract void parse(String fileName); //public void parse(String fileName); 생략가능
}
class ParserManager {
// 리턴타입이 Parseable인터페이스이다. => 구현 클래스의 인스턴스를 반환해야 한다.
public static Parseable getParser(String type) {
if(type.equals("XML")) {
return new XMLParser(); //XMLParser implements Parseable임으로 구현클래스
} else {
Parseable p = new HTMLParser(); // Parseable과 HtmlParser는 자식과 부모의 관계임으로 형변환 가능
return p; //이또한 구현클래스이다.
}
}
}
class XMLParser implements Parseable {
public void parse(String fileName) {
System.out.println(fileName + "- XML parsing completed.");
}
}
class HTMLParser implements Parseable {
public void parse(String fileName) {
System.out.println(fileName + "-HTML parsing completed.");
}
}
class ParserTest {
public static void main(String args[]) {
Parseable parser = ParserManager.getParser("XML");
parser.parse("document.xml");
parser = ParserManager.getParser("HTML");
}
}
ex)
class RepairableTest{
public static void main(String[] args) {
//유닛 생성
Tank tank = new Tank();
Dropship dropship = new Dropship();
Marine marine = new Marine();
SCV scv = new SCV();
scv.repair(tank); // SCV가 Tank를 수리하도록 한다.
scv.repair(dropship);
// scv.repair(marine);
}
}
interface Repairable {}// 수리가능한가 아닌가의 판별시 사용
class GroundUnit extends Unit { // 지상유닛은 unit class를 상속받는다.
GroundUnit(int hp) {
super(hp); // 부모클래스의 생성자 이용
}
}
class AirUnit extends Unit {// 공중유닛은 unit class를 상속받는다.
AirUnit(int hp) {
super(hp);// 부모클래스의 생성자 이용
}
}
class Unit {
int hitPoint;
final int MAX_HP;
Unit(int hp) {
MAX_HP = hp;
}
//...
}
class Tank extends GroundUnit implements Repairable {
Tank() {
super(150); //부모인 GroundUnit -> 조부모인 Unit까지 hp는 150설정이 됨
hitPoint = MAX_HP; // 조부모의 멤버면수
}
public String toString() {
return "Tank";
}
//...
}
class Dropship extends AirUnit implements Repairable {
Dropship() {
super(125); // Tank와 동일
hitPoint = MAX_HP;
}
public String toString() {
return "Dropship";
}
//...
}
class Marine extends GroundUnit {
Marine() {
super(40);
hitPoint = MAX_HP;
}
//...
}
class SCV extends GroundUnit implements Repairable{
SCV() {
super(60);
hitPoint = MAX_HP;
}
void repair(Repairable r) { //r은 Repairable의 구현체여야 한다. 즉 scv, dropship, tank만 가능하다.
if (r instanceof Unit) {
Unit u = (Unit)r; // 매개변수로 받아온 unit을 형변환
while(u.hitPoint!=u.MAX_HP) {
/* Unit의 HP를 증가시킨다. */
u.hitPoint++;
}
System.out.println( u.toString() + "의 수리가 끝났습니다.");
}
}
//...
}
인터페이스의 이해
-클래스를 사용하는 쪽과 클래스를 제공하는 쪽이 있다.
-메서드 사용하는 쪽에서는 사용하려는 메서드의 선언부만 알면된다.
두가지 사항을 생각해보자!
class A {
public void methodA(B b) { // 사용하는 쪽
b.methodB();
}
}
class B {
public void methodB() {// 제공하는 쪽
System.out.println("methodB()");
}
}
class InterfaceTest {
public static void main(String args[]) {
A a = new A();
a.methodA(new B());
}
}
클래스 A를 보면 B의 인스턴스를 생성하고 사용한다. 위를 보면 A를 작성하기 위해서는 B가 작성되어야 한다. 그리고 B의 선언부가 변경된다면 이를 사용하는 A도 변경되어야 한다. 이렇게 보면 A와 B는 직접적으로 관련이 되어 있다. 이것을 인터페이스를 사용하면 B의 변경이 생겨도 A에는 영향이 가지 않게 구현할 수 있다.
interface I{
public void method();
}
class A {
public void methodA(I i) { // 사용하는 쪽
i.method();
}
}
class B implements I{
public void method() {// 제공하는 쪽
System.out.println("methodB()");
}
}
class InterfaceTest {
public static void main(String args[]) {
A a = new A();
a.methodA(new B());
}
}
이렇게 하면 A-B의 직접적인 관계에서 A-I-B 이렇게 간접적인 관계로 변경되었다. 살펴보면 A는 이제 B의 내용을 모르고 B를 사용할 수 있게 되었다. 또한 클래스의 영향을 받지 않게 된다.