Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

forDevLife

[자바의정석] Chap 7. 객체지향 프로그래밍4 본문

Java

[자바의정석] Chap 7. 객체지향 프로그래밍4

JH_Lucid 2020. 12. 28. 17:02

16.1 ) 추상 클래스

추상화와 구체화를 다음과 같이 정의하면 이해하기가 쉽다.

 

1) 추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업

2) 구체화 : 상속을 통해 클래스를 구현, 확장하는 작업

 

즉, 상속계층도를 따라 내려 갈수록 세분화되며, 올라갈 수록 공통요소만 남게된다.

 

코드는 없지만, 해당 내용을 요약하자면

1) 추상클래스로부터 상속받은 자손클래스는 오버라이딩을 통해 조상의 추상메서드를 모두 구현해줘야 한다.

2) 일부만 구현될 경우, 자손 클래스 역시 추상 클래스로 지정되어야 한다.

3) 자손 클래스에게 추상 메서드의 구현을 반드시 하도록 강요하기 위해 abstract, 즉 추상 메서드를 사용한다.

4) 복습으로, 메서드는 참조변수의 타입과 관계없이 실제 인스턴스의 메서드가 호출된다.

5) 하지만 멤버변수는 참조변수(class)의 것을 따라간다.

6) 추상 클래스는 그 자체로 인스턴스 생성 불가하다.

 

7.1) 인터페이스

인터페이스는 일종의 추상클래스이다. 하지만 추상클래스에 비해서 추상화정도가 높아, 오직 추상 메서드와 상수만을 멤버로 가질 수 있다.

 

인터페이스의 작성 방법은 아래와 같다.

interface 인터페이스 이름 {
	public static final 타입 상수이름 = 값;
    	public abstract 메서드이름(매개변수 목록);
}

일반적인 클래스의 멤버들과는 달리, 인터페이스의 멤버들은 다음의 제약사항을 가진다.

1) 모든 멤버변수는 public static final 이어야 하며, 이는 생략 가능하다.

2) 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. 단 JDK1.8 부터 static, default 메서드는 예외이다.(추가 가능)

 

생략 시 컴파일러가 자동으로 추가해준다.

 

특징을 요약하면, 아래와 같다.

1) 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속이 가능하다.(by extends keyword)

2) 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하며, 이는 구현 한다는 keyword인 'implements' 를 사용한다.

3) 만일 구현하는 인터페이스 메서드 중 일부만 구현한다면, abstract를 붙여서 추상클래스로 선언해야 한다.(아래 예시)

abstract class Fighter implements Fightable {
	public void move(int x, int y) { ... }
   	 //public void attack(Unit u) 이 부분은 구현 안할 경우
}

4) 상속과 구현을 동시에 할 수 있다. 아래는 Unit을 상속하며 Fightable을 구현하는 Fighter 클래스이다.

class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { ... }
    	public void attack(Unit u) { ... }
}

 

아래는 상속과 구현을 동시에 한 예문이다. 인터페이스도 다른의미로의 구현으로 볼 수 있어, instanceof 실행 시 true를 반환한다.

Fighter 내에서 메서드에 public 명시가 필요한데, 오버라이딩 시에는 조상의 메서드(인터페이스의 메서드 : public)보다 넓은 범위의 접근 제어자를 사용해야 하므로, public 말고는 쓸 수 없다. 기본 접근 범위는 default이므로 public 명시가 꼭 필요하다.

public class FighterTest {

	public static void main(String[] args) {
		Fighter f = new Fighter();
		
		if(f instanceof Unit) 
			System.out.println("f is unit's child");
		if(f instanceof Fightable)
			System.out.println("f is fightable's child");
		if(f instanceof Movable)
			System.out.println("f is Movable's child");
		if(f instanceof Attackable)
			System.out.println("f is Attackable's child");
	
	}

}
class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { } //기본은 default 이므로, public을 명시해 줘야한다. 
	public void attack(Unit u) { } // 마찬가지로 public 명시.
}

class Unit {
	int currentHP; // unit의 체력
	int x;
	int y;
}
interface Fightable extends Movable, Attackable { }
interface Movable { void move(int x, int y); } //public abstract가 생략되어 있다. 
interface Attackable { void attack(Unit u); } // public abstract가 생략되어 있다.

unit은 상속, 나머지는 구현이므로 모든게 출력된다.

 

두 클래스로부터 상속을 못 받으므로 아래와 같이 한 쪽만 선택하여 상속받고, 나머지 한 쪽은 클래스 내에 포함시켜 내부적으로 인스턴스를 생성해서 사용한다.

아래 예시는 Tv기능을 상속받고, VCR을 클래스 내부로 포함시켜 두 기능 동작을 포함하는 TVCR 클래스이다. 이 때 VCR 클래스 타입의 참조변수를 멤버 변수로 선언하여 ICVR 인터페이스의 추상 메서드를 구현하는데 사용한다.

 

이때, 굳이 인터페이스를 작성하지 않고 VCR 을 TVCR에 포함 시키는 것으로도 충분하지만, 인터페이스를 이용하면 다형적 특성?을 이용할 수 있다는 장점이 있다. 다음 단원에서 배워보자.

package test_java;

public class Multiple_extends_example {

	public static void main(String[] args) {
		TVCR tv = new TVCR();
		
		if(tv instanceof IVCR)
			System.out.println("tv is family if IVCR");
		
		tv.power();
		System.out.println(tv.power);
		
		tv.power();
		System.out.println(tv.power);
	
		tv.vcr.play();
		tv.vcr.setCounter(5);
		System.out.println(tv.vcr.getCounter());
	}

}
class Tv1 {
	protected boolean power;
	protected int channel;
	protected int volume;
	
	public void power() { power = ! power; }
	public void channelUp() { channel++; }
	public void channelDown() { channel--; }
	public void volumeUp() { volume++; }
	public void volumeDown() { volume--; }
}

class VCR {
	protected int counter; //VCR의 카운터
	
	public void play() { System.out.println("play vcr");}// Tape 재생
	public void stop() { System.out.println("stop vcr");}// tape stop
	public void reset() { System.out.println("reset vcr");}
	public int getCounter() {
		return counter;
	}
	public void setCounter(int c) {
		counter = c;
	}
}

interface IVCR {
	public void play();
	public void stop();
	public void reset();
	public int getCounter();
	public void setCounter(int c);
}

class TVCR extends Tv1 implements IVCR {
	VCR vcr = new VCR();
	
	public void play() {
		vcr.play();
	}
	public void stop() {
		vcr.stop();
	}
	public void reset() {
		vcr.reset();
	}
	public int getCounter() {
		return vcr.getCounter();
	}
	public void setCounter(int c) {
		vcr.setCounter(c);
	}
}

 

7.2) 인터페이스를 이용한 다형성

다형성에 대해 학습할 때, 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다고 배웠다.

1) 인터페이스도 마찬가지로, 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로 형 변환도 가능하다.

2) 또한 인터페이스는 아래와 같이 메서드의 매개변수 타입으로 사용될 수 있다.

void attack(Fightable f) { ... }

위의 의미는, 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다는 의미이다.

만약 Fighter라는 클래스가 Fightable이라는 인터페이스를 구현하고 있다면, attack의 매개변수로 Fighter의 인스턴스를 넘겨줄 수 있다는 의미이다. attack(new Fighter())와 같은 방법으로도 작성이 가능하다.

 

3) 메서드의 리턴 타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.

Fightable method() {
	...
    Fighter f = new Fighter();
    return f;
    //return new Fighter();로 작성해도 됨
}

리턴타입이 인터페이스라는 것은, 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 의미이다. 이 문장은 외우자.

 

아래의 예시에서, Parseble 인터페이스 타입을 반환하는 getParser 부분을 보자.

조건에 따라 Parseable 인터페이스를 구현한 XMLParser 또는 HTMLParser를 반환하는 것을 볼 수 있다.

리턴된 XMLParser(또는 HTMLParser) 인스턴스의 주소는 메인의 Parseable 인터페이스의 참조변수 parser로 넘겨지게 되며 이 참조변수를 통해 각 클래스의 메서드가 각각 수행된다.

 

나중에 새로운 종류의 XML 구문분석기인 newXML클래스가 나와도, ParserTest클래스는 변경할 필요 없이 getParcer메서드에서 리턴타입만 변경해주면 된다.

이 장점은 특히 분산환경 프로그래밍에서 위력을 발휘하는데, 서버측의 변경만으로 사용자가 새로운 프로그램을 사용하는게 가능해진다.

package test_java;

interface Parseable {
	//구문 분석 작업을 수행
	public abstract void parse(String fileName);
}

class ParserManager {
	//리턴타입이 parseable 인터페이스이다.
	public static Parseable getParser(String type) {
		if(type.equals("XML")) {
			return new XMLParser();
		} else {
			Parseable p = new HTMLParser();
			return p;
			// return new HTMLParser();
		}
	}
}
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.");
	}
}

public class ParserTest {

	public static void main(String[] args) {
		Parseable parser = ParserManager.getParser("XML");
		parser.parse("document.xml"); // XMLParser의 parse 메서드 실행
		parser = ParserManager.getParser("HTML"); 
		parser.parse("document2.html"); // HTMLParser의 parse 메서드 실행
	}
}

 

7.3) 인터페이스의 장점

 

인터페이스의 장점으로는, 관계 없는 클래스 끼리 관계를 맺어줄 수 있다.

아래에서, Tank와 Dropship은 Unit을 모체로 둘 뿐 관계가 없다. 하지만 SCV에 의해 수리된다는 공통점을 부여하고 싶다면, 아래와 같이 Repairable라는 인터페이스를 구현하도록 하면 해당 인터페이스의 참조변수로 각 Tank와 Dropship을 받아서 공통되는 작업을 처리할 수 있다. 이 때 Repairable 인터페이스에는 아무것도 구현되어 있지 않으므로, 형 변환(캐스팅)을 통해 unit의 기능을 부여한다.

 

marine의 경우 Repairable 인터페이스를 구현하지 않으므로, 수리 부분에서 에러가 발생한다.

package test_java;

public 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.repair(dropship);
		// scv.repair(marine); 이 부분은 에러 발생
	}

}
interface Repairable {} // 아무것도 구현 안함

class Unit {
	int hitPoint;
	final int MAX_HP;
	Unit(int hp) {
		MAX_HP = hp;
	}
}

class GroundUnit extends Unit {
	GroundUnit(int hp) {
		super(hp);
	}
}

class AirUnit extends Unit {
	AirUnit(int hp) {
		super(hp);
	}
}

class Tank extends GroundUnit implements Repairable {
	Tank() {
		super(150);
		hitPoint = MAX_HP;
	}
	public String toString() {
		return "Tank";
	}
}
class Dropship extends AirUnit implements Repairable {
	Dropship() {
		super(125);
		hitPoint = MAX_HP;
	}
	public String toString() {
		return "Dropship";
	}
}
class Marine extends GroundUnit {
	Marine() {
		super(40);
		hitPoint = MAX_HP;
	}
	public String toString() {
		return "Marine";
	}
}
class SCV extends GroundUnit implements Repairable {
	SCV() {
		super(60);
		hitPoint = MAX_HP;
	}
	void repair(Repairable r) {
		if (r instanceof Unit) {
			Unit u = (Unit)r;
			while(u.hitPoint != u.MAX_HP) {
				u.hitPoint++;
			}
			System.out.println(u.toString()+"의 수리가 끝났습니다.");
		}
	}
}

 

7.4) 디폴트 메서드와 static 메서드

 

1) 디폴트 메서드 : 말 그대로 일반 클래스처럼 메서드를 만들어 줄 수 있다. 여기에서 default는 범위지정과 다르며, 범위 지정의 경우 아무것도 안써주는 것이므로 차이가 있다.

이름의 중복이 중요한데,

 

- 여러 인터페이스의 디폴트 메서드끼리 충돌할 경우, 인터페이스를 구현한 클래스에서 해당 메서드를 오버라이딩 한다. 이렇게 될 경우 인터페이스에 구현된 메서드는 당근 의미가 없어진다.

 

- 디폴트 메서드와 조상 클래스 메서드 간의 충돌일 경우, 무조건 조상 클래스의 메서드를 따라간다. 클래스가 더 강하다!

 

- 아래 child 에서 super를 통해 method1, 2를 오버라이딩 하였다. 만약 아래와 같이 method1, 2를 오버라이딩 하지 않으면, 컴파일러 입장에서 어느 인터페이스의 method1, 2를 부를 지 모르므로 오류가 발생한다. 인터페이스 둘 다 해당 메서드가 정의되어있기 때문이다.

따라서 필요한 쪽의 메서드를 명시적으로 호출해준다.(super 이용)

* super는 자손에서 조상의 멤버를 참조하는데 사용되는 참조 변수이며, super()는 조상 클래스의 생성자이다. 

+ 근데 여기에서는, 아래와 같은 설명이 있다.

걍 외우자

https://joswlv.github.io/2018/10/25/javadefaultmethod/ 참고

 

2) static 메서드 : 클래스와 동일하게, interface name으로 각자의 메서드를 호출할 수 있다.

 

public class DefaultMethodTest {

	public static void main(String[] args) {
		Child c = new Child();
		c.method1();
		c.method2(); // 조상이 상속되며, 디폴트 메서드는 무시
		MyInterface.staticMethod();
		MyInterface2.staticMethod();
	}

}
class Child extends Parent implements MyInterface, MyInterface2 {
	public void method1() {
		MyInterface.super.method1();
	}
	public void method2() {
		MyInterface2.super.method2();
	}
}
class Parent {
	
}
interface MyInterface {
	default void method1() {
		System.out.println("method1() in MyInterface");
	}
	default void method2() {
		System.out.println("method2() in MyInterface");
	}
	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface");
	}
}
interface MyInterface2 {
	default void method1() {
		System.out.println("method1() in MyInterface2");
	}
	default void method2() {
		System.out.println("method2() in MyInterface2");
	}
	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface2");
	}
}
Comments