학습 목차
- 클래스 정의와 사용
- 상속
학습 개요
- 클래스를 정의하고, 정의된 클래스를 사용하는 것에 관해 살펴봄
- 생성자를 정의하는 방법과
static
필드와 메소드 및final
필드와 메소드의 의미 학습 - 객체의 생성과 초기화 및 메소드의 오버로딩 학습
- 기존 클래스를 이용해 새로운 클래스 정의하기 위한 클래스 상속 개념 이해
학습 목표
- 클래스 정의 문법 숙지
static
과final
키워드의 의미와 사용법 이해- 메소드 오버로딩과 오버라이딩 구분하고 적용 가능
- 상속 개념 활용해 클래스 정의
1. 클래스 정의와 사용
1-1. 메소드 정의
- 클래스 정의 내부에 존재
- 해더와 몸체로 구성
ex)void f( ) { }
- 메소드 정의 문법
1 2 3 4 5
[접근 제어자] 반환형 메소드이름([자료형 인자[, 자료형 인자...]]) // 헤더 [throws 예외이름] // 예외 처리 { // 메소드의 몸체 문장 ... }
1 2 3 4 5 6 7
public double getArea() { return radius * radius * PI; } public void setRadius(int r) { // int 값 입력받는 메소드, return 값 없음 radius = r; }
1-2. 생성자
- 객체가 생성될 때 자동으로 실행되는 메소드
- 객체의 필드 값을 초기화 하거나 메모리 할당 등의 작업
- 객체 생성 방법은
new 클래스 이름(인자..)
ex)Circle c = newCircle(5);
new
연산자를 이용해 객체 생성 (메모리 할당)- 생성자가 호출 (데이터 필드의 초기화)되면서
- 객체의 참조 값을 변수에 대입(
=
)
1-3. 생성자 정의
- 보통의 메소드와 정의 방법 다름
- 생성자는
new
로 객체 생성 시 자동 호출
- 생성자는
- 정의 방법
- 생성자 이름은 클래스 이름과 같음
- 반환형을 선언하지 않음
- 여러 생성자 정의 가능(생성자 오버로딩)
- 하나의 클래스에 여러개의 생성자 정의 가능
- 이름이 같은 메소드가 하나의 클래스 안에 중복해서 정의 가능
- 인자의 개수와 인자의 자료형으로 구분
- 하나의 클래스에 여러개의 생성자 정의 가능
- 접근 제어자는 보통
public
- 생성자 정의 문법
1 2 3 4 5 6 7 8 9 10
class Circle { // 클래스 이름과 생성자 이름이 같음 double r; public Circle(double a) { // 생성자 정의 r = a; // 반환형 존재하지않음 } public double getArea() { return r * r * 3.14; } }
1 2 3 4 5 6 7 8 9
public class CircleArea2 { public static void main(String args[]) { Circle c = new Circle(5.0); // 생성자 호출 System.out.println(c.r); // r은 접근 제어자가 없어 같은 패키지의 다른 클래스에 존재하는 r 사용 가능 System.out.println(c.getArea()); } }
1-4. 기본 생성자
- 인자가 없는 생성자, 디폴트 생성자(default construcor)
- 클래스 정의에 한 개의 생성자 정의도 없으면 컴파일러가 다음과 같은 것을 자동으로 생성
public Circle() {}
- 생성자 몸체의 첫 줄에 부모 생성자의 명시적 호출이 없다면 다음 코드가 자동으로 들어감
super(); // 부모 클래스의 기본 생성자를 호출
- 따라서 부모 클래스에서 기본 생성자의 존재를 확인해야함
- 자식 클래스의 생성자 정의 첫 줄에 부모 생성자의 호출이 존재하지 않을 경우 부모 클래스의 기본 생성자가 자동 호출
1-5. 클래스의 사용
- 상속을 위해 사용
class CSub extends CSuper { }
Csub
- 새로운 클래스
- 자식 클래스
CSuper
- 기존 클래스
- 부모 클래스
- 변수를 선언하고 객체 생성
- 클래스 참조형 변수의 선언
Circle c; // 어딘가에 Circle 클래스에 대한 정의 존재
c = new Circle(-5.;
- 클래스 참조형 변수의 선언
1-6. 객체의 사용
- 객체 변수와 점(
.
) 연산자를 사용하여 멤버에 접근- 객체가 소유하는 데이터(인스턴스 변수)를 읽거나 쓰기
- 객체를 이용하여 메소드(인스턴스 메소드)를 호출
ex)c.r = 5; // c의 r을 변경, 객체 사용
ex)c.getArea() // c에게 getArea() 실행을 요청
1 2 3
public double getArea() { return this.r * this.r * 3.14; }
1-7. static 필드
static
필드
1 2 3
class Circle { static int count; }
- 정적 필드 or 클래스 변수
- 클래스의 모든 객체가 공유하는 데이터
- 객체의 생성이 없어도 항상 사용 가능
- 어떤 객체도 값을 변경할 수 있음
- 사용 방법
클래스이름.정적필드
객체변수.정적필드
도 가능
1-8. static 메소드
static
메소드- 정적 메소드 or 클래스 메소드
- 객체에 무관하게 호출되고 실행
- 메소드의 몸체에서
this
사용 불가
- 메소드의 몸체에서
static
필드와 인자 가지고 작업- 사용 방법
클래스이름.정적메소드()
ex)Math.sqrt(2.0);
ex)Integer.parselnt("102");
1-9. final 필드와 final 메소드
final
필드- 상수 데이터 선언
- 선언할 때 초기 값 지정 필요
static과
자주 함께 사용final static double PI = 3.141592;
final
메소드- 자식 클래스로 상속은 가능하나 재정의 할 수 없는 메소드
1-10. 객체 초기화
- 객체 생성 시 데이터 필드에 초기값 지정하는 것
- 클래스 변수는 프로그램 시작 시에 자동 초기화
- 데이터 필드는 자동으로 초기값이 주어질 수 있음
- 방법 (순서)
1 2 3 4 5 6 7 8 9
class IniTest { int nValue = 1; // 초기값 선언하지 않을경우 0으로 자동 초기화 { nValue = 2; // 초기값 변경 } public IniTest() { nValue = 3; // 초기값 변경 } }
static
(클래스 변수) 필드의 선언문에서 초기화static
(클래스 변수) 초기화 블록 실행1 2 3 4
static int nValue = 1; static { nValue = 2; // 클래스 변수 초기화 블록 }
non-static
(인스턴스 변수) 필드의 선언문에서 초기화non-static
(인스턴스 변수) 초기화 블록 실행1 2 3 4
int nValue = 1; { nValue = 2; // 인스턴스 변수 초기화 블록 }
- 클래스 몸체 내 임의 위치에 포함
- 초기값 지정을 위한 코드
static
필드는static
블록을 사용
- 생성자 실행
- 객체 초기화 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import java.awt.Point; class Rectangle { public int width = 0; // non-static 변수 초기화 public int height = 0; public Point origin; // Point 클래스 : 좌표상의 어떤 위치를 나타내는데 사용하는 클래스 public Rectangle() { origin = new Point(0,0); } public Rectangle(Point p, int w, int h) { // 생성자를 사용해 데이터 필드 초기화 origin = p; width = w; height = h; } }
1 2 3 4 5 6
public class Test { public static void main(String args[]) { Point originOne = new Point(23, 94); // Point 클래스에 x와 y좌표값을 저장하기 위한 멤버 변수 존재 Rectangle rectOne = new Rectangle(originOne, 100, 200); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
package kr.ac.knou.mxxikr; import java.awt.Point; public class RectangleTest { public static void main(String args[]) { Point originOne = new Point(23, 94); // Point 클래스에 x와 y좌표값을 저장하기 위한 멤버 변수 존재 Rectangle rectOne = new Rectangle(originOne, 100, 200); // 매개변수가 3개인 Rectangle 객체 생성 Rectangle rectTwo = new Rectangle(); // 매개변수가 없는 Rectangle 객체 생성 Rectangle rectThree = new Rectangle(rectTwo.origin, 200, 300); System.out.println("rectOne origin: "+ rectOne.origin); // Rectangle(Point p, int w, int h) 생성자 호출 System.out.println("rectOne width: "+ rectOne.width); System.out.println("rectOne height: "+ rectOne.height + "\n"); System.out.println("rectTwo origin: "+ rectTwo.origin + "\n"); // Rectangle() 생성자 호출 System.out.println("rectThree origin: "+ rectThree.origin); // Rectangle(Point p, int w, int h) 생성자 호출 System.out.println("rectThree width: "+ rectThree.width); System.out.println("rectThree height: "+ rectThree.height); } }
1-11. 메소드 오버로딩
- 인자의 개수나 인자의 자료형이 다르면 같은 이름의 메소드를 한 클래스에서 여러개 정의 가능
- 인자의 개수와 자료형이 정확히 일치하면 중복 정의 불가
- 메소드 시그니처를 가지고 메소드 구분
- 메소드의 이름, 매개 변수 리스트
- 메소드를 호출할 때, 가장 가까운 매개변수 목록을 가진 메소드 호출
ex)System.out.println(); // 인자 없음
ex)System.out.println("문자열"); // 인자는 String
ex)System.out.println(241); // 인자는 int
ex)System.out.println(34.5); // 인자는 double
ex)mc.add(10, 10.5); // add(int, int)와 add(double, double) → 자동 형변환 후 add(double, double) 호출
- 클래스와 객체의 사용
1 2 3 4 5 6 7 8 9 10 11 12 13
class Cylinder { private Circle c; // 원 (Circle 클래스 변수 정의) private int h; // 높이 public Cylinder(Circle a, int b) { c = a; h = b; } public double getVolume() { return c.getArea() * h; } }
1 2 3 4 5 6 7 8
public class CylinderVolume { public static void main(String args[]) { Circle c = new Circle(7); // Circle 객체 생성 int h = 8; Cylinder cy = new Cylinder(c, h); System.out.println(cy.getVolume()); } }
2. 상속
2-1. 클래스의 재사용
- 합성
- 기존 클래스를 새로운 클래스에서 데이터 필드의 자료형으로 사용
- has-a 관계
class Line { Point begin, end; }
- 상속
- 기존 클래스(부모)를 사용해 새로운 클래스(자식)를 정의
- 코드의 중복 작성을 줄이고 프로그램의 확장성 증가
- 기존 클래스를 확장 or 특화하는 것
- 자식 is -a 부모의 관계
2-2. 클래스의 상속
- 상속은 부모 클래스와 자식 클래스 간의 관계
- 자식 클래스가 부모 클래스의 필드와 메소드를 상속 받음
- 기존 클래스를 상속받을 때 키워드
extends
사용
ex)class Manager extends Employee {} // 자식 클래스(새로운 클래스)-Manager 부모클래스(기존 존재 클래스)-Employee
- 자식 클래스에서 상속받은 메소드 오버라이딩(재정의) 가능
- 클래스의 상속은 단일 상속만 가능
- 인터페이스 상속의 경우는 다중 상속 가능
- 클래스의 상속
1 2 3 4 5 6 7 8 9 10 11 12
class CSuper { private int f1; public int f2; public void setPrivate() { f1 = 10; } public void setPublic() { f2 = 20; } private void mPrivate() { f1 = 30; } } class CSub extends CSuper { private int f3; public int f4; }
1 2 3 4 5 6 7 8 9 10 11 12
public class InheritTest { public static void main(String args[]) { CSub sub = new CSub(); sub.f1 = 40; // 오류 발생 sub.f2 = 50; sub.f3 = 60; // 오류 발생 sub.f4 = 70; sub.setPrivate(); sub.setPublic(); sub.mPrivate(); // 오류 발생 } }
2-3. 메소드 오버라이딩
- 부모로부터 상속받은 메소드의 몸체를 자식 클래스에서 재정의하는 것
- 부모와 자식에서 같은 이름의 메소드가 다른 기능 수행하게 됨
- 방법
- 메소드의 이름, 인자의 개수와 자료형, 반환형이 같은 메소드를 정의
- 반환형은 서브 타입(상속 관계에서 자식 클래스)도 가능함
- 접근 제어자의 가시성(접근 범위)은 같거나 커져야함
protected
인 경우protected
또는public
public
인 경우public
만 가능
- 메소드 오버라이딩의 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class Shape { // 부모 클래스 public double getArea(double h, double w) { return h * w; } } class Triangle extends Shape { // 자식 클래스 public double getArea(double h, double w) { return h * w * 0.5; } } // getArea 메소드 재정의 -> 메소드 오버라이딩 public class OverridingTest { public static void main(String args[]) { Triangle t = new Triangle(); // Shape 자식 클래스 Triangle 객체 생성 System.out.println(t.getArea(3.0, 4.0)); // 3.0 * 4.0 * 0.5 = 6.0 출력 } }
2-4. this
- 메소드 호출 시, 숨은 인자로
this
가 메소드에 전달됨- this는 현재 객체에 대한 참조값을 가지고 있음
c1.display()
과c2.display()
의 결과가 다른 이유
- this는 현재 객체에 대한 참조값을 가지고 있음
- 인스턴스 메소드나 생성자에서만 사용 가능
1 2 3 4 5 6 7 8 9 10 11
class Circle { static double PI = 3.14; double radius; public double getArea() { return this.radius * this.radius * PI; // c1.radius 호출 시 this값은 c1값을 가지게 됨 } public void display() { System.out.println("반지름:" + this.radius + " 면적:" + this.getArea()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class CircleThis { static double PI = 3.14; // 정적 변수, 클래스 변수 double radius = 2; public double getArea() { return this.radius * this.radius * PI; // c1.radius 호출 시 this값은 c1값을 가지게 됨 } public void display() { System.out.println("반지름:" + this.radius + " 면적:" + this.getArea()+ " this:" + this); } public static void main(String args[]) { CircleThis c1 = new CircleThis(); CircleThis c2 = new CircleThis(); c1.display(); // 반지름:2.0 면적:12.56 this:kr.ac.knou.mxxikr.CircleThis@76ccd017 c2.display(); // 반지름:2.0 면적:12.56 this:kr.ac.knou.mxxikr.CircleThis@182decdb } }
2-5. super
this
와 같으나 자료형이 부모 클래스 유형- 자식 클래스의 인스턴스 메소드나 생성자에서 사용됨
this
와 마찬가지로static
메소드에서 사용할 수 없음
- 부모 클래스에서 오버로딩 당한 메소드를 호출하거나 상속 되었으나 감춰진 필드에 접근할 때 필요함
super.메소드(인자)
super.필드
this
와super
의 사용1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
class CSuper { // 부모클래스 public double x; } class CSub extends CSuper { // 자식 클래스 public double x; public CSub(double new_x) { this.x = new_x; // 자식 클래스의 x 필드 값 지정 super.x = new_x * 10; // 부모클래스의 x 필드 값 지정 } public double getSuper() { return super.x; // 상속받은 x 리턴 } public double getSub() { return this.x; // 재정의한 x 리턴 } }
1 2 3 4 5 6 7 8 9 10
public class ThisSuperTest { public static void main(String args[]) { CSub sub = new CSub(10.0); System.out.println(sub.x); // 10 System.out.println(sub.getSuper()); // 100 출력 System.out.println(sub.getSub()); // 10 출력 } }
2-7. this()와 super()
this()
- 같은 클래스의 다른 생성자를 호출하는 것
super()
- 부모 클래스의 생성자를 호출하는 것
- 상속 받은 데이터 필드를 초기화하기 위한 것
- 생성자 몸체에서 부모 클래스 생성자의 명시적 호출이 없다면, 인자가 없는 생성자인
super()
가 자동 호출됨
- 둘다 생성자 몸체의 첫번째 문장에서만 사용 가능
super
와super()
의 사용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 31 32 33
public class Cyliner extends Circle { private double height; public Cylinder() { super(); // 부모클래스의 생성자 초기화 height = 1.0; } public Cylinder(double radius, double h) { super(radius); // double값을 가지는 인자로 부모클래스의 생성자 초기화 this.height = h; } public double getHeight() { return height; } public void setHeight(double h) { this.height = h; } public double getArea() { // Cylinder의 getArea() 메소드 return 2 * PI * getRadius() * height + 2 * super.getArea(); // 상속받은 this.getRadius() 호출 } public double getVolume() { return super.getArea() * height; // Circle의 getArea() 메소드 호출 } public String toString() { return "Cylinder of radius = " + getRadius() + " height = " + height; } }
학습 정리
- 생성자를 정의하려면 이름을 클래스 이름과 같게 하고 반환형을 지정하지 않음
- 클래스 정의에 있는 데이터 필드의 선언문, 초기화 블록, 생성자 통해 객체 생성 시 필요한 데이터 필드의 초기값 지정 가능
- 클래스 정의 시 객체마다 각각 데이터가 필요하면 인스턴스 변수(non-static 데이터 필드)로, 모든 객체가 공유하는 데이터는
static
데이터 필드로 정의 - 메소드 오버로딩이란 한 클래스에서 이름이 같은 여러 메소드가 존재하는 상황
- 오버로딩된 메소드끼리는 매개변수 목록이 달라 구별 가능
- 부모로부터 상속받은 메소드의 몸체를 자식 클래스에서 다시 정의하는 것을 메소드 오버라이딩이라함
- 인스턴스 메소드와 생성자에서 숨은 인자인
this
와super
사용 가능
연습문제
Q1.
1
2
3
4
5
6
7
8
9
10
/**
키워드 final에 관한 설명으로 틀린 것은?
1. final 클래스의 자식 클래스를 정의할 수 없다.
2. 부모 클래스의 final 메소드는 자식 클래스로 상속될 때 재정의될 수 없다.
3. final 변수는 상수로 사용된다.
4. final 클래스의 객체를 생성할 수 없다.
**/
// 4. final 클래스의 객체를 생성할 수 없다.
Q2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Circle {
// ㄱ
private double radius;
public Circle(double radius) {
// ㄴ
}
public double getArea() {
return radius * radius * PI;
}
}
/**
아래 Circle 클래스에서 원주율 PI를 상수로 선언하기 위해 밑줄 친 ㄱ에 들어갈 적당한 내용은 무엇인가?
1. double PI = 3.14;
2. final double PI = 3.14;
3. const double PI = 3.14;
4. static final double PI = 3.14;
**/
// 4. static final double PI = 3.14;
Q3.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Circle {
// ㄱ
private double radius;
public Circle(double radius) {
// ㄴ
}
public double getArea() {
return radius * radius * PI;
}
}
// Circle 클래스의 생성자에서 밑줄 친 ㄴ에 들어갈 적당한 내용을 작성하시오.
// this.radius = radius;