자바의 정석 | Ch 07 - 객체지향 프로그래밍2-1
by 무작정 개발상속 (inheritance)
- 기존 클래스로 새로운 클래스를 작성하는 것(코드의 재사용)
- 두 클래스를 부모와 자식으로 관계를 맺어주는 것
- 자손은 조상의 모든 멤버를 상속받는다. ( 생성자, 초기화 블록 제외)
- 자손의 변경은 조상에 영향을 미치지 않는다.
- 자손의 멤버개수는 조상보다 적을 수가 없다.(같거나 많다)
두 클래스는 서로 상속 관계에 있다고 하면, 상속해주는 클래스를 '조상 클래스' , 상속받는 클래스를 '자손 클래스'라고 한다. 예를 들어 Parent 클래스를 조상, Child 클래스를 자손 클래스라 생각해보자.
class Parent { }
class Child extends Parent { }
이렇게 자손 클래스 뒤에 extends를 써줘서 Parent 클래스를 상속 받는다.
만약에 조상 클래스에 age라는 정수형 변수를 멤버 변수로 추가하면, 자손 클래스는 조상의 멤버를 모두 상속받기 때문에
, 자동적으로 age라는 멤버 변수가 추가된 것과 같은 효과를 얻을 수 있다.
반대로 자손 클래스에 새로운 멤버로 play()를 추가한다.
자손 클래스인 Child에 play메서드가 추가되었지만 조상인 Parent클래스에는 아무런 영향을 주지 않는다.
이를 보면, 조상 클래스가 변경되면 자손 클래스에 자동적으로 영향을 주지만, 자손 클래스가 변경되는 것은
조상 클래스에 아무런 영향을 주지 못한다.
package Chapter07;
// 조상(부모) 클래스 : Tv
class Tv {
boolean power; // 전원상태(on/off)
int channel; // 채널
// Tv 클래스의 메서드
void power() { power = !power; }
void channelUp() { ++channel; }
void channelDown() { --channel; }
}
// 자손(자식) 클래스 : CaptionTv
class CaptionTv extends Tv {
boolean caption; // 캡션상태(on/off)
void displayCaption(String text) {
if (caption) { // 캡션 상태가 on(true)일 때만 text를 보여 준다.
System.out.println(text);
}
}
}
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"); // 캡션을 화면에 보여 준다.
}
}
포함 관계 - (클래스 간의 관계)
상속 이외에도 클래스를 재사용하는 방법이 클래스 간에 포함 관계를 맺어주는 것이다.
- 클래스의 멤버로 다른 클래스 타입의 참조 변수를 선언하는 것
- 작은 단위의 클래스를 만들고 이들을 조합해서 클래스를 만든다.
클래스 간 관계 결정
- 상속 관계 - ~는 ~이다.(is - a) / extends Point { }
- 포함 관계 - ~는 ~을 가지고 있다.(has - a) / Point c = new Point();
※ 프로그램의 모든 클래스를 분석하여 가능한 많은 관계를 맺도록 노력해서 코드의 재사용성을 높여야 한다.
package Chapter07;
class Shape {
//전역변수(인스턴스변수)
String color = "black";
// draw 메서드
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값을 문자열로 반환
}
}
// Circle 클래스에 Shape 클래스 상속받기
class Circle extends Shape { // Circle와 Shape는 상속 관계
//전역변수(인스턴스변수)
Point center; //원의 원점좌표 , Circle와 Point는 포함 관계
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;
}
// draw 메서드
void draw() { //원을 그리는 대신에 원의 정보를 출력
System.out.printf("[center = (%d,%d), r = %d, color = %s]%n", center.x, center.y, r, color);
}
}
// Triangle 클래스에 Shape 클래스 상속받기
class Triangle extends Shape {
Point [] p = new Point[3]; // 3개의 Point인스턴스를 담을 배열 생성
//생성자
Triangle(Point[] p) {
this.p = p;
}
//draw 메서드(Override)
void draw() {
System.out.printf("[p1=%s, p2=%s, p3=%s, color=%s]%n", p[0].getXY(), p[1].getXY(), p[2].getXY(), color);
}
}
public class DrawShape {
public static void main(String[] args) { // main 함수
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), 150);
//위의 문장은 이 두 문장을 합친 것이다.
// Point p = new Point(150,150);
// Circle c = new Circle(p,50);
t.draw(); // 삼각형을 그린다.
c.draw(); // 원을 그린다.
}
}
Circle 클래스는 Shape클래스로부터 모든 멤버를 상속받아서 Shape클래스에 정의된 color, draw() 메서드를
사용할 수 있다. Circle에도 draw() 메서드가 있는데 여기서 호출되는 것은 Shape클래스의 draw()이다.
이처럼 조상(부모) 클래스에 정의된 메서드를 자손 클래스에 재정의하는 것을 오버라이드(Override)이라 한다.
단일 상속 (single inheritance)
- Java에서는 단일 상속(하나의 부모만 상속)만 허용한다.
- 비중이 높은 클래스만 상속, 나머지는 포함관계로 한다.
단일 상속의 장점
- 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다.
package Chapter07;
class Tv1 {
//전역변수(인스턴스변수)
boolean power; // 전원상태(on/off)
int channel; // 채널
//power 메서드
void power () {
power = !power;
}
// 채널 업 메서드
void channelUp() {
++channel;
}
//채널 다운 메서드
void channelDown() {
--channel;
}
}
class Vcr {
//전역변수(인스턴스변수)
boolean power; // 전원상태(on/off)
int counter = 0;
// power 메서드
void power() {
power = !power;
}
//play,stop,rew,ff 메서드 내용 생략
void play() {}
void stop() {}
void rew() {}
void ff() {}
}
//TvCr클래스는 Tv1클래스를 상속받는다.
public class TvCr extends Tv1 {
Vcr vcr = new Vcr(); //TvCr클래스에 Vcr 클래스를 포함시켜 사용(포함관계)
int counter = vcr.counter;
//Vcr의 play,stop,rew,ff 메서드를 호출해서 사용
void play() {
vcr.play();
}
void stop() {
vcr.stop();
}
void rew() {
vcr.rew();
}
void ff() {
vcr.ff();
}
}
이렇게 하면 Vcr클래스의 메서드 내용이 변경되더라도 TvCr클래스의 메서드들 역시 변경된 내용이 적용되는 결과를
얻을 수 있다.
Object 클래스 - 모든 클래스의 조상
Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상(부모) 클래스이다.
- 모든 클래스의 조상(부모)이 없는 클래스는 자동으로 Object클래스를 상속 받음
- 모든 클래스는 Object 클래스에 정의된 11개의 메서드를 상속 받음
모든 상속계층도의 최상위에는 Object 클래스가 위치하기 때문에 Java의 모든 클래스들은 Object 클래스에 정의된
멤버들을 사용할 수 있다.
ex) toString() or equals(Object o) 메서드를 따로 정의하지 않고도 사용할 수 있던 이유가 이 메서드들이
Object클래스에 정의된 것들이기 때문이다.
Overriding - 오버 라이딩
오버 라이딩(Overriding)이란 조상(부모) 클래스로부터 상속받은 메서드의 내용을 변경하는 것(재정의)이다.
- 상속받은 조상(부모)의 메서드를 자손 클래스 자신에 맞게 변경하는 것
조건(규칙)
(1) 오버 라이딩은 메서드의 내용만을 재정의하는 것이기 때문에 메서드의 선운부는 조상(부모)과 일치해야 한다.
- 이름이 같아야 한다.
- 매개변수가 같아야 한다.
- 반환 타입이 같아야 한다.
(2) 접근제어자를 조상(부모) 클래스보다 좁은 범위로 변경할 수 없다.
(3) 조상(부모) 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
오버 로딩 vs 오버 라이딩
오버 로딩과 오버 라이딩을 구별할 수 있어야 한다!!
오버 로딩(Overloading) - 기존에 없는 새로운 메서드를 정의하는 것(new)
오버 라이딩(Overriding) - 상속받은 메서드의 내용을 변경하는 것(change, modify)
참조 변수 super
참조변수 : 메모리에 생성된 인스턴스를 가리키는 변수
super는 자손(자식) 클래스에서 조상(부모) 클래스로부터 상속받은 멤버를 참조하는 데 사용되는 참조 변수이다.
- 객체 자신을 가리키는 참조 변수. 인스턴스 메서드(생성자) 내에서만 존재한다.
- 조상의 멤버를 자신의 멤버와 구별할 때 사용
package Chapter07;
class Parent {
int x=10;
}
class Child extends Parent {
void method() {
System.out.println("x=" + x);
System.out.println("this.x=" + this.x);
System.out.println("super.x=" + super.x);
}
}
//SuperTest 클래스에 Parent 클래스를 상속 받음
public class SuperTest extends Parent {
public static void main(String[] args) {
Child c = new Child(); // SuperTest클래스에 Child 클래스를 포함시켜 사용(포함관계)
c.method(); // Child 클래스의 method()메서드 호출
}
}
여기서는 x, this.x, super x 모두 같은 변수를 의미하므로 같은 값이 출력된다.
package Chapter07;
class Parent2 {
int x=10;
}
//Child2클래스는 Parent2클래스를 상속받는다.
class Child2 extends Parent2 {
int x=20;
void method2() {
System.out.println("x=" + x); // 20 -> Child2클래스의 멤버변수 사용
System.out.println("this.x=" + this.x); // 20 -> this는 자식 클래스에 선언된 멤버변수를 뜻함.
System.out.println("super.x=" + super.x); // 10 -> super는 부모 클래스로부터 상속받은 멤버변수를 뜻함
}
}
public class SuperTest2 {
public static void main(String[] args) {
Child2 c2 = new Child2();// SuperTest2클래스에 Child2 클래스를 포함시켜 사용(포함관계)
c2.method2();// Child2 클래스의 method2()메서드 호출
}
}
super.x는 부모 클래스로부터 상속받은 멤버 변수 x(10)를 뜻하고, this.x는 자식 클래스(자신)에 선언된 멤버 변수를
뜻해서 20이 나온다.
이처럼 부모 클래스에 선언된 멤버 변수와 같은 이름의 멤버 변수를 자식 클래스에서 중복해서 정의하는 것이 가능하고
참조 변수 super를 이용해서 서로 구별할 수 있다.
super() - 조상 클래스의 생성자
this()와 마찬가지로 super() 역시 생성자이다. this()는 같은 클래스의 다른 생성자를 호출할 때 사용하고, super()는
부모 클래스의 생성자를 호출할 때 사용한다.
- 부모의 생성자를 호출할 때 사용(생성자와 초기화 블록은 상속이 안되니까)
- 부모의 멤버는 부모의 생성자를 호출해서 초기화, 자식의 생성자는 자신이 선언한 변수만 초기화할 수 있음
- 생성자의 첫 줄에는 반드시 생성자를 호출해야 함
package Chapter07;
class Point1 {
int x=10;
int y=20;
Point1(int x, int y) {
//생성자 첫 줄에서 다른 생성자를 호출하지 않기 때문에 컴파일러가 super()를 여기에 삽입한다.
//super()는 Point클래스의 조상인 Object클래스의 기본 생성자인 Object()를 의미
this.x = x;
this.y = y;
}
}
//Point3D클래스에 Point1클래스를 상속받음
class Point3D extends Point1 {
int z=30;
Point3D() {
this(100, 200, 300); // Point3D(int x, int y, int z)를 호출
}
Point3D(int x, int y, int z) {
super(x, y); // Point1(int x, int y)를 호출 , super는 부모클래스의 생성자를 호출할 때 사용해서
this.z = z; //
}
}
public class PointTest {
public static void main(String argsp[]) {
Point3D p3 = new Point3D(); // PointTest클래스에 Point3D클래스를 포함시켜 사용(포함관계)
System.out.println("p3.x=" + p3.x);
System.out.println("p3.y=" + p3.y);
System.out.println("p3.z=" + p3.z);
}
}
생성자 호출 순서
Point 3 D() -> Point 3 D(int x, int y, int z) -> Point(int x, int y) -> Object()
제어자(modifier)
제어자(modifier)는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다.
제어자의 종류는 크게 접근 제어자, 그 외의 제어자로 나눌 수 있다.
접근 제어자 - public, protected, default, private
그 외의 제어자 - static, final, abstract 등등
- 하나의 대상에 여러 제어자를 같이 사용 가능(접근 제어자는 1개만 가능)
static - 클래스의, 공통적인
- static은 멤버 변수, 메서드, 초기화 블록에서 사용(객체 생성 없이 가능)
- static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다.
제어자 | 대상 | 의미 |
static | 멤버변수 | - 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다. - 클래스변수는 인스턴스(객체)를 생성하지 않고도 사용 가능하다. - 클래스가 메모리에 로드될 때 생성된다. |
메서드 | - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다. - static 메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다. |
※ static초기화 블록은 클래스가 메모리에 로드될 때 단 한 번만 수행되며, 주로 클래스 변수(static변수)를 초기화하는데
주로 사용된다.
class StaticTest {
static int width = 200; // 클래스 변수(static변수)
static int height = 120; // 클래스 변수(static변수)
static { //클래스 초기화 블럭
// static변수의 복잡한 초기화 수행
}
static int max(int a, int b) { // 클래스 메서드(static 메서드)
return a > b ? a : b;
}
}
final - 마지막의, 변경될 수 없는
- final는 어디든 붙일 수 있다. 어디에 붙느냐에 따라 기능이 달라진다.
(1) 클래스 : 변경, 확장 불가능한 클래스, final로 지정된 클래스는 다른 클래스의 조상이 될 수 없음
(2) 메서드 : 오버 라이딩(Overriding)이 불가능한 메서드(변경될 수 없는 메서드)
(3) 변수(멤버 변수, 지역변수) : 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.
abstract - 추상의, 미완성의
메서드의 선언 부만 작성하고 실제 수행 내용은 구현하지 않은 추상 메서드를 선언하는 데 사용
abstract가 사용될 수 있는 곳 - 클래스, 메서드
(1) 클래스 : 클래스 안에 추상 메서드가 선언되어 있음을 의미(미완성 설계도)
(2) 메서드 : 선언 부만 작성하고 구현부가 없는 메서드(추상 메서드)
※ abstract 클래스는 객체(인스턴스)를 생성할 수 없고, 추상 메서드를 갖고 있으니 상속받아서 완성해줘야 한다.
abstract class AbstractTest { // 추상클래스(추상 메서드를 포함한 클래스)
abstract void move(); // 추상 메서드(구현부가 없는 메서드)
}
접근 제어자 (access modifier)
제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스 | 전체 |
public | O | O | O | O |
protected | O | O | O | |
(default) | O | O | ||
private | O |
접근 범위
public > protected > (default) > private
접근 제어자를 이용한 캡슐화(접근 제어자를 사용하는 이유??)
- 외부로부터 데이터를 보호하기 위해(외부에서 메서드를 통해 간접 접근 허용)
- 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해
'Language > Java' 카테고리의 다른 글
[Java] JDK, JRE 이란? (0) | 2022.10.03 |
---|---|
자바의 정석 | Ch 07 - 객체지향 프로그래밍2-2 (0) | 2022.02.06 |
[JAVA + 국비교육] Java 팀 프로젝트(자판기) | 소스 코드, 실행 영상, 느낀 점 (0) | 2022.01.19 |
[JAVA+국비교육] XML파일 읽어오기, 정규화 표현식 (0) | 2022.01.17 |
[JAVA+국비교육] Class 클래스, 정규화표현식, 채팅프로그램 만들기 (0) | 2022.01.14 |
블로그의 정보
무작정 개발
무작정 개발