⦁ 상속(Inheritance)
- 기존의 클래스로 새로운 클래스를 작성하는 것(코드의 재사용)
- 두 클래스를 부모와 자식으로 관계를 맺어주는 것
class Parent { }
class Child extends Parent {
// ...
}
- 자손은 조상의 모든 멤버를 상속받는다.(생성자, 초기화블럭은 상속이 되지 않는다)
- 자손의 멤버 개수는 조상보다 적을 수 없다.(같거나 많다)
- 자손의 변경은 조상에 영향을 미치지 않는다.
class Tv {
boolean power; // 전원상태(on/off)
int channel; // 채널
void power() {
power = !power;
}
void channelUp() {
++channel;
}
void channelDown() {
--channel;
}
}
class SmartTv extends Tv { // SmartTv는 Tv에 캡션(자막)을 보여주는 기능을 추가
boolean caption; // 캡션상태 (on/off)
void displayCaption(String text) {
if (caption) { // 캡션 상태가 on(true)일 때만 text를 보여준다.
System.out.println(text);
}
}
}
public class Ex7_1 {
public static void main(String[] args) {
SmartTv stv = new SmartTv();
stv.channel = 10; // 조상 클래스로부터 상속받은 멤버
stv.channelUp(); // 조상 클래스로부터 상속받은 멤버
System.out.println(stv.channel);
stv.displayCaption("Hello, World");
stv.caption = true;
stv.displayCaption("Hello, World2");
}
}
⦁ 포함 관계
▶ 포함(composite)이란?
- 클래스의 멤버로 참조변수를 선언하는 것
- 작은 단위의 클래스를 만들고, 이 클래스들을 조합해서 새로운 클래스를 만든다.

class Car {
Engine e =new Engine(); // 엔진
Door[] d = new Door[4]; // 문, 문의 개수를 넷으로 가정하고 배열로 처리했다.
// ...
}
ex)
class MyPoint {
int x,y;
}
//class Circle extends MyPoint { // 상속
// int r;
//}
class Circle { // 포함
MyPoint P = new MyPoint(); // 참조변수의 초기화
int r;
}
public class InheritanceTest {
public static void main(String[] args) {
Circle c = new Circle();
c.P.x = 1;
c.P.y = 2;
c.r = 3;
System.out.println("c.P.x = "+c.P.x);
System.out.println("c.P.y = "+c.P.y);
System.out.println("c.r = "+c.r);
}
}
⦁ 클래스 간의 관계 결정하기
상속관계 ‘~은 ~이다.(is-a)’
포함관계 ‘~은 ~을 가지고 있다.(has-a)’
ex) 원(Circle)은 점(Point)이다. - Circle is a Point.
원(Circle)은 점(Point)를 가지고 있다. - Circle has a Point.
두 번째 문장이 더 자연스러운 관계이므로 포함관계가 어울린다.
실제로 대부분의 경우(90%?)가 포함관계이다.
⦁ 단일 상속(Single Inheritance)
- Java는 단일상속만을 허용한다(C++은 다중상속 허용)
- 비중이 높은 클래스 하나만 상속관계로, 나머지는 포함관계로 한다.
⦁ Object클래스 – 모든 클래스의 조상
- 부모가 없는 클래스는 자동적으로 Object클래스를 상속받게 된다.
- 모든 클래스는 Object클래스에 정의된 11개의 메소드를 상속받는다.
toString(), equals(Object obj), hashCode() 등등...
ex)
class MyPoint {
int x,y;
}
//class Circle extends MyPoint { // 상속
// int r;
//}
class Circle { // 포함
MyPoint P = new MyPoint(); // 참조변수의 초기화
int r;
}
public class InheritanceTest {
public static void main(String[] args) {
Circle c = new Circle();
Circle c2 = new Circle();
System.out.println(c.toString());
System.out.println(c);
}
}
⦁ 메소드 오버라이딩(overriding)
- 상속받은 조상의 메소드를 자신에 맞게 변경하는 것
내용만 변경 가능(구현부, { } ), 선언부 변경 불가
ex)
class MyPoint3 {
int x,y;
MyPoint3 (int x, int y) {
this.x=x;
this.y=y;
}
public String toString() { // Object 클래스의 toString()을 오버라이딩
return "x:" + x + ", y:" + y;
}
}
public class OverrideTest {
public static void main(String[] args) {
MyPoint3 p = new MyPoint3(1,2);
System.out.println(p);
// MyPoint3 p = new MyPoint3();
// p.x = 1;
// p.y = 2;
// System.out.println("p.x=" + p.x);
// System.out.println("p.y=" + p.y);
}
}
⦁ 오버라이딩의 조건 (★꼭 숙지★)
1. 선언부(반환타입, 메소드이름, 매개변수 목록)가 조상 클래스의 메소드와 일치해야 한다.
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) {} // 오버로딩
void childMethod() {} // 중복정의, 에러
}
⦁ 참조변수 super
- 객체 자신을 가리키는 참조변수. 인스턴스 메소드(생성자) 내에서만 존재
- 조상의 멤버를 자신의 멤버와 구별할 때 사용
class Parent {
int x = 10; // super.x
}
class Child extends Parent {
int x = 20; // this.x
void method() {
System.out.println("x=" + x);
System.out.println("this.x=" + this.x);
System.out.println("super.x=" + super.x);
}
}
public class Ex7_2 {
public static void main(String[] args) {
Child c = new Child();
c.method();
}
}
결과
x=20
this.x=20
super.x=10
ex) this와 super 둘다 해당되는 경우
class Parent2 {
int x = 10; // super.x와 this.x 둘 다 가능
}
class Child2 extends Parent2 {
void method() {
System.out.println("x=" + x);
System.out.println("this.x=" + this.x);
System.out.println("super.x=" + super.x);
}
}
public class Ex7_3 {
public static void main(String[] args) {
Child2 c = new Child2();
c.method();
}
}
결과
x=10
this.x=10
super.x=10
⦁ super() - 조상의 생성자
- 조상의 생성자를 호출할 때 사용
- 조상의 멤버는 조상의 생성자를 호출해서 초기화해야 한다. ★
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z;
Point3D (int x, int y, int z) {
super(x, y); // 조상 클래스의 생성자 Point(int x, int y)를 호출
this.z = z; // 자신의 멤버를 초기화
}
}
- 생성자의 첫 줄에 반드시 생성자를 호출해야 한다. ★★★
그렇지 않으면 컴파일러가 생성자의 첫 줄에 super();를 삽입한다.

class Point {
int x;
int y;
Point (int x, int y) {
super();
this.x=x;
this.y=y;
}
String getLocation() {
return "x : " + x + ", y : " + y;
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) {
super(x, y);
this.z=z;
}
String getLocation() {
return "x : " + x + ", y : " + y + ", z : " + z;
}
}
public class PointTest {
public static void main(String[] args) {
Point3D p3 = new Point3D(1,2,3);
System.out.println(p3.getLocation());
}
}
⦁ 패키지(package)
- 서로 관련된 클래스의 묶음
- 클래스는 클래스 파일(*.class), 패키지는 폴더. 하위 패키지는 하위 폴더
- 클래스의 실제 이름(full name)은 패키지를 포함한다.(java.lang.String)
⦁ 패키지의 선언
- 패키지는 소스파일의 첫 번째 문장으로 단 한번 선언
- 같은 소스 파일의 클래스들은 모두 같은 패키지에 속하게 된다.
- 패키지 선언이 없으면 이름없는(unnamed) 패키지에 속하게 된다.
package com.codechobo.book;
public class PackageTest {
public static void main(String[] args) {
System.out.println(“Hello, world!”);
}
}
class PackageTest2 {}
⦁ 클래스 패스(classpath)
- 클래스 파일(*.class)의 위치를 알려주는 경로(path)
- 환경변수 classpath로 관리하며, 경로간의 구분자는 ‘;’를 사용
classpath(환경변수)에 패키지의 루트를 등록해줘야 함.
⦁ import문
(이클립스 단축기 ctrl + shift + o 로 자동으로 import문 생성 가능)
- 클래스를 사용할 때 패키지 이름을 생략할 수 있다.
- 컴파일러에게 클래스가 속한 패키지를 알려준다.
- java.lang패키지의 클래스는 import 하지 않고도 사용할 수 있다.
ex) String, Object, System, Thread ...
⦁ import문의 선언
- import문을 선언하는 방법은 다음과 같다.
import 패키지명.클래스명; 또는 import 패키지명.*;
- import문은 패키지문과 클래스선언의 사이에 선언한다.
- import문은 컴파일 시에 처리되므로 프로그램의 성능에 영향없음.
- 이름이 같은 클래스가 속한 두 패키지를 import 할 때는 클래스 앞에 패키지명을 붙여줘야 한다.
import java.sql.*;
import java.util.*;
public class ImportTest {
public static void main(string[] args) {
java.util.Date today = new java.util.Date();
}
}
⦁ 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만으로 참조가능
ex)
import static java.lang.System.out;
import static java.lang.Math.*;
public class Ex7_6 {
public static void main(String[] args) {
// System.out.println(Math.random());
out.println(random());
// System.out.println("Math.PI :" + Math.PI);
out.println("Math.PI :" + PI);
}
}
⦁ 제어자(modifier)
- 클래스와 클래스의 멤버(멤버 변수, 메소드)에 부가적인 의미 부여
접근 제어자 public, protected, (default), private
그 외 static, final, abstract, native, transient, synchronized, volatile, strictfp
- 하나의 대상에 여러 제어자를 같이 사용가능(접근 제어자는 하나만)
public class ModifierTest {
public static final int WIDTH = 200;
public static void main(String args) {
System.out.println(“WIDTH=”+WIDTH);
}
}
⦁ static – 클래스의, 공통적인

⦁ final – 마지막의, 변경될 수 없는

final class FinalTest { // 조상이 될 수 없는 클래스
final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수(상수)
final void getMaxSize() { // 오버라이딩 할 수 없는 메소드(변경불가)
final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수(상수)
return MAX_SIZE;
⦁ abstract – 추상의, 미완성의

abstract class AbstractTest { // 추상 클래스(추상 메소드를 포함한 클래스)
abstract void move(); // 추상 메소드(구현부가 없는 메소드)
}
미완성 클래스(미완성 설계도)이므로 인스턴스 생성 불가(제품 생성 불가)
추상 클래스를 상속 받아서 완전한 클래스를 만든 후에 객체 생성 가능
⦁ 접근 제어자(access modifier)
private 같은 클래스 내에서만 접근이 가능하다.
(default) 같은 패키지 내에서만 접근이 가능하다.
protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.
public 접근 제한이 전혀 없다.
4개중 단 하나만 사용 가능.
class 앞에 붙을 수 있는건 public과 (default) 뿐이고 멤버에는 4가지가 다 붙을 수 있다.
ex)
package pkg1;
public class MyParent {
private int prv; // 같은 클래스
int dft; // 같은 패키지
protected int prt; // 같은 패키지+자손
public int pub; // 접근 제한 없음.
public void printMembers() {
System.out.println(prv); // OK
System.out.println(dft); // OK
System.out.println(prt); // OK
System.out.println(pub); // OK
}
public static void main(String[] args) {
MyParent p = new MyParent();
// System.out.println(p.prv); // 에러, private는 같은 클래스 내에서만 사용 가능
System.out.println(p.dft); // OK
System.out.println(p.prt); // OK
System.out.println(p.pub); // OK
}
}
package pkg2;
import pkg1.MyParent;
// ctrl + shift + o 눌러서 import를 해줘야 한다.
class MyChild extends MyParent {
public void printMembers() {
// System.out.println(prv); // 에러
// System.out.println(dft); // 에러
System.out.println(prt); // OK, 다른패키지의 자손 클래스에서 접근 가능
System.out.println(pub); // OK
}
}
public class MyParentTest2 {
public static void main(String[] args) {
MyParent p = new MyParent();
// System.out.println(p.prv); // 에러
// System.out.println(p.dft); // 에러 다른 패키지의 다른 클래스라서 3개 에러
// System.out.println(p.prt); // 에러
System.out.println(p.pub); // OK
}
}
⦁ 캡슐화와 접근 제어자
접근 제어자를 사용하는 이유
- 외부로부터 데이터를 보호하기 위해서
- 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서
public class Time {
private int hour;
private int minute; // 접근제어자를 private로 하여 외부에서 직접 접근하지 못하도록 한다. 메소드를 통하여 간접접근 허용
private int second;
// 메소드는 public으로 하여 private에 간접접근 할 수 있게 한다.
public int getHour() {
return hour;
}
public void setHour(int hour) {
if (hour < 0 || hour > 23) return; // 값을 보호
this.hour = hour;
}
ex)
class Time {
private int hour; // 0~23 사이의 값을 가져야 함.
private int minute;
private int second;
public void setHour(int hour) {
if(isNotValidHour(hour)) return;
this.hour = hour;
}
// 매개변수로 넘겨진 hour가 유효한지 확인해서 알려주는 메소드, alt + shift + m 으로 추출했다.
private boolean isNotValidHour(int hour) { // 클래스 내부에서만 쓰는 메소드는 private로 접근범위를 줄이는게 좋다.
return hour < 0 || hour > 23;
}
public int getHour() {
return hour;
}
}
public class TimeTest {
public static void main(String[] args) {
Time t = new Time();
t.setHour(21); // hour의 값을 21로 변경
System.out.println(t.getHour());
t.setHour(100);
System.out.println(t.getHour());
}
}
⦁ 다형성 (polymorphism)
- 여러 가지 형태를 가질 수 있는 능력
- 조상 타입 참조 변수로 자손 타입 객체를 다루는 것
Tv t = new SmartTv(); // 조상 타입 참조변수 t로 자손 타입 객체 SmartTv를 다룬다.
- 객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이?
SmartTv s = new SmartTv(); // 모든 멤버 사용 가능
Tv t = new SmartTv(); // Tv가 가지고 있는 멤버만 사용 가능
- 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 없다.
Tv t = new SmartTv(); // Ok. 허용
SmartTv s = new Tv(); // 에러, 허용 안 됨.
⦁ 참조변수의 형변환
- 사용할 수 있는 멤버의 개수를 조절하는 것
- 조상-자손 관계의 참조변수는 서로 형변환 가능
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!!!");
}
}
class Ambulance extends Car {
}
public class Pr {
public static void main(String[] args) {
FireEngine f = new FireEngine();
Car c = (Car)f; // OK. 조상인 Car타입으로 형변환
FireEngine f2 = (FireEngine)c; // OK. 자손인 FireEngine타입으로 형변환
// Ambulance a = (Ambulance)f; // 에러, 상속관계가 아닌 클래스 간의 형변환 불가
}
}
c는 Car에 속한 멤버 4개만 사용 가능
f2는 다시 멤버 5개 사용 가능
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!!!");
}
}
public class Ex7_7 {
public static void main(String[] args) {
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water();
car = (Car)fe;
// car.water(); // 에러, Car타입의 참조변수로는 water()를 호출할 수 없다.
fe2 = (FireEngine)car;
fe2.water();
}
}
출처 : 남궁성의 정석코딩 Youtube
'Java > Java의 정석' 카테고리의 다른 글
220310 Java - Chapter 7. 객체지향개념 II Part.3 (0) | 2022.03.11 |
---|---|
220309 Java - Chapter 7. 객체지향개념 II Part.2 (0) | 2022.03.10 |
220307 Java - Chapter 6. 객체지향 개념I Part. 2 (0) | 2022.03.07 |
220305 Java - Chapter 6. 객체지향 개념I Part.1 (0) | 2022.03.05 |
220304 Java - Chapter 5. 배열 (0) | 2022.03.04 |