객체지향 프로그래밍 II (Object - oriented Programming II)

1. 상속(inheritance)

1.1 상속의 정의와 장점

상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
상속을 통해서 관리하면 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다.

class Child extends parents {
	// ...
}

이 두 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상 클래스'
상속 받는 클래스를 '자손 클래스'라고 한다.

조상 클래스 : 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스
자손 클래스 : 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스

  • 자손 클래스는 조상 클래스의 모든 멤버를 상속 받는다.
  • 생성자와 초기화 블럭은 상속되지 않는다. only 멤버만 상속
  • 자손 클래스는 조상클래스의 모든 멤버를 상속 받으므로 항상 조상 클래스보다 같거나 더 많은 멤버를 갖는다.

클래스 간의 관계에서 형제관계와 같은 것은 없다.
조상 클래스를 변경하면 모든 자손 클래스에, 자손의 자손클래스에까지 영향을 미치기 때문에, 클래스간의
상속 관계를 맺어주면 자손 클래스들의 공통적인 부분은 조상클래스에서 관리하고, 자손클래스는 자신에 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져서 관리가 용이해진다.

전체 프로그램을 구성하는 클래스들을 면밀히 설계 분석하여 클래스간의 상속관계를 적절히 맺어 주는 것이 객체지향 프로그래밍에서 가장 중요한 부분이다.

자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.

1.2 클래스간의 관계 - 포함관계

상속 이외에도 클래스를 재사용 하는 또 다른 방법
클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것이다.

class Circle {
	int x;	//원점의 x좌표
    int y;  //원점의 y좌표
    int r;	//반지름(radius)
}

class Point {
	int x;  //x좌표
    int y;  //y좌표
}

Point클래스를 재사용해서 Circle클래스를 작성한다면 다음과 같다.

class Circle {
	Point c = new Point(); //원점
    int r;
}

한 클래스를 작성하는 데 다른 클래스를 멤버변수로 선언하여 포함시키는 것은 좋은 생각이다.
하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있다.

예시 : Car클래스를 작성할 때, Car클래스의 단위구성요소인 Engine, Door와 같은 클래스를 미리 작성해 놓고 이 들을 Car클 클래스의 멤버 변수로 선언 (포함관계)

calss Car {
	Engine e = new Engine();	//엔진
    Door[] d = new Door[4];		//문, 문의 개수를 네승로 가정하고 배열처리 

1.3 클래스간의 관계 결정하기

클래스를 작성하는데 있어서 상속관계를 맺어 줄 것인지, 포함관계를 맺을 것인지 혼동스러울 땐
'~은 ~이다(is - a)'와 '~은 ~을 가지고 잇다(has - a)'을 넣어서 문장을 만들어보면
클래스 간의 관계가 보다 명확해진다.

원(Circle)은 점(Point)이다. - Circle is a Porint. -> 상속
원(Circle)은 점(Point)을 가지고 있다. - Circle has a Poinr. -> 포함관계
매번 딱딱 떨어지는건 아니지만 적어도 클래스 간의 관계를 맺어주는데 가장 기본적인 원칙에 대한 감은 잡을 수 있다.

조상 클래스에 정의된 메서드와 같은 메서드를 자손 클래스에 정의하는 것을 '오버라이딩'이라고 한다.

1.4 단일상속(single inheritance)

자바에서는 오직 단일 상속만 허용한다.

1.5 Object클래스 - 모든 클래스의 조상

Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다. 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받게 함으로써 이것을 가능하게 한다.

2. 오버라이딩(overriding)

2.1 오버라이딩이란?

조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다.

class Point {
	int x;
    int y;
    
    String getLocation() {
    	retirn "x :" + x + ", y :" + y;
    }
}

class Point3D extends Potin P
	int z;
    
    String getLocation() {
    	return "x :" + x + ", y : "+ y, z :" + z;
        }
}

이 메서드는 Point3D클래스 자신에 맞게 z축의 좌표값도 포함하여 반환하도록 오버라이딩 하였다.

2.2 오버라이딩의 조건

자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와

  • 이름이 같아야 한다.
  • 매개변수가 같아야 한다.
  • 반환타입이 같아야 한다.
    한마디로 요약하면 선언부가 서로 일치해야 한다는 것이고 다만 접근 제어자(access modifier)와 예외(exception)는 제한된 조건 하에서만 다르게 변경할 수 있다.
    1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
      만일 조상 클래스에 정의된 메서드의 접근 제어자가 protectedf라면, 이를 오버라이딩하는 자손 클래스의 메서드는 접근 제어자가 protected나 public이어야 한다. 대부분의 경우 같은 범위의 접근 제어자를 사용한다. 접근 제어자의 접근범위를 넓은 것에서 좁은 것 순으로 나열하면 public, protected, (default), privated이다.
    1. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

조상 클래스의 메서드를 자손 클래스에서 오버라이딩할 때

    1. 접근제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
    1. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
    1. 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.

2.3 오버로딩 vs. 오버라이딩

오버로딩(overloading) : 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change, modify)

class Parent {
	void parentMethod() {}
}

class Child extends Parent {
	void parentMethod() {}			//오버라이딩
    void parentMethod(int i) {}		//오버로딩
    
    void childMethod() {}
    void childMethod(int i) {}		//오버로딩
}    
    

2.4 super

super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조 변수이다.
멤버변수와 지역번수의 이름이 같을때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 멤버와 이름이 같을 때는 super를 붙여서 구별할 수 있다.
모든 인스턴스메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 this와 super의 값이 된다.

class Point {
	int x;
   int y;
   
   String getLocation() {
   	retirn "x :" + x + ", y :" + y;
   }
}

class Point3D extends Potin P
	int z;
   
   String getLocation() {
   	return "x :" + x + ", y : "+ y, z :" + z;
       return super.getLocation() + ", z :" + z; //조상의 메서드 호출
       }
}

getLocation()을 오버라이딩할 때 조상 클래스의 getLocation()을 호출하는 코드를 포함시켰다.
조상클래스의 메서드의 내용에 추가적으로 작업을 덧붙이는 경우라면 이처럼 super를 사용해서 조상클래스의 메서드를 포함시키는 것이 좋다.

2.5 super() - 조상 클래스의 생성자

this()와 마찬가지로 super() 역시 생성자다. this()는 같은 클래스의 다른 생성자를 호출하는데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용된다.

Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this() 또는 super(),를 호출해야 한다. 그렇지 않으면 컴파일러가 자동적으로 'super();'를 생성자의 첫줄에 삽입한다.

인스턴스를 생성할 때는 클래스를 선택하는 것만큼 생성자를 선택하는 것도 중요하다.

    1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
    1. 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가?

3. package와 import

3.1 패키지(package)

패기지란, 클래스의 묶음이다. 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있다.

  • 하나의 소스파일에는 첫번쨰 문장으로 단 한 번의 패키지 선언만을 허용한다.
  • 모든 클래스는 반드시 하나의 패키지에 속해야 한다.
  • 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
  • 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.

3.2 패키지의 선언

클래스나 인터페이스의 소스파일(.java)의 맨 위에 다음과 같이 한 줄 적어주면 된다.
package 패키지명;

3.3 import문

다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야 한다.
클래스의 코드를 작성하기 전에 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스 이름에서 패키지명은 생략할 수 있다.

import문의 역할은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하는 것이다.

3.4 import문의 선언

소스파일(*.java)의 구성은 다음의 순서로 되어 있다.

    1. package문
    1. import문
    1. 클래스 선언

import문을 선언하는 방법

  • import 패키지명.클래스명;
  • import 패키지명.*;

클래스 이름을 지정해주는 대신 *을 사용하면, 지정된 패키지에 속하는 모든 클래스를 패키지명 없이 사용할 수 있다.

3.5 static import문

import문을 사용하면 클래스의 패키지명을 생략할 수 있는 것과 같이 static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다.

import static java.lang.Integer.*;		//Integer클래스의 모든 static 메서드
import static java.lang.Math.random;	//Math.random()만. 괄호 안붙임 
import static java.lang.System.out;		//System.out을 out만으로 참조 가능

System.out.println(Math.random());  <------>  out.println(random());

4. 제어자(modifier)

4.1 제어자란?

제어자(modifier)는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다.
제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.

접근 제어자 : public,protected,default,private
그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

4.2 static - 클래스의, 공통적인

static은 '클래스의' 또는 '공통적인'의 의미를 가지고 있다.
인스턴스메서드와 static메서드의 근본적인 차이는 메서드 내에서 인스턴스 멤버를 사용하는가의 여부에 있다.

static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화 블럭

제어자 : static
대상 : 멤버변수

  • 모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다.
  • 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능하다.
  • 클래스가 메모리에 로드될 때 생성된다.

제어자 : static
대상 : 메서드

  • 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다.
  • static메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다.

인스턴스 멤버를 사용하지 않는 메서드는 static을 붙여서 static메서드로 선언하는 것을 고려해보도록 하자. 가능하다면 static메서드로 하는 것이 인스턴스를 생성하지 않고도 호출이 가능해서 더 편리하고 속도도 더 빠르다.

4.3 final - 마지막의 변경될 수 없는

변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손클래스를 정의하지 못하게 된다.

final이 사용될 수 있는 곳 - 클래스, 메서드, 멤버변수, 지역변수

제어자 : final
대상 : 클래스

  • 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다.
  • final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.

제어자 : final
대상 : 메서드

  • 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
  • final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.

제어자 : final
대상 : 멤버변수, 지역변수

  • 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.

대표적인 final클래스로는 String과 Math가 있다.

생성자를 이용한 final멤버 변수의 초기화
final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스변수의 경우 생성자에서 초기화 되도록 할 수 있다.
클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것이다.

이 기능을 활용하면 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 하는 것이 가능하다.
만일 이것이 불가능하다면 클래스에 선언된 final이 붙은 인스턴스변수는 모든 인스턴스에서 같은 값을 가져야만 할것이다.

4.4 abstract - 추상의, 미완성의

메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.
abstract가 사용될 수 있는 곳 - 클래스, 메서드

제어자 : abstract
대상 : 클래스

  • 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.

제어자 : abstract
대상 : 메서드

  • 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 없다.

abstract class AbstractTest {		//추상 클래스(추상 메서드를 포함한 클래스)
	abstract void move();			//추상 메서드(구현부가 없는 메서드)
}

꽤 드물지만 추상 메서드가 없는 클래스, 즉 완성된 클래스도 abstract를 붙여서 추상 클래스로 만드는 경우도 있다. ex) java.awt.WindowAdapter는 아래와 같이 아무런 내용이 없는 메서드들만 정의되어 있다.
이런 클래스는 인스턴스를 생성해봐야 할 수 있는 것이 아무것도 없다. 그래서 인스턴스를 생성하지 못하게 클래스 앞에 제어자 'abstract'를 붙여 놓은 것이다.

이 클래스 자체로는 쓸모 없지만, 다른 클래스가 이 클래스를 상속 받아서 일부의 원하는 메서드만 오버라이딩 해도 된다는 장점이 있다.

4.5 접근 제어자(access modifier)

접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다. 접근 제어자가 default임을 알리기 위해 실제로 default를 붙이지 않는다. 클래스나 멤버변수, 메서드, 생성자에 접근 제어자가 지정되어 있지 않다면 접근 제어자가 default임을 뜻한다.

접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메서드, 생성자
private : 같은 클래스 내에서만 접근이 가능하다.
default : 같은 패키지 내에서만 접근이 가능하다.
protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.
public : 접근 제한이 전혀 없다.

public > protected > (default) > private
public은 접근 제한이 전혀 없는 것이고, private은 같은 클래스 내에서만 사용하도록 제한하는 가장 높은 제한이다. default는 같은 패키지내의 클래스에서만 접근이 가능하도록 하는 것이다.
접근 제어자가 default라는 것은 아무런 접근 제어자도 붙이지 않는 것을 의미한다.

접근제어자를 이용한 캡슐화

  • 외부로부터 데이터를 보호하기 위해서
  • 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서

접근제어자를 선택해서 접근 범위를 최소화하도록 노력하자.

public class Time {
	private int hour;
    private int minute;
    private int second;
    
    public int getHour() {
    	return hour;
    }
    public void setHour(int hour) {
    	if(hour < 0 || hour > 23) {
    		return;
        }
    this.hour = hour;
	}
    
    public int getMinute() {
  		return minute;
    }
    public void setMinute(int minute) {
    	if(minute < 0 || minute > 59) {
        	return;
        }
    this.minute = minute;
	}
    
     public int getSecond() {
  		return second;
    }
    public void setSecond(int second) {
    	if(second < 0 || second > 59) {
        	return;
        }
    this.second = second;
	}
}

get으로 시작하는 메서드는 단순히 멤버변수의 값을 반환하는 일을 하고, set으로 시작하는 메서드는 매개변수에 지정된 값을 검사하여 조건에 맞는 값일 때만 멤버변수의 값을 변경하도록 작성하였다.
만일 상속을 통해 확장될 것이 예상되는 클래스라면 멤버에 접근 제한을 주되 자손 클래스에서 접근하는 것이 가능하도록 하기 위해 private 대신 protected를 사용한다.

멤버변수의 값을 읽는 메서드의 이름을 'get멤버변수 이름'
멤버변수의 값을 변경하는 메서드의 이름을 'set멤버변수 이름'으로 한다.
get으로 시작하는 메서드를 '겟터(getter', set으로 시작하는 메서드를 '셋터(setter)'라고 부른다.

생성자의 접근 제어자
생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다. 보통 생성자의 접근 제어자는 클래스의 접근 제어자와 같지만, 다르게 지정할 수도 있다.
생성자의 접근 제어자를 private으로 지정하면, 외부에서 생성자에 접근 할 수 없으므로 인스턴스를 생성할 수 없게 된다. 그래도 클래스 내부에서는 인스턴스를 생성할 수 있다.

class Singleton {
	private Singleton() {
    	...
    }
    ...
}

대신 인스턴스를 생성해서 반환해주는 public메서드를 제공함으로써 외부에서 이 클래스의 인스턴스를 사용하도록 할 수 있다. 이 메서드는 public인 동시네 static이어야 한다.

class Singletn {
	...
    ///getInstance()에서 사용될수 있도록 인스턴스가 미리 생성되어야 하므로 static이어야 한다.
    private static Singleton s = new Singleton(); 
    private Singleton() {
    	...
    }
    
    //인스턴스를 생성하지 않고도 호출할 수 있어야 하므로 static이어야 한다.
    public static Singleton getInstance() {
    	return s;
    }
    	...
}

이처럼 생성자를 통해 직접 인스턴스를 생성하지 못하게 하고 public메서드를 통해 인스턴스에 접근하게 함으로써 사용할 수 있는 인스턴스의 개수를 제한할 수 있따.

또 한가지, 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다. 왜냐하면, 자손클래스의 인스턴스를 생성할 때 조상클래스의 생성자를 호출해야만 하는데, 생성자의 접근 제어자가 private이므로 자손클래스에서 호출하는 것이 불가능하기 때문이다. 그래서 클래스 앞에 final을 더 추가하여 상속할 수 없는 클래스라는 것을 알리는게 좋다.

Math클래스는 몇 개의 상수와 static메서드만으로 구성되어 있기 때문에 인스턴스를 생성할 필요가 없다.
그래서 외부로부터의 접근을 막기 위해 다음과 같이 생성자의 접근 제어자를 private으로 지정하였다.

public final class Math {
	private Math() {}
    	...
}

4.6 제어자(modifier)의 조합

클래스 : public, (default), final, abstract
메서드 : 모든 접근 제어자, final, abstract, static
멤버변수 : 모든 접근 제어자, final, static
지역변수 : final

제어자를 조합해서 사용할 때 주의점

    1. 메서드에 static과 abstract를 함께 사용할 수 없다.
      static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.
    1. 클래스에 abstract와 final을 동시에 사용할 수 없다.
      클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.
  • 3.abstract메서드의 접근 제어자가 private일 수 없다.
    abstract메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손클래스에서 접근할 수 없기 때문이다.
  • 4.메서드에 private과 final을 같이 사용할 필요는 없다.
    접근 제어자가 private인 메서드는 오버라이딩 될 수 없기 때문이다. 이 둘 중 하나만 사용해도 의미가 충분하다.

5. 다형성(polymorphism)

5.1 다형성이란?

여러가지 형태를 가질 수 있는 능력을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현했다.

조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조하 수 있도록 했다는 것이다.
참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야한다.

참조변수의 타입이 참조변수가 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 결정한다는 사실을 이해한다는 것을 매우 중요하다.

조상 타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

5.2 참조변수의 형변환

자손타입 ----> 조상타입(Up-casting) : 형변환 생략가능
자손타입 <---- 조상타입(Down-casting) : 형변환 생략불가

참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수를) 조절하는 것 뿐이다.

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

5.3 instanceof연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다. 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.
그리고 연산의 결과로 boolean값인 true와 flase중의 하나를 반환한다.
instanceof를 이용한 연산결과가 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 때문에, 실제 인스턴스와 같은 타입의 참조변수로 형변환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있다.

5.4 참조변수와 인스턴스의 연결

멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

5.5 매개변수의 다형성

참조변수의 다형적인 특징은 메서드의 매개변수에도 적용된다.

class Product {
	int price;				//제품의 가격
    int bonusPoint;			//제품구매 시 제공하는 보너스 점수
}
class Tv		extends prodict{}
class computer	extends product{}
class Audio		extends product{}

class Buyer {				//고객, 물건을 사는 사람
	int money = 1000;		//소유 금액
    int bonusPoint = 0;		보너스 점수
}


>
//Buyer클래스에 물건을 구입하는 기능의 메서드를 추가해보자. 
//구입할 대상이 필요하므로 매개변수로 구입할 제품을 넘겨받아야 한다. 
//Tv를 살 수 있도록 매개 변수를 Tv타입으로 하였다.

void buy(Tv t) {
	//Buyer가 가진 돈(money)에서 제품의 가격(t.price)만큼 뺸다.
    money = money - t.price;
    
    //Buyer의 보너스 점수(bonusPoint)에 제품의 보너스점수(t.bonusPorint)를 더한다.
    bonusPoint = bonusPoint + t.bonusPoint;
}

//buy(Tv t)는 제품을 구입하면 제품을 구입한 사람이 가진 돈에서 
//제품의 가격을 빼고 보너스점수는 추가하는 작업을 하도록 작성되었다. 
//그런데 buy(TV t)로는 Tv밖에 살 수 없기 때문에 
//아래와 같이 다른 제품들도 구입할 수 있는 메서드가 추가로 필요하다. 

void buy(Computer c) {
	money - money - c.price;
    bonusPoint = bonusPoint + c.bonusPoint;
}

void buy(Audio a) {
	money - money - a.price;
    bonusPoint = bonusPoint + a.bonusPoint;
}

//이렇게 되면, 제품의 종류가 늘어날 때마다 Buyer클래스에는 새로운 buy메서드를 추가해줘야 한다.
//그러나 메서드의 매개변수에 다형성을 적용하면 아래와 같이 하나의 메서드로 처리할 수 있다.

void buy(Product p) {
	money = money - p.price;
    bonusPoint = bonusPoint + p.bonusPoint;
}

//매개변수가 Product타입의 참조변수라는 것은, 메서드의 매개변수로 Product클래스의
//자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 것이다.
//그리고 Product클래스에 price와 bonusPoint가 선언되어 있기 때문에 참조변수 p로
//인스턴스의 price와 bonusPoint가 선언되어 있기 때문에 참조변수 P로 인스턴스의
//price와 bonusPoint를 사용할수 있기에 이와 같이 할 수 있다.

앞으로 다른 제품 클래스를 추가할 때 Product클래스를 상속받기만 하면, buy(Product p)메서드의
매개변수로 받아들여 질 수 있다.

Buyer b = new Buter();
tv t = new Tv();
Computer c = new Computer();
b.buy(t);
b.buy(c);

5.6 여러 종류의 객체를 배열로 다루기

조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하므로, Product클래스가
Tv, Computer, Audio클래스의 조상일 때, 다음과 같이 할 수 있는 것을 배웠다.

Product p1 = new Tv();
product p2 = new Computer();
product p3 = new Audio();
위의코드를 Product타입의 참조변수 배열로 처리하면 아래와 같다.

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

Product배열로 구입한 제품들을 저장할 수 있도록 했지만, 배열의 크기를 10으로 했기 때문에 11개 이상의 제품을 구입할 수 없는 것이 문제다. 그렇다고 해서 배열의 크기를 무조건 크게 설정할 수도 없다.
이런 경우, Vector클래스를 사용하면 된다. Vector클래스는 내부적으로 Object타입의 배열을 가지고 있어서, 이 배열에 객체를 추가하거나 제거할 수 있게 작성되어 있다.
그리고 배열의 크기를 알아서 관리해주기 때문에 저장할 인스턴스의 개수에 신경 쓰지 않아도 된다.

public class Vector extends AbstractList implements list, cloneable, java.io.Serializable {
	protected Object elementData[];
    	...
}   

Vector클래스는 이름 떄문에 클래스의 기능을 오해할 수 있는데, 단지 동적으로 크기가 관리되는 객체배열일 뿐이다.

  • Vector() : 10개의 객체를 저장할 수 있는 Vector인스턴스를 생성하고, 10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가된다.
  • boolean add(Object o) : Vector에 객체를 추가한다. 추가에 성공하면 결과값으로 true, 실패하면 false를 반환한다.
  • boolean remove(Ovject o) : Vector에 저장되어 있는 객체를 제거한다. 제거에 성공하면 true, 실패하면 false를 반환한다.
  • boolean isEmpty() : Vector가 비어있는지 검가한다. 비어있으면 true, 비어있지 않으면 false 반환
  • Object get(int index) : 지정된 위치(index)의 객체를 반환한다. 반환타입이 Object타입이므로 적절한 타입으로의 형변환이 필요하다.
  • int size() : Vector에 저장된 객체의 개수를 반환한다.
import java.util.*; // Vector 클래스를 사용하기 위해 추가

class Product {
	int price; 		//제품의 가격
    int bonusPoint;	//제품구매 시 제공하는 보너스점수
    
    Product(int price) {
    	this.price = price;
        bonusPoint = (int)(price/10.0);
    }
    
    Product() {
    	price = 0;
        bonusPoint = 0;
    }
}

class Tv extends Product {
	Tv() {super(100);}
    public String toString() {return "Tv";}
}
class Coumputer extends Product {
	Tv() {super(200);}
    public String toString() {return "Computer";}
}
class Audio extends Product {
	Audio() {super(50;}
    public String toString() {return "Audio";}
}

class Buyer {			//고객,물건을 사는 사람
	int money = 1000;	//소유금액
    int bonusPoint = 0;	//보너스 점수
    Vector item = new Vector();		//구입한 제품을 저장하는데 사용될 Vector 객체
    
    void buy(Product p) {
    	if(money < p.price) {
        	System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
            return;
        }
        money -= p.price;			//가진 돈에서 구입한 제품의 가격을 뺸다.
        bonusPoint += p.bonusPoint;	//제품의 보너스 점수를 추가한다.
        itme.add(p);				//구입한 제품을 Vector에 저장한다.
        System.out.println(p + "을/를 구입하셨습니다.");
    }
    
    void refund(product p) {			//구입한 제품을 환불한다.
    	if(item.remove(p)) {			//제품을 Vector에서 제거한다.
        	money += p.price;
            bonusPoint -= p.bonusPoint;
            System.out.println(p + "을/를 반품하셨습니다.");
        }else {						//제거에 실패한 경우 
        	System.out.println("구입하신 제품 중 해당 제품이 없습니다.");
        }
    }
    
    void summary() {
        int sum = 0;
        String itemList = "";
    
   	    if(item.isEmpty()) {
        System.out.println("구입하신 제품이 없습니다.");
        return;
        }
    
        //반복문을 이용해서 구입한 물품의 총 가격과 목록을 만든다.
       for(int i = 0; i < item.size(); i++) {
       	   Product p = (Product)item.get(i);
           sum += p.price;
           itemList += (i == 0) ? "" + p : ", " + p;
    
    	}
        System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
        System.out.println("구입하신 제품은 " + itemList + "입니다.");
	 }
 }
 
 class PolyArgumentTest3 {
 	public static void main(String args[]) {
    	Buyer b = new Buyer();
        Tv tv = new Tv();
        Computer com = new Computer();
        Audio audio = new Audio();
        
        b.buy(tv);
        b.buy(com);
        b.buy(audio);
        b.summary();
        System.out.println();
        b.refund(com);
        b.summery();
     }
 }
 
 실행결과
 Tv을/를 구입하셨습니다.
 Computer을/를 구입하셨습니다.
 Audio을/를 구입하셨습니다.
 구입하신 물품의 총금액은 350만원입니다.
 구입하신 제품은 Tv, Computer, Audio입니다.
 
 Computer을/를 반품하셨습니다.
 구입하신 물품의 총금액은 150만원입니다.
 구입하신 제품은 Tv, Audio입니다.

//구입한 물건을 다시 반환할 수 있도록 refund(Product p)를 추가 하였다. 이 메서드가 호출되면, 구입물품이 저장되어 있는 item에서 해당제품을 제거한다.

6. 추상클래스(abstract class)

6.1 추상클래스란?

설계도에 비유한다면 추상클래스는 미완성 설계도이다. 맴버의 개수에 관계된 것이 아니라, 단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.
추상클래스로 인스턴스는 생성할 수 없다. 추상클래는 상속을 통해서 자손클래스에 의해서만 완성 될 수 있다.

추상클래스 자체로는 클래스의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 추상클래스는 키워드 'abstract'를 붙이기만 하면 된다. 이렇게 함으로써 이 클래스를 사용할 때, 클래스 선언부의 abstract를 보고 이 클래스에는 추상메서드가 있으니 상속을 통해 구현해주어야 한다는 것을 쉽게 알 수 있을 것이다.

추상메서드를 포함하고 있다는 것을 제외하고는 일반클래스와 똑같다. 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.

6.2 추상메서드(abstract method)

선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.
미완성 상태로 남겨두는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에
조상 클래스에서는 선언부만 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성 되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것이다.

추상메서드 역시 키워드 'abstract'를 앞에 붙여 주고, 추상메서드는 구현부가 없으므로 괄호{}대신 문장의 끝을 알리는 ';'을 적어준다.

주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.
abstract 리턴타입 메서드이름();

abstract class Player { //추상 클래스
	abstract void play(int pos);	//추상 메서드
    avstract void stop();
}

class AudioPlayer extends Player {
	void play(int pos)	{ //내용생략 }	//추상메서드를 구현
    void stop()	{//내용생략}			//추상메서드를 구현
}

abstract class AbstractPlayer extends Player {
	void play(int pos) {//내용생략} 	//추상메서드를 구현
    

6.3 추상클래스의 작성

여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하도록 하는 경우도 있다.

상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 이와 반대로 추상화는 기존의 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것이라고 할 수 있다.

추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업

abstract class Plater {
	boolean pause;			//일시정지 상태를 저장하기 위한 변수
    int currentPos;			//현재 play되고 있는 위치를 저장하기 위한 변수
    
    Player() {				//추상클래스도 생성자가 있어야 한다.
    	pause = false;
        currentPos = 0;
    }
    //지정된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성되어야 한다.
    abstract void play(int pos);	//추상메서드
    //재생을 즉시 멈추는 기능을 수행하도록 작성해야 한다.
    abstract void stop();			//추상메서드
    
    void play() {
    	play(currentPos);			//추상메서드
    }
    
    void pause() {
    	if(pause)	{			//pause가 true일때(정지상태)에서 pause가 호출되면,
        	pause = false;		//pause의 상태를 false로 바꾸고,
            play(currentPos); 	//현재의 위치에서 play를 한다.
        }else {					//pause가 false일 때(play상태)에서 pause가 호출퇴면,
        	pause = true;		//pause의 상태를 true로 바꾸고
            stop();				//play를 멈춘다.
        }
    }
}	

//이제 조상 PLayer클래스를 조상으로 하는 CDPlayer클래스를 만들어보자

class CDPlayer extends Player {
	void play(int currnetPos)	{
    	//조상의 추상메서드를 구현.
    }
    void stop() {
    	//조상의 추상메서드를 구현
    }
    
    //CDPlayer클래스에 추가로 정의된 멤버
    int currentTrack; //현재 재생중인 트랙
    void nextTrack() {
    	currnetTrack++;
        	...
    }
    
    void preTrack() {
    	if(currentTraCk > 1) {
        	currentTrack--;
        }
        	...
    }
}

조상 클래스의 추상메서드를 CDPlayer클래스의 기능에 맞게 완성해주고, CDPlayer만의 새로운 기능들을 추가하였다. 사실 Player클래스의 Play(int pos)와 stop()을 추상메서드로 하는 대신, 아무 내용도 없는 메서드로 작성할 수 있다. 아무런 내용 없이 단지 괄호{}만 있어도 추상 메서드가 아닌 일반 메서드로 간주되기 때문이다.

class Player {
	...
    void play(int pos) {} 
    void stop() {}
    ...
}

abstract을 붙여서 추상메서드로 선언하는 이유는 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다.

이번엔 기존의 클래스로부터 공통된 부분을 뽑아내서 추상클래스를 만들어보자.

class Marine {	//보병
	int x, y:	//현재 위치
    void move(int x, int y) {//지정된위치로 이동}
    void stop()				{//현재 위치에 정지}
    void stimPack()			{//스팀팩을 사용한다}
}

class Tank {	//탱크
	int x, y:	//현재 위치
    void move(int x, int y) {//지정된위치로 이동}
    void stop()				{//현재 위치에 정지}
    void changeMode()		{//공격모드로 변환한다.}
}

class Dropship {	//수송선
	int x, y:	//현재 위치
    void move(int x, int y) {//지정된위치로 이동}
    void stop()				{//현재 위치에 정지}
    void load()				{//선택된 대상을 태운다}
    void unload()			{//선택된 대상을 내린다}
}

//공통된 부분을 뽑아서 추상화 시킨다.
abstract class Unit {
	int x, y;
    abstract void move(int x, int y);
    void stop() {//현재위치에 정지}
}

class Marine extends Unit 	{//보병
	void move(int x, int y) {//지정된 위치로 이동}
    void stimPack()			{//스팀팩을 사용한다}
}

class Tank extends Unit 		{//탱크
	void move(int x, int y) 	{//지정된 위치로 이동}
    void changeMode()			{//공격모드로 변환한다.}
}

class Dropship extends Unit 	{//수송선
	void move(int x, int y) 	{//지정된 위치로 이동}
    void load()					{//선택된 대상을 태운다}
    void unload()				{//선택된 대상을 내린다}
}

이 Unit클래스는 다른 유닛을 위한 클래스를 작성하는데 재활용될 수 있다.
최대한 공통부분을 뽑아내기 위한 것이기도 하지만, 모든 유닛은 이동할 수 있어야 하므로 Unit클래스에는 move메서드가 반드시 필요한 것이기 때문이다.

Unit[] group = new Unit[4];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new Marine();
group[3] = new Dropship();
for(int i = 0; i < group.length; i++) {
group[i].move(100, 200);
}

공통조상인 Unit클래스 타입의 참조변수 배열을 통해서 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있다는 것을 보여준다. 공통조상이 없었다면 하나의 배열로 다룰 수 없다.
추상메서드가 구현된 Marine, Tank, Dropship 인스턴스의 메서드가 호출되는 것이다.

7. 인터페이스(interface)

7.1 인터페이스란?

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.
오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인처페이스는 구현된 것은 아무것도 없고 밑그림만 그려져 있는 '기본 설계도'라고 할 수 있다.

인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체로만으로 사용되기 보다는 다른 클래스를 작성하는데 도움을 줄 목적으로 작성된다.

7.2 인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 키워드로 class 대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

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

일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항이 있다.

  • 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
    단, static메서드와 디폴트 메서드는 예외(jdk1.8부터)

원래는 인터페이스의 모든 메서드는 추상이어야 하는데, JDK1.8부터 인터페이스에 static메서드와 디폴트 메서드(default method)의 추가를 허용하는 방향으로 변경되었다.

7.3 인터페이스의 상속

인터페이스는 인터페이스로부터만 상속을 받으며, 클래스와는 달리 다중상속, 즉 여러개의 인터페이스로부터 상속을 받는것이 가능하다.

인터페이스는 클래스와 달리 Object클래스와 같은 최고 조상이 없다.

interface Movable {
	//지정된 위치(x,y)로 이동하는 기능의 메서드
    void main(int x, int y);
}

interface atackable {
	//지정된 대상(u)을 공격하는 기능의 메서드
    void attack(Unit u);
}

interface fightable extends Movable, Attackable { }

클래스의 상속과 마찬가지로 자손 인터페이스(Fightable)는 조상 인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다.
그래서 Fightable 자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로부터 상속받은 두 개의 추상메서드, move(int x,int y)와 attack(Unit u)을 멤버로 갖게 된다.

7.4 인터페이스의 구현

인퍼테이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상 클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않다.

다만 클래스는 확장한다는 의미의 키워드 'extends'를 사용하지만 인터페이스는 구현한다는 의미의 키워드
'implements'를 사용할 뿐이다.

class 클래스 이름 implements 인터페이스이름 {
	//인터페이스에 정의된 추상메서드를 구현해야 한다.
}
class Fighter implements Fightable {
	public void move(int x, int y) {//내용생략}
    public void attack(Unit u) {//내용생략}
}

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

abstract class Fighter implements Fightable {
	public void move(int x, int y) {//내용생략}
}

그리고 다음과 같이 상속과 구현을 동시에 할 수도 있다.

class Fighter extends Unit implements Fightable {
	public void move(int x, int y) {//내용생략}
    public void attack(Unit u) {//내용생략}
}

인터페이스의 이름에는 주로 Fightable과 같이 '~을 할 수 있는'의 의미인 'able'로 끝나는 것들이 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다.
또한 그 인터페이스를 구현한 클래스는 '~를 할 수 있는' 능력을 갖추었다는 의미이기도 하다.

7.5 인터페이스를 이용한 다중상속

인터페이스가 다중상속을 위한 것으로 오해를 사곤 하는데, 꼭 그렇지만은 않다.

만일 두 개의 클래스로부터 상속을 받아야 할 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나 어느 한쪽의 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.

7.6 인터페이스를 이용한 다형성

다형성에 대해 학습할 때 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배웠다. 인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조 할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.

인터페이스 Fightable을 클래스 Fighter가 구현했을 때, 다음과 같이 Fighter인스턴스를 Fightable타입의 참조변수로 참조하는 것이 가능하다.

Fightable f = (Fightable)new Fighter();
	또는
Fightable f = new Fighter();

따라서 인터페이스는 다음과 같이 메서드의 매개변수의 타입으로 사용될 수 있다.

void attack(Fightable f) {
	//...
}

인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다.
그래서 attack메서드를 호출할 때는 매개변수로 Fightable인터페이스를 구현한 클래스의 인스턴스를 념겨주어야 한다.

class Fighter extends Unit implements Fightable {
	public void move(int x, int y)  {//내용생략}
    public void attack(Fightable f) {//내용생략}
}    

위와 같이 Fightable인터페이스를 구현한 Fighter클래스가 있을 때, attack메서드의 매개변수로 Fighter인스턴스를 넘겨 줄 수 있다. 즉 attack(new Fighter())와 같이 할 수 있다.
그리고 다음과 같이 메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.

Fightable method() {
	...
    Fighter f = new Fighter();
    return f;
}

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

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.");
    }   
}
class ParserTest {
	public static void main(String argsp[]) {
    Parseable parser = ParserManager.getParser("XML");
    parser.parse("document.xml");
    parser = ParserManager.getParser("HTML");
    parser.parse("document2.html");
    }
}
실행결과 
document.xml - XML parsing completed.
document2.html - HTML parsing completed.

Parseable 인터페이스는 구문분석(parsing)을 수행하는 기능을 구현할 목적으로 추상메서드 'parse(String fileName)'을 정의했다. 그리고 XMLParser클래스와 HTMLParser클래스는 Parseable인터페이스를 구현하였다.
ParserManager클래스의 getParser메서드는 매개변수로 넘겨받는 type의 값에 따라 XMLParser인스턴스 또는 HTMLParser인스턴스를 반환한다.

Parseable paser = ParseManager.getParser("XML");
public static Parseable getParser(String type) {
	if(type.equals("XMK")) {
    	return new XMLParser();
    }else {
    	Parseable p = new HTMLParser();
        return p;
    }
}

getParser메서드의 수행결과로 참조변수 parser는 XMLParser인스턴스의 주소값을 갖게 된다.
마치 'Parseable parser = new XMLParser();이 수행된 것과 같다.
참조변수 parser를 통해 parse()를 호출하면, parser가 참조하고 있는 XMLParser인스턴스의 parse메세드가 호출된다.

만일 나중에 새로운 종류의 XML구문 분석기 NewXMLPaser클래스가 나와도 ParserTest클래스는 변경할 필요 없이 PaserManager클래스의 getParser메서드에서 'return new XMLParser(); 대신 'return new NewXMLParser();로 변경하기만 하면 된다.

이러한 장점은 특히 분산환경 프로그래밍에서 위력을 발휘한다. 사용자 컴퓨터에 설치된 프로그램을 변경하지 않고 서버측의 변경만으로도 사용자가 새로 개정된 프로그램을 사용하는 것이 가능하다.

7.7 인터페이스의 장점

인터페이스의 장점

  • 개발시간을 단축 시킬 수 있다.
    일단 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다.
    그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.
  • 표준화가 가능하다.
    프로젝트에서 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
    서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.
  • 독립적인 프로그래밍이 가능하다.
    인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 떄문에 실제 구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

데이터 베이스 관련 인터페이스를 정의하고 이를 이용해서 프로그램을 작성하면, 데이터베이스의 종류가 변경되더라도 프로그램을 변경하지 않도록 할 수 있다.
단, 데이터베이스 회사에서 제공하는 클래스도 인터페이스를 구현하도록 요구해야 한다.

데이터베이스를 이용한 응용프로그램을 작성하는 쪽에서는 인터페이스를 이용해서 프로그램을 작성하고,
데이터베이스 회사에서는 인터페이스를 구현한 클래스를 작성해서 제공해야 한다.

실제로 자바에서는 다수의 데이터베이스와 관련된 다수의 인터페이스를 제공하고 있으며, 프로그래머는 이 인터페이스를 이용해서 프로그래밍하면 특정 데이터베이스에 종속되지 않는 프로그램을 작성할 수 있다.

7.8 인터페이스의 이해

  • 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
  • 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.

7.9 디폴트 메서드와 static메서드

원래는 인터페이스에 추상 메서드만 선언할 수 있었는데, JDK1.8부터 디폴트 메서드와 static메서드도 추가 할 수 있게 되었다. static메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 예전부터 인터페이스에 추가하지 못할 이유가 없었다.

디폴트 메서드
조상 클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만, 인터페이스의 경우에는 보통 큰 일이 아니다.
인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 하기 때문이다.
고심끝에 나온 것이 디폴트 메서드(default method)라는 것이다.

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상메서드가 아니기 떄문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

디폴트 메서드 앞에 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통 {}이 있어야 한다. 디폴트 메서드 역시 접근 제어자가 public이며, 생략 가능하다.

interface MyInterface {							interface MyInterface {
	void method();								    oid method();
    void newMethod();   //추상메서드 ----->		  	  default void newmethod(){
}  												}

위의 왼쪽과 같이 newMethod()라는 추상 메서드를 추가하는 대신, 오른쪽과 같이 디폴트 메서드를 추가하면, 기존의 MyInterface를 구현한 클래스를 변경하지 않아도 된다.
즉, 조상 클래스에 새로운 메서드를 추가한 것과 동일해 지는 것이다. 대신, 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생한다. 이 충돌을 해결하는 규칙은 다음과 같다.

  • 여러 인터페이스의 디폴트 메서드 간의 충돌
    인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
  • 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
    조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

위의 규칙이 외우기 귀찮으면, 그냥 필요한 쪽의 메서드와 같은 내용 오버라이딩 해버리면 그만이다.

8. 내부클래스(inner class)

내부 클래스는 클래스 내에 선언된다는 점을 제외하고는 일반적인 클래스와 다르지 않다.
내부 클래스는 사용빈도가 높지 않으므로 내부 클래스의 기본 원리와 특징을 이해하는정도까지만 학습해도 충분하다.

8.1 내부 클래스란?

내부 클래스는 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스를 선언하는 이유는 간단하다. 두 클래스가 서로 긴밀한 관계에 있기 때문이다.

내부클래스의 장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다(캡슐화)
class A {						class A {	//외부클래스
	...								...
}				-------->		}   class B {
class B {							}
	...
}								}

이 때 내부 클래스인 B는 외부 클래스인 A를 제외하고는 다른 클래스에서 잘 사용되지 않는 것이어야 한다.

8.2 내부 클래스의 종류와 특징

내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다.

  • 1.인스턴스 클래스(instance class)
    외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다루어진다.
    주로 외부 클래스의 인스턴스멤버들과 관련된 직업에 사용될 목적으로 선언된다.
    1. 스태틱 클래스(static class)
      외부 클래스의 멤버변수는 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다루어진다. 주로 외부 클래스의 static멤버, 특히 static메서드에서 사용될 목적으로 선언된다.
    1. 지역 클래스(local class)
      외부 클르새의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.
    1. 익명 클래스(anonymous class)
      클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

8.3 내부 클래스의 선언

변수가 선언된 위치에 따라 인스턴스 변수, 클래스 변수(static변수), 지역변수로 나뉘듯이 내부 클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다. 그리고, 각 내부 클래스의 선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위(scope)와 접근성(accessibility)을 갖는다.

class Outer {								class Outer {
	int iv = 0;									class InstanceInner {}
    static int cv = 0;							static class StaticInner {}
    						------------>		
    void myMethod() {							void myMethod() {
    int lv = 0;										class LocalInner {}
    }											}
}											}

8.4 내부 클래스의 제어자와 접근성

아래 코드에서 인스턴스 클래스와 스태틱 클래스는 외부클래스의 멤버변수(인스턴스변수, 클래스변수)와 같은 위치에 선언되며, 또한 멤버변수와 같은 성질을 갖는다.

class Outer {								class Outer {
private int iv = 0;					  			private class InstanceInner {}
protected static int cv = 0;					protected static class StaticInner {}
    						  ----------->		
    void myMethod() {							void myMethod() {
    int lv = 0;										class LocalInner {
    }											}
}											}

내부 클래스도 클래스이기 때문에 abstract나 final과 같은 제어자를 사용할 수 있을 뿐만 아니라, 멤버변수들처럼 private, proteced과 접근제어자도 사용이 가능하다.

8.5 익명 클래스(anonymous class)

익명클래스는 특이하게도 다른 내부 클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다

new 조상클래스이름() {
	//멤버 선언
}
또는

new 구현인터페이스이름() {
//멤버 선언
}

이름이 없기 떄문에 생성자도 가질 수 없으며, 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인퍼테이스를 구현 할 수도 없다. 

class InnerEx6 {
object iv = new Object() { void method() {} }; //익명 클래스
stastic Object cv = new Object() { void method(){}}; //익명 클래스

void myMethod() {
	Object lv = new Object() { void method(){}}; 		//익명 클래스
이 예제를 컴파일 하면 다음과 같다.
InnerEx6.class
EnnerEx6$1.class <- 익명 클래스
EnnerEx6$2.class <- 익명 클래스
EnnerEx6$3.class <- 익명 클래스

>익명 클래스는 이름이 없기 때문에 '외부 클래스명$숫자.class'의 형식으로 클래스 파일이 결정된다. 

좋은 웹페이지 즐겨찾기