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
'Java > Java의 정석' 카테고리의 다른 글
Chapter 09 java.lang 패키지와 유용한 클래스 (0) | 2022.02.24 |
---|---|
Chapter 08 예외처리 Exception Handling (0) | 2022.02.23 |
Chapter 06 객체지향 프로그래밍 I (0) | 2022.02.22 |
Chapter 05 배열(Array) (0) | 2022.02.20 |
Chapter 04 조건문과 반복문 (0) | 2022.02.19 |