Java/Java의 정석

Chapter 07 객체지향 프로그래밍 II

계란💕 2022. 2. 22. 12:32

1. 상속(Inheritance)

  1.1 상속의 정의와 장점

  Def) 상속: 기존의 클래스를 재사용해서 새로운 클래스를 작성하는 것

  - 코드의 중복 제거, 생산성 향상, 유지 보수에 기여한다.

  - 'extends' 키워드 사용 

  - 두 클래스는 조상 - 자손 클래스 관계를 맺는다.

  - 자손은 조상의 "모든 멤버"를 상속 받는다. (멤버 - 멤버 변수나 메서드만 상속) 

  - 생성자와 초기화 블럭은 상속되지 않는다. 

  - 자손의 멤버 개수는 조상보다 적을 수 없다.  (클래스는 멤버들의 집합)

    -> 조상 클래스: 부모(parent)클래스, 상위(super) 클래스, 기반(base) 클래스

    -> 자손 클래스: 자식(child)클래스, 하위(sub) 클래스, 파생된(derived) 클래스, 유도 클래스

    Ex)

<hide/>
class Tv {
	boolean power;  // 전원상태 (on / off)
	int channel;	// 채널
	void power()		{power = !power; }
	void channelUp()	{ ++channel;     }	
	void channelDown()  { --channel;	 }
}
class CaptionTv extends Tv{
	boolean caption; 				 // 캡션상태 (on, off)
	void displayCaption (String text) {
		if( caption ) { 			 // 캡션 태가 on(true)일 떄만 text를 보여준다.
			System.out.println(text);
		}
	}
}

public class CaptionTvTest {
	public static void main(String[] args) {
		CaptionTv ctv = new CaptionTv();
		ctv.channel = 10;				// 조상클래스로부터 상속받은 멤버
		ctv.channelUp();				// 조상클래스로부터 상속받은 멤버
		System.out.println(ctv.channel);
		ctv.displayCaption("Hello, world");
		ctv.caption = true;				// 캡션(자막) 기능을 켠다.
		ctv.displayCaption("Hello, World");	
	}
}

  Note) 실행결과

  - displayCaption(String text) 메서드는 캡션 상태가 On일 때만 text를 보여준다.

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

 

 

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

  Def) 포함(Composite): 한 클래스의 멤버 변수로 다른 클래스를 선언하는 것

  • 클래스 간의 포함관계를 맺어 주는 것은 한 클래스의 멤버 변수로 다른 클래스 타입의 참조 변수를 선언하는 것이다.
  • 작은 단위의 클래스를 먼저 만들고, 이 둘을 조합하여 하나의 커다란 클래스를 만든다.

 

 

  1.3 클래스 간의 관계 설정하기 - 상속 vs포함

  - 가능한 한 많은 관계를 맺어줘 재사용성을 높이고 관리하기 쉽도록 한다.

  - 'is-a'와 'has-a'를 가지고 문장을 만들어 본다. 

    -> 'is-a' : 상속 관계 - 더욱 객체지향적, 다형성과 관련됨

      ex) SportsCar is a Car.

    -> 'has-a': 포함 관계 - 재사용성을 높임

      ex) Circle has a Point. ( 'is a'보다 더 명확함.)

  Ex) 상속 

<hide/>
package javaStudy;
public class DrawShape {
	public static void main(String[] args) {
		Point [] p = { 	new Point(100, 100), 
						new Point(140, 50), 
						new Point(200, 100) 
					};
		Triangle t = new Triangle(p);
		Circle c = new Circle( new Point(150, 150), 50 );
		t.draw();  // 삼각형을 그린다.
		c.draw();  // 원을 그린다. 
	}
}
class Shape {
	String color = "black";
	void draw() {
		System.out.printf("[color = %s]%n", color );
	}
}
class Point {
	int x;
	int y;
	Point(int x, int y){
		this.x = x;
		this.y = y;
	}
	Point(){
		this(0, 0);	
	}
	String getXY() {
		return "(" + x + "," + y + ")";  // x, y를 문자열로 반환
	}
}
class Circle extends Shape{  // 원은 Shape클래스 상속 받음
	Point center; 			// 원의 원점 좌표
	int r;					// 반지름
	Circle(){
		this(new Point(0, 0) , 100);	// Circle(Point center, int r)을 호출
	}
	Circle(Point center, int r){
		this.center = center;
		this.r = r;	
	}
	void draw() {			// 원을 그리는 대신에 원의 정보를 출력하도록 했다. 
	System.out.printf("[center = (%d, %d), r = %d, color = %s]%n", center.x, center.y , r, color  );
	}			
}
class Triangle extends Shape {  // Triangle은 Shape클래스를 상속 
	Point [] p = new Point[3];  // 3개의 Point인스턴스를 담을 배열을 생성한다. 
	
	Triangle(Point[] p) {
		this.p = p;
	}
	void draw() {
		System.out.printf("[p1 = %s, p2 = %s, p3 = %s, color = %s]%n", p[0].getXY(), p[1].getXY(), p[2].getXY(), color   );	
	}
}

 

  Note) 실행 결과

  - Circle 클래스와 Shape는 상속관계 

    -> Shape클래스에는 draw()가 정의 되어 있다.

    -> 그런데, Circle클래스에서 다시 새로운 draw()를 정의한다..... "오버라이딩"

    -> 결과, Circle클래스의 draw()가 호출된다. 

  - Triangle클래스와 Point는 포함관계

 

 

  Ex) 포함 관계

<hide/>
package javaStudy;


public class DeckTest {
	public static void main(String[] args) {
		Deck d = new Deck();		// 카드 한 벌(deck)을 만든다.
		Card c = d.pick(0); 		// 섞기 전 제일 위 카드를 뽑는다.
		System.out.println(c);		// System.out.println(c.toString());과 같다.
		d.shuffle();
		c = d.pick(0);				// 섞은 후에 제일위 카드를 뽑는다.
		System.out.println(c);		
	}
}
class Deck {
	final int CARD_NUM = 52;  	//카드의 개수
	Card cardArr[] = new Card[CARD_NUM]; 	// Card 객체 배열을 포함
	
	Deck ()	{	// Deck 카드를 초기화한다. 
		int i = 0;
		for(int k = Card.KIND_MAX; k > 0; --k)
			for(int n = 0; n < Card.NUM_MAX ; ++n)
				cardArr[i++] = new Card(k, n + 1);
	}
	Card pick(int index) {		// 지정된 위치index에 있는 카드 하나를 꺼내서 반환
		return cardArr[index];
	}
	Card pick() {			// Deck에서 카드하나를 선택한다.
		int index = (int)(Math.random() * CARD_NUM);
		return pick(index);
	}
	void shuffle() { 		//카드의 순서를 섞는다.
		for(int i = 0; i < cardArr.length; ++i) {
			int r = (int)( Math.random() * CARD_NUM );
			Card temp = cardArr[i];
			cardArr[i] = cardArr[r];
			cardArr[r] = temp;
		}
	}
}
class Card{
	static final int KIND_MAX = 4;  // 카드 무늬의수 
	static final int NUM_MAX = 13;  // 무늬별 카드의 수
	static final int SPADE = 4;
	static final int DIAMOND = 3;
	static final int HEART = 2;
	static final int CLOVER = 1;
	int kind;
	int number;
	Card(){
		this(SPADE, 1);
	}
	Card(int kind, int number){
		this.kind = kind;
		this.number = number;
	}
	public String toString() {
		String[] kinds = {"", "CLOVER", "HEART", "DIAMOND", "SPADE" };
		String numbers = "0123456789XJQK";  // 숫자 10은 X로 표현
		return "kind : " + kinds[this.kind] + ", number : " + numbers.charAt(this.number);
	}
}

  Note) 실행 결과

 

  - pick()은 Card객체 배열 cardArr에 저장된 Card객체 중에서 하나를 꺼내 반환한다.

  - Card객체 배열 cardArr은 참조변수 배열이고, 이 배열에 실제 저장된 것은 객체의 주소이다.

  - toString()은 인스턴스의 정보를 문자열로 반환할 목적으로 정의되었다.

 

 

  1.4 단일 상속

  - Java는 단일 상속만을 허용한다.(C++, Python은 다중 상속 허용)

  - 비중 높은 클래스 하나만 상속 관계로, 나머지는 포함 관계로 본다.

 

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

  - 조상이 없는 클래스는 자동적으로 Object클래스를 상속 받게 된다. 

  - 상속계층도의 최상위에는 Object클래스가 위치한다.

  - 모든 클래스는 Object클래스에 정의된 11개의 메서드를 상속 받는다. 

     -> toString(), equals(Object obj), hashCode(), ...

 

 

2. 오버라이딩(Overriding)

  2.1 오버라이딩이란?

  Def) 오버라이딩

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

  - "메서드 재정의", "메서드 대체"라고도 표현한다.

 

  2.2 오버라이딩의 조건

    1) 선언부가 같아야 한다. (이름, 매개변수, 반환형)

      - 매개변수가 달라지면 '오버로딩'

    2) 접근제어자를 좁은 범위로 변경할 수 없다. 

      - 조상의 메서드가 protected라면 범위가 같거나 넓은 protected나 public으로만 변경할 수 있다. 

      - final, private 인 메서드는 오버라이딩 불가능하다.

    3) 조상클래스의 메서드보다 많은 수의 예외를 선언할 수 있다. (조상 클래스가 선언한 예외 내에서만 선언 가능)

      - static메서드를 instance 메서드로 오버로딩/ 오버라이딩 할 수 없다.

 

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

  - 오버로딩: 기존에 없는 새로운 메서드를 정의하는 것(New) - 매개변수 다름

    -> 한 클래스 내에서 같은 이름의 메서드를 여러 개 정의한다.

  - 오버라이딩: 부모 클래스로부터 상속 받은 메서드의 내용을 변경하는 것(Change, Modify) 재정의 

  Ex) 

<hide/>
class Parent {
	void parentMethod() {}; 
}
class Child ewtends Parent{
	void parentMethod() {}		// 오버라이딩
	void parentMethod(int i) {} 	// 오버로딩
	
	void childMethod() {}			
	void childMethod(int i) {}		// 오버로딩
	void childMethod() {}
}

 

  2.4 super

  - super: this와 비슷한데 조상의 멤버자신의 멤버를 구별하는 데 사용한다. 

  - 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.

  - super(): 부모 클래스의 생성자를 호출한다.

  - this: 인스턴스 자신을 가리키는 참조 변수, 인스턴스의 주소가 저장되어있음,

    -> 모든 인스턴스 메서드에 지역 변수로 숨겨진 채로 존재

  Ex)

<hide/>
package javaStudy;
public class SuperTest {
	public static void main(String[] args) {
		Child c = new Child();
		c.method();	
	}
}
class Parent{
	int x = 10;
}
class Child extends Parent{
	int x = 20;
	void method() {
		System.out.println("x = " + x);
		System.out.println("this.x = " + this.x);
		System.out.println("super.x = " + super.x);	
	}
}

  Note) 실행 결과

  - 같은 이름의 멤버변수 x가 조상클래스인 Parent에도 있고 자손 클래스인 Child클래스에도 있을 때는 super.x , Parent.x가 서로 다른 값을 참조한다. 

    -> super.x: 조상 클래스로부터 상속받은 멤버변수 x

    -> Parent.x: 자손 클래스에 선언된 멤버변수

 

  2.5 super() - 조상의 생성자  

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

  - 조상의 멤버들도 초기화되어야 하므로 자손의 생성자의 첫 문장에서 조상의 생성자를 호출해야한다. 

  - Object클래스를 제외한 모든 클래스의 생성자 첫 글자에는 생성자(같은 클래스의 다른 생성자 또는 조상의 생성자)를 호출해야 한다.

    -> 그렇지 않으면 컴파일러가 자동적으로 'super()'를 생성자의 첫 줄에 삽입한다. 

 

 

3. Package와 Import

  3.1 패키지(Package)

  - 서로 관련된 클래스와 인터페이스의 묶음

  - 클래스가 물리적으로 클래스파일(*.)인 것처럼, 패키지는 물리적으로 폴더이다.

    -> 패키지는 서브 패키지을 가질 수 있으며 , '.'으로 구분한다. 

  - 클래스의 실제 이름(full name)은 패키지명이 포함된 것이다. 

    -> String클래스의 full name은 java.lang.String, (java: 패키지 이름, lang:서브패키지, String:클래스이름)

 

  3.2 패키지의 선언

  - package 패키지명;

  - 패키지는 소스 파일에 첫 번째 문장(주석 제외)으로 단 한번 선언한다.

  - 하나의 소스파일에 둘 이상의 클래스가 포함된 경우, 모두 같은 패키지에 속하게 된다. 

  - 클래스패스(classpath) 설정

    -> 클래스패스(classpath)는 클래스파일(*.class)를 찾는 경로. 구분자는 ';'

    -> 클래스패스에 패키지가 포함된 폴더나 jar파일을 (*.jar) 나열한다.

    -> 클래스패스가 없으면 자동적으로 현재 폴더가 포함되지만 클래스 패스를 지정할 때는 현재 폴더(.)도 함께 추가해줘야한다.

 

  3.3  import문 (Ctrl + Shift + O)

  - 사용할 클래스가 속한 패키지를 지정하는데 사용

  - import문을 사용하면 클래스를 사용할 때 패키지명을 생략할 수 있다. 

  - java.lang 패키지의 클래스는 import하지 않고도 사용할 수 있다. 

  - import문은 패키지문과 클래스선언의 사이에 선언한다. 

 

  3.4 import문의 선언

  - 형식: import.패키지명.클래스명; 또는 import.패키지명.*;

  - 이름이 같은 클래스가 속한 두 패키지를 import할 때는 클래스 앞에 패키지명을 붙여줘야 한다. 

 

  3.5 static import문

  - static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다.   

    Ex)  

<hide/>
import static java.lang.Integer.*;
import static java.lang.Math.random;
import static java.lang.System.out;

  Note) 만약 위와 같이 선언했다면 아래와 같이 간략히 사용할 수 있다. 

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

 

 

4. 제어자(modifier)

  4.1 제어자란?

  Def) 제어자: 클래스, 변수 또는 메세드의 선언부와 함께 사용되어 부가적인 의미를 부여한다.

  - 접근제어자(public, protectef, default, private) / 그 외(static, final, abstract, native, transient, ...)

 

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

  Def) static + 멤버변수: 인스턴스 변수는 하나의 클래스로부터 생성되더라도 각기 다른 값을 유지하지만 클래스 변수(static 멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다. 

    -> 모든 인스턴스에서 공통적으로 사용되는 클래스 변수이다. 하나의 static 변수를  모든 인스턴스가 공유한다.

 

  Def) static + 메서드: 인스턴스를 생성하지 않고도 호출 가능한 메서드, 값을 생성하지 않아도 호출 가능, 메서드 내부에서 super, this사용 불가

 

  - static을 사용하면 객체를 선언하지 않고 객체의 변수를 바로 사용가능하다.

  - 멤버변수, 메서드, 초기화 블럭에 사용된다.

    -> 변수나 메서드의 특성을 바꾸는 키워드

  - 하나의 클래스 변수를 모든 인스턴스가 공유한다. (메모리에 한 번만 할당된다.)

  - 객체가 만들어지기 전부터 static변수가 먼저 만들어진다.

  - 멤버변수/ 메서드/ 초기화 블럭에 사용될 수 있다. 

  - static, final 모두 대문자로 쓴다.

 

  4.3 final - 마지막의, 변경될 수 없는(상수)

  - 클래스, 메서드, 멤버변수, 지역변수

    -> final + 클래스: 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. (다른 클래스의 조상이 될 수 없다.) - 상속 불가능 클래스

        ex) String 클래스

    -> final + 메서드: 변경될 수 없는 메서드이다. 즉, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.

    -> final + 멤버변수/ 지역변수: 값을 변경할 수 없는 "상수"가 된다. (반드시 한 번만 할당 가능)

  - 생성자를 이용한 final 멤버 변수 초기화

    -> final을 붙이면 상수이므로 보통 선언과 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화한다. 

  - final 앞에 static을 선언하는 경우가 많다.

  - static과는 다르게 객체를 생성한 다음, final이 쓰인 멤버변수를 쓸 수 있다. 

 

  4.4 abstract - 추상의, 미완성의

  - 클래스, 메서드에 사용가능

    -> abstract + 클래스: 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.

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

  - 추상클래스는 인스턴스를 생성하지 못한다. 

 

  4.5 접근 제어자(access modifier)

  - 멤버 또는 클래스에 사용되어 외부로부터의 접근을 제한한다. (접근 범위 조절)

  - 클래스, 멤버변수, 메서드, 생성자

    -> private: 같은 클래스

    -> (default): 같은 패키지 (접근 제어자가 없으면 default를 생략한 것이다.)

    -> protected: 같은 패키지 + 자손 클래스

    -> public: 접근 제한 전혀 없음

    -> (아래로 갈수록 범위가 넓다.)

 

  - 접근 제어자를 이용한 캡슐화(encapsulation)

    -> 접근제어자를 사용하는 이유: 데이터를 보호하기 위해, 내부적으로만 사용하는 부분을 감추려고

    -> 데이터가 유효한 값을 가지도록 하고, 비밀번호같은 데이터를 변경하지 못하도록 한다. 

    -> '데이터 감추기(data hiding)'

    -> private를 사용하면 외부에서 접근할 필요없는 멤버를 외부에 노출시키지 않음으로써 복잡성을 줄일 수 있다. 

 

  - 보통적으로

    -> 멤버변수의 값을 읽는 메서드이름 -> 'get멤버변수이름'

    -> 멤버변수의 값을 변경하는 메서드이름 -> 'set멤버변수이름' ...라고 정하는 암묵적인 규칙이 있다.  

    -> get으로 시작하는 메서드 => 겟터(getter)

    -> set으로 시작하는 메서드 => 셋터(setter)

 

   - 생성자의 접근 제어자

    -> 생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.

    -> 일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치한다. 

    -> private클래스는 다른 클래스의 조상이 될 수 없다. 

  

  4.6 제어자의 조합

  1) 메서드에 static과 abstract를 함께 사용할 수 없다.

    -> static메서드에는 몸통(구현부)에 있는 메서드에만 사용할 수 있기 때문

  2) 클래스에 abstract, final을 함께 사용할 수 없다. 

    -> abstract(상속을 해야 완성이 된다) <=> final(클래스 확장 불가)

  3) abstract의 메서드의 접근제어자가 private일 수 없다. 

    -> abstract메서드는 자손클래스에서 구현해줘야 하는데 접근제어자가 private이면 자손클래스에서 접근 불가.

  4) 메서드에 final과 private 함께 사용 불가

     -> private 메서드는 오버라이딩 불가,(둘 중 하나만 사용해도 충분)

 

 

5. 다형성(polymorphism)

  5.1 다형성이란?

  Def) 다형성: 여러 가지 형태를 가질 수 있는 능력

  - 하나의 참조 변수로 여러 타입의 객체를 참조 가능 (한 객체가 여러 가지 타입을 가질 수 있다.)

  - 조상 클래스 타입의 참조변수로 자손클래스 타입의 객체를 다룰 수 있는 것이다.

  - 상속관계 / 업 캐스팅 / 다운 캐스팅 / 객체 확인 연산자 (instance of)

 

  Note) instanceof 

  - 피연산자1 instanceof 피연산자2  

  - 피연산자1이 객체변수가 참조하는 객체가 실제 피연산자2클래스 이름이면 true, 아니면 false를 반환한다.

  - 상속 관계가 없으면 문법 오류가 발생한다.

 

 

  Ex) Tv클래스를 상속받는 CaptionTv 클래스가 있다고 하자. 상속 관계에 있기 때문에

    인스턴스를 참조 변수로 참조 또는 조상 타입의 참조 변수로 참조하는 것이 모두 가능하다.

CaptionTv c = new CaptionTv();  // 인스턴스를 같은 타입의 참조변수로 참조
Tv t		= new CaptionTv();  // 인스턴스를 조상타입의 참조변수로 참조

  Note) 

  - 위 코드에서 CaptionTv 인스턴스를 두 개 생성하고 참조변수 c, t가 생성된 인스턴스를 하나씩 참조하도록 했다. 

  - 이 경우 실제 인스턴스가 CaptionTv 타입이라고 할지라도,

  - 참조 변수 t로는 CaptionTv 인스턴스의 모든 멤버를 사용할 수 없다.

  - Tv타입 참조 변수로는 CaptionTv인스턴스 중에서 Tv클래스의 멤버들 (상속받은 멤버 포함)만 사용가능하다.

  - CaptionTv 인스턴스의 멤버 중에서 Tv클래스에 정의되지 않은 멤버인 text, caption()는 참조변수 t로 사용 불가능하다.

  - 즉, 둘 다 같은 타입의 인스턴스지만 참조 변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

  - 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다. 

 

 

  5.2 참조변수의 형변환

  Def) 클래스 형변환: 부모 타입으로 자식 객체를 참조하게 되면 부모의 메서드만 이용 가능하다. 따라서, 자식 객체의 메서드나 속성까지 이용하기 위해 형변환할 수 있다.

 

  - 서로 상속관계에 있는 타입간의 형변환만 가능하다.

  - 자손 타입에서 조상 타입으로 형변환하는 경우, 형변환 생략 가능

    -> 자손 타입 -> 조상 타입(업 캐스팅, Up-casting): 형변환 생략 가능

    -> 조상 타입 -> 자손 타입(다운 캐스팅, Down-casting): 형변환 생략 불가

  - 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조 변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.

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

  Ex) 

<hide/>
package javaStudy;
public class CastingTest1 {
	public static void main(String[] args) {
		Car car = null;
		FireEngine fe = new FireEngine();
		FireEngine fe2 = null;
		fe.water();
		car = fe;		// car = (Car)fe;에서 형변환이 생략된 형태이다.
//		car.water();	// 컴파일 에러 
		fe2 = (FireEngine)car;	// 자손타입 <- 조상타입 형변환
		fe2.water();	
	}
}

class Car {
	String color;
	int door; 
	void drive() {		// 운전 기능
		System.out.println("drive, Brrrr~");
	}
	void stop() {		// 멈추는 기능
		System.out.println("stop!!!");
	}
}
class FireEngine extends Car{		// 소방차
	void water() {
		System.out.println("water!!!");
	}
}

  Note) 실행결과

  Ex)

<hide/>
package javaStudy;
public class CastingTest2 {
	public static void main(String[] args) {
		Car  car = new Car();  // Car  car = new FireEngine();으로 바꿔야 정상.
		Car  car2 = null;
		FireEngine fe = null;
		car.drive();
		fe = (FireEngine)car; 	// 에러 발생지점
		fe.drive();
		car2 = fe;
		car2.drive();
	}
}

  Note) 실행결과

  - 컴파일은 성공하지만 실행 시 에러(ClassCastException)가 발생한다.

    ->  컴파일 할 때는 참조변수 간의 타입만 체크하기 때문에 실행 시 생성될 인스턴스 타입에 대해서는 알지 못함.

  - Car  car = new Car(); 부분을 Car  car = new FireEngine();으로 바꿔야 에러가 안 난다.

 

  5.3 instanceof 연산자

  - 참조변수가 참조하는 인스턴스의 실제 타입을 체크하는데 사용

  - 이항연산자이며 피연산자는 참조형 변수와 타입, 연산 결과는 true, false

  - 형변환이 가능한지 알려준다. 

  - 참조변수.getClass().getName();참조변수가 가리키고 있는 인스턴스 클래스 이름을 문자열(String)로 반환한다. 

  - 어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

 

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

  - "멤버변수가 중복정의된 경우", 참조변수의 타입에 따라 연결되는 멤버변수가 달라진다.

    -> 참조변수 타입에 따라 달라진다.

  - "메서드가 중복정의된 경우", 메서드는 참조변수 타입에 관계없이 항상 실제 인스턴스에 정의된 메서드 호출된다.

    -> 참조변수 타입에 영향받지 않는다. 

  Ex)

<hide/>
package javaStudy;
public class BindingTest3 {
	public static void main(String[] args) {
	Parent p = new Child();
	Child c = new Child();
	System.out.println("p.x = "+ p.x );	
	p.method();
	System.out.println();
	System.out.println("c.x = " + c.x);
	c.method();	
	}
}
class Parent{
	int x = 100;
	void method() {
		System.out.println("Parent Method");	
	}
}
class Child extends Parent{
	int x = 200;
	
	void method() {
		System.out.println("x = " + x );  // this.x와 같다.
		System.out.println("super.x = " + super.x );  // this.x와 같다.
		System.out.println("this.x = " + this.x );  // this.x와 같다.	
	}
}

 

  Note) 실행결과

  - 자손클래스 Child에 선언된 인스턴스 변수 x와 조상클래스 Parent로부터 상속받은 인스턴스 변수 x를 구분하는데 super, this가 사용된다.

  - x와 this.x는 Child클래스의 인스턴스변수 x를 뜻한다. 그래서 둘의 결과 값이 같다.

 

   5.5 매개변수의 다형성

  - 참조형 매개변수는 호출 시, 자신과 같은 타입 또는 자손 타입의 인스턴스를 넘겨줄 수 있다.

  Ex) 

<hide/>
package javaStudy;
class Product{
	int price;		// 가격
	int bonusPoint;	// 보너스 점수
	Product(int price){
		this.price = price;
		bonusPoint = (int)(price / 10.0) ; // 보너스 점수는 제품 가격의 1 / 10
	}
}
class Tv extends Product{
	
	Tv(){		// 조상클래스의생성자 Product(int price)를 호출한다.
		super(100);		// Tv의 가격을 100만원으로 한다. 
	}			
	public String toString() { return "Tv";	}		// Object클래스의 toString을 오버라이딩한다.
}

class Computer extends Product {
	Computer() { super(200); }
	public String toString() { return "Computer";	}
}
class Buyer{				// 고객 , 물건을 사는 사람
	int money = 1000;		// 소유금액
	int bonusPoint = 0;		// 보너스 점수
	void buy(Product p) {
		if( money < p.price ) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다. ");
			return;
		}
		money -= p.price;			// 가진 돈에서 구입한 제품의 가격을 뺀다.
		bonusPoint += p.bonusPoint; // 제품의 보너스 포인트를 추가한다./
		System.out.println(p + "을/를 구입하셨습니다.");
	}
}
public class PolyArgumentTest {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		b.buy(new Tv());
		b.buy(new Computer());
		System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
		System.out.println("현재 남은 보너스 점수는" + b.bonusPoint + "점입니다.");
	}
}

  Note) 실행 결과

  - 고객이 buy메서드를 이용해서 Tv와 Computer를 구입하고 잔고와 보너스 포인트를 출력한다.

 

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

  - 조상 타입의 배열에 자손들의 객체를 담을 수 있다. 

  - java.util.Vector - 모든 종류의 객체들을 저장할 수 있는 클래스

 

  Ex) 앞서 공부한 예제의 클래스에서 구입한 제품을 저장하기 위해 Product 배열을 추가

<hide/>
package javaStudy;
import java.util.*;
class Product{
	int price;
	int bonusPoint;
	
	Product(int price){
		this.price = price;
		bonusPoint = (int)(price / 10.0) ; // 보너스 점수는 제품 가격의 1 / 10
	}
	Product(){
		price = 0;
		bonusPoint = 0;
	}
}
class Tv extends Product{
	
	Tv(){super(100);}
	public String toString() { return "Tv";	}
}
class Computer extends Product {
	Computer() { 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; // 제품의 보너스 포인트를 추가한다./
		item.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()) {		// vector가 비어있는지 확인한다. 
			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 + "입니다.");
	}
}
public class PolyArgumentTest {
	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.summary();
	}
}

  Note) 실행 결과

  - Vector클래스는 내부적으로 Object타입의 배열을 가지고 있어서 이 배열에 객체를 추가하거나 제거 가능.

  - 배열의 크기를 알아서 관리해주기 때문에 저장한 인스턴스 개수에 신경쓰지 않는다. 

  - 구입한 물건을 반환할 수 있도록 refund(Product p)를 추가한다.

  

 

6. 추상클래스 (abstract class)

  6.1 추상클래스란?

  Def) 추상 클래스: 클래스설계도라면 추상 클래스는  '미완성 설계도'

  - 상속받는 클래스에 따라 내용이 달라질 수 있기 때문에 미완성

  -  하나 이상의 추상 메서드(미완성 메서드)를 포함하고 있는 클래스 - 일반 클래스와 차이점

  - 완성된 설계도가 아니므로 인스턴스를 생성할 수 없다.  

  - 다른 클래스에 도움을 주기 위해 사용된다. (자손 클래스에 의해서만 완성 가능)

  - 일반 메서드가 추상 메서드를 호출할 수 있다. 

  - 추상 클래스 자체는 객체 생성 불가

  -  형식

<hide/>
abstract class 클래스이름{
	...
}

/* 주석을 통해 어떤 기능을 수행할 목적으로 작성했는지 설명한다. */
abstract 반환형 메서드이름();		//구현부 없음

 

  Note) 추상클래스/ 인터페이스 차이점

  - 추상 클래스는 멤버를 작성할 수 있다. (인터페이스는 멤버가 없다.)

  - 추상 클래스는 추상 메서드를 하나 이상 포함하는 클래스 / 인터페이스는 모든 메서드가 추상 메서드이다.

  - 추상클래스는 상속 받는 클래스를 만들어서 기능을 이용하고 확장하는데 목적이 있다. 

    -> 인터페이스는 추상메서드의 구현을 강제함으로써 인터페이스를 구현한 객체들에 대해 같은 동작을 보장한다.

 

  6.2 추상 메서드(abstract method)

 - 선언부만 있고 구현부(몸통, body)가 없는 메서드 (구현부는 자식 클래스에 따라 다를 수 있음) 

 - 자식 클래스에서 반드시 오버라이딩 해야하는 메서드

 Ex)

<hide/>
public abstract void setHealth();

 

  6.3 추상클래스의 작성

  - 어떤 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나 기존클래스의 공통 부분을 뽑아서 조상클래스를 만든다. 

 

 

7. 인터페이스(interface)

  7.1 인터페이스란?

  Def) 인터페이스

  - 일종의 추상클래스, 추상클래스(미완성 설계도)보다 추상화 정도가 높다. (기본 설계도)

  - 실제 구현된 것이 전혀 없는 기본 설계도(껍데기)

  - 추상 메서드와 상수만을 멤버로 가질 수 있다.

  - 인스턴스를 생성할 수 없고, 클래스 작성에 도움 줄 목적으로 사용된다. 

  - 이미 정해진 규칙에 맞게 구현하도록 표준을 제시하는데 사용된다. 

  - 다중 상속처럼 이용할 수 있다.

  - 몸통 없는 클래스라고 볼 수 있다.

 

  7.2 인터페이스의 작성

  - 구성요소(멤버)는 추상 메서드와 상수만 가능

  - 모든 멤버변수는 "public static final"이어야 하며 생략 가능 - (static 상수만 정의 가능)

  - 모든 메서드는 "public abstract"이어야 하고 생략 가능

    -> 단, static메서드와 default메서드는 예외 ( JDK1.8부터 )

 

  7.3 인터페이스의 상속

  - 클래스처럼 상속 가능하다. 그러나 인터페이스는 클래스와 다르게 다중 상속이 가능하다.

    ex) public class 엘지리모콘 implements 리모콘, 전화기능 { }

  - 클래스와 같은 최고 조상(Object)이 없다.

  - 클래스와 인터페이스를 동시에 상속하는 것도 가능하다.

    ex) 클래스(군인), 인터페이스(IBS, 대테러진압, 산악행국) => public class SSU extends 군인 implements IBS, 대테러진압, 산악행국

 

  7.4 인터페이스 구현

  - 인터페이스를 구현하는 것은 클래스를 상속받는 것과 같다.

    -> 'implements'사용 i.e. 구현한다는 뜻

  - 인터페이스에 정의된 추상메서드를 완성해야한다. 

  - 상속과 구현이 동시에 가능하다.

 

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

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

  - 인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 없다.

  - 인터페이스를 메서드의 매개변수 타입으로 지정할 수 없다. 

  - 인터페이스를 메서드의 반환형으로 지정할 수 있다. 

  - 반환형이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 뜻.  - 암기!

 

  7.7 인터페이스의 장점

  1) 개발 시간을 단축시킬 수 있다.

  2) 표준화 가능

  3) 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.

  4) 독립적인 프로그래밍이 가능하다.

 

  7.8 인터페이스의 이해

  - 클래스를 사용하는 쪽(user)과 클래스를 제공하는 쪽(Provider)이 있다.

  - 매서들를 사용(호출)하는 쪽(user)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다. (내용을 몰라도된다.)

  - getInterface(): 인스턴스를 제공하는 메서드

  Ex)

<hide/>
package javaStudy;

public class InterfaceTest3 {
	public static void main(String[] args) {
		A a = new A();
		a.methodA();
	}	
}
class A{
	void methodA() {
		I i = InstanceManager.getInstance();
		i.methodB();
		System.out.println(i.toString());
	}
}
interface I{
	
	public abstract void methodB();	
}
class B implements I{
	public void methodB() {
		System.out.println("methodB in B class");
		
	}
	public String toString() {return "class B";}
	
}
class InstanceManager{
	public static I getInstance() {
		return new B();	
	}
}

  Note) 실행 결과

  - 인스턴스를 생성하지 않고, getInstance()를 통해 제공받는다.

  - 이렇게 하면 나중에 다른 클래스의 인스턴스로 변경되어도 A클래스의 변경없이 getInstance()만 변경하면 된다. 

    -> return new B(); => 이부분만 변경하면 된다. 

 

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

  - 디폴트 메서드(default method): 추상 메서드의 기본적인 구현을 제공하는 메서드이다. 

  - 디폴트 메서드는 추상 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다. 

 

  1) 여러 인터페이스의 디폴트 메서드 간의 충돌

    -> 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야한다.

  2) 디폴트 메서드와 조상 클래스와 메서드 간의 충돌

    -> 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다. 

 

 

8. 내부 클래스(inner class) 

  8.1 내부 클래스란?

  Def) 내부 클래스: 클래스 내에 선언된 클래스이다. (긴밀한 관계에 있는 클래스끼리)

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

  - 내부클래스의 장점

    -> 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근 가능하다. (외부 클래스에서는 내부 클래스 접근 불가)

    -> 코드의 복잡성을 줄일 수 있다. (캡슐화)

 

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

  - 인스턴스 클래스(instance class): 외부 클래스의 멤버변수 선언 위치에 선언, 외부클래스의 인스턴스멤버처럼 다뤄진다. 

  - 정적 클래스(static class): 외부 클래스의 멤버변수 위치에 선언, 외부 클래스의 static 멤버처럼 다뤄진다. 

  - 지역 클래스(local class): 외부 클래스의 메서드나 초기화 블럭 안에 선언하며 선언된 영역 내부에서만 사용될 수 있다.

  - 익명클래스(anonymous class): 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

 

  8.3 내부 클래스의 선언

  - 변수의 선언 위치와 동일하다. 

 

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

  - 인스턴스 클래스와 스태틱클래슨는 외부 클래스의 멤버 변수와 같은 위치에 선언된다. 

  - 그리고 멤버변수와 같은 성질을 갖는다. 

  - 인스턴스 멤버와 static멤버간의 규칙도 똑같이 적용된다. 

  Ex)

<hide/>
package javaStudy;
public class InnerEx2 {
	class InstanceInner{}
	static class StaticInner{}
	// 인스턴스 멤버 간에는 서로 직접 접근이 가능하다. 
	InstanceInner iv = new InstanceInner();
	// 스태틱 멤버 간에는 서로 직접 접근이 가능하다. 
	static StaticInner cv = new StaticInner();
	static void staticMethod() {
//		static멤버는 인스턴스멤버에 직접 접근할 수 없다. 		
//		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();
		// 굳이 접근하려면 아래와 같이 객체를 생성한다. 
		// 인스턴스 클래스는 외부 클래스를 먼저 생성해야만 생성할 수 있다.
		InnerEx2  outer = new InnerEx2();
		InstanceInner obj1 = outer.new InstanceInner();
	}	
	void instanceMethod() {
		// 인스턴스메서드에서는 인스턴스 멤버와 static 멤버와 모두 접근 가능하다.
		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();
		// 메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근할 수 없다. 
//		LocalInner lv = new LocalInner(); 		
	}
	void myMethod() {
		class LocalInner{
			LocalInner lv = new LocalInner();		
		}
	}
}

  Note) 스태틱 멤버는 인스턴스 멤버를 직접 호출할 수 없는 것처럼 외부 클래스의 인스턴스 멤버를 객체생성 없이 사용 불가

 

 Ex) 외부클래스가 아닌 다른 클래스에서 내부 클래스를 생성하고 내부 클래스의 멤버에 접근 경우

  - 컴파일 시 파일명: "외부 클래스$내부 클래스명.class"

 

  Ex) 내부 클래스와 외부 클래스에 선언된 변수의 이름이 같을 때 

  - 변수 앞에 "this"또는 "외부클래스명.this"를 붙여 구분가능

 

  8.5 익명 클래스(anonynous class)

  Def) 익명클래스는 다른 내부클래스와 다르게 이름이 없다. => 생성자도 없다. 

  - 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한 번만 사용될 수 있고  오직 하나의 객체만을 생성.

  - 오로지 단 하나의 클래스를 상속 받거나 단 하나의 인터페이스만 구현할 수 있다. 

  - 일회용 클래스이다. 

  - 형식

<hide/>
new 조상클래스이름(){
	// 멤버 선언
}

또는

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

 

  Ex)

<hide/>
package javaStudy;
import java.awt.Button;
import java.awt.event.*;

public class InnerEx8 {
	public static void main(String[] args) {
		Button b = new Button("Start");
		b.addActionListener (new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("ActionEvent occured!!!");
			}
		  }	
		);			
	}
}

 

  Note)  두 개의 독립된 클래스를 작성한 후에 다시 익명클래스를 이용하여 변경하면 된다. 

  - 오로지 하나의 클래스만 상속 받거나 하나의 인터페이스만 구현할 수 있다. 

 

  Note) 필드 참조 메서드 setter, getter

  - 객체를 만들었는데 객체의 

  - setter:필드의 값을 저장하는 메서드

  - getter: 필드의 값을 반환하는 메서드

 

  Ex) sette, getter

  - 필드 cardNumber가 private일 때 사용한다.

<hide/>

public class CreditCard {

	private long cardNumber;
	public String cardOwner;
	
	// setter
	public void setCardNumber(long cardNumber) {
		this.cardNumber = cardNumber;
		
	}
	// getter
	public long getCardNumber() {
		return cardNumber;
	}
    
}
<hide/>
public class CreditCardTest {
	public static void main(String[] args) {
		
		CreditCard myCard = new CreditCard();
// 오류	myCard.cardNumber = 1234_5678_1234_1234L;
		myCard.cardOwner = "홍길동";
		myCard.setCardNumber(1234_5678_1234_1234L);
		
// 에러	System.out.println(myCard.cardNumber);
		System.out.println(myCard.getCardNumber());
		
	}
}

  Note) 실행 결과: 1234567812341234