객체지향 프로그래밍 (OOP)
Java는 객체지향 프로그래밍(Object-Oriented Programming) 언어입니다. 객체지향이란 현실 세계의 사물을 프로그램 속 객체로 표현하는 방식입니다.
자동차를 생각해보세요. 자동차는 색상, 속도, 연료량 같은 속성(데이터)과 출발하기, 멈추기, 가속하기 같은 기능(메서드)을 가지고 있습니다. 이처럼 속성과 기능을 하나로 묶어 표현한 것이 객체입니다.
객체지향의 4대 특징
| 특징 | 설명 |
|---|---|
| 캡슐화 (Encapsulation) | 데이터와 메서드를 하나로 묶고, 외부에서 직접 접근을 제한 |
| 상속 (Inheritance) | 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스 생성 |
| 다형성 (Polymorphism) | 같은 이름의 메서드가 상황에 따라 다르게 동작 |
| 추상화 (Abstraction) | 복잡한 내부 구현을 숨기고 핵심 기능만 노출 |
클래스와 객체
클래스란?
클래스(Class)는 객체를 만들기 위한 설계도(틀)입니다. 클래스에는 객체가 가질 속성(변수)과 기능(메서드)이 정의됩니다.
// 클래스 정의 (설계도)
public class Car {
// 속성 (필드, 멤버 변수)
String color;
int speed;
// 기능 (메서드)
void accelerate() {
speed += 10;
}
void brake() {
speed -= 10;
}
}
객체란?
객체(Object)는 클래스를 바탕으로 실제로 생성된 인스턴스(Instance)입니다. 하나의 클래스로 여러 객체를 만들 수 있습니다.
클래스 (설계도) 객체 (실체)
┌─────────────┐ ┌─────────────┐
│ Car │ ───→ │ myCar │
│ - color │ │ color="빨강" │
│ - speed │ │ speed=60 │
│ + accelerate│ └─────────────┘
│ + brake │ ┌─────────────┐
└─────────────┘ ───→ │ yourCar │
│ color="파랑" │
│ speed=80 │
└─────────────┘
객체 생성
new 키워드를 사용하여 객체를 생성합니다.
public class CarTest {
public static void main(String[] args) {
// 객체 생성
Car myCar = new Car();
// 속성 설정
myCar.color = "빨강";
myCar.speed = 0;
// 메서드 호출
myCar.accelerate();
System.out.println("속도: " + myCar.speed); // 속도: 10
myCar.accelerate();
System.out.println("속도: " + myCar.speed); // 속도: 20
myCar.brake();
System.out.println("속도: " + myCar.speed); // 속도: 10
}
}
여러 객체 생성
public class CarTest {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
car1.color = "빨강";
car1.speed = 100;
car2.color = "파랑";
car2.speed = 80;
System.out.println("car1: " + car1.color + ", " + car1.speed + "km/h");
System.out.println("car2: " + car2.color + ", " + car2.speed + "km/h");
}
}
출력:
car1: 빨강, 100km/h
car2: 파랑, 80km/h
• 클래스: 붕어빵 틀 (설계도)
• 객체: 붕어빵 틀로 만든 각각의 붕어빵 (실체)
하나의 틀(클래스)로 여러 개의 붕어빵(객체)을 만들 수 있습니다.
인스턴스 변수와 클래스 변수
인스턴스 변수
인스턴스 변수는 각 객체마다 독립적으로 가지는 변수입니다.
public class Student {
String name; // 인스턴스 변수
int score; // 인스턴스 변수
}
Student s1 = new Student();
Student s2 = new Student();
s1.name = "홍길동";
s1.score = 90;
s2.name = "김철수";
s2.score = 85;
// s1과 s2는 각자의 name, score를 가짐
클래스 변수 (static)
클래스 변수는 모든 객체가 공유하는 변수입니다. static 키워드를 사용합니다.
public class Student {
String name; // 인스턴스 변수
int score; // 인스턴스 변수
static int studentCount; // 클래스 변수 (공유)
}
Student s1 = new Student();
Student.studentCount++; // 클래스 이름으로 접근
Student s2 = new Student();
Student.studentCount++;
System.out.println(Student.studentCount); // 2
인스턴스 변수 vs 클래스 변수
| 구분 | 인스턴스 변수 | 클래스 변수 (static) |
|---|---|---|
| 선언 | int num; |
static int num; |
| 생성 시점 | 객체 생성 시 | 클래스 로드 시 |
| 메모리 위치 | 힙(Heap) | 메서드 영역 |
| 공유 여부 | 객체마다 개별 | 모든 객체가 공유 |
| 접근 방법 | 객체명.변수 |
클래스명.변수 |
접근 제어자
접근 제어자(Access Modifier)는 클래스, 변수, 메서드에 대한 접근 범위를 제한합니다.
접근 제어자 종류
| 접근 제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스 | 전체 |
|---|---|---|---|---|
public |
O | O | O | O |
protected |
O | O | O | X |
(default) |
O | O | X | X |
private |
O | X | X | X |
public class Person {
public String name; // 어디서든 접근 가능
protected int age; // 같은 패키지 + 자식 클래스
String address; // 같은 패키지만 (default)
private String password; // 같은 클래스만
}
캡슐화와 정보 은닉
캡슐화는 데이터를 private으로 숨기고, public 메서드(getter/setter)를 통해 접근하는 것입니다.
public class BankAccount {
private int balance; // 외부에서 직접 접근 불가
// Getter: 값을 반환
public int getBalance() {
return balance;
}
// Setter: 값을 설정 (유효성 검사 가능)
public void setBalance(int balance) {
if (balance >= 0) {
this.balance = balance;
}
}
// 입금
public void deposit(int amount) {
if (amount > 0) {
balance += amount;
}
}
// 출금
public void withdraw(int amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
}
}
}
public class BankTest {
public static void main(String[] args) {
BankAccount account = new BankAccount();
// account.balance = 1000; // 오류! private 접근 불가
account.setBalance(1000); // setter로 접근
account.deposit(500);
account.withdraw(200);
System.out.println("잔액: " + account.getBalance()); // 1300
}
}
• 데이터 보호: 잘못된 값이 들어가는 것을 방지
• 유지보수: 내부 구현 변경 시 외부 코드에 영향 없음
• 유효성 검사: setter에서 값을 검증할 수 있음
메서드 정의 및 호출
메서드란?
메서드(Method)는 특정 작업을 수행하는 코드 블록입니다. 재사용 가능하고, 코드를 구조화하는 데 도움이 됩니다.
메서드 정의
접근제어자 반환타입 메서드명(매개변수) {
// 실행할 코드
return 반환값; // 반환타입이 void가 아닌 경우
}
메서드 예제
public class Calculator {
// 반환값이 없는 메서드
public void printHello() {
System.out.println("Hello!");
}
// 매개변수와 반환값이 있는 메서드
public int add(int a, int b) {
return a + b;
}
// 여러 매개변수
public int multiply(int a, int b) {
return a * b;
}
// 조건에 따른 반환
public String getGrade(int score) {
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else return "F";
}
}
메서드 호출
public class CalculatorTest {
public static void main(String[] args) {
Calculator calc = new Calculator();
calc.printHello(); // Hello!
int sum = calc.add(10, 20);
System.out.println("합계: " + sum); // 합계: 30
int product = calc.multiply(5, 4);
System.out.println("곱: " + product); // 곱: 20
String grade = calc.getGrade(85);
System.out.println("학점: " + grade); // 학점: B
}
}
메서드 오버로딩
메서드 오버로딩(Overloading)은 같은 이름의 메서드를 매개변수의 타입이나 개수를 다르게 하여 여러 개 정의하는 것입니다.
public class Calculator {
// 정수 2개 덧셈
public int add(int a, int b) {
return a + b;
}
// 정수 3개 덧셈
public int add(int a, int b, int c) {
return a + b + c;
}
// 실수 덧셈
public double add(double a, double b) {
return a + b;
}
}
Calculator calc = new Calculator();
System.out.println(calc.add(10, 20)); // 30 (int, int)
System.out.println(calc.add(10, 20, 30)); // 60 (int, int, int)
System.out.println(calc.add(1.5, 2.5)); // 4.0 (double, double)
• 메서드 이름이 같아야 함
• 매개변수의 타입, 개수, 순서 중 하나 이상이 달라야 함
• 반환 타입만 다른 것은 오버로딩이 아님
생성자
생성자란?
생성자(Constructor)는 객체가 생성될 때 자동으로 호출되는 특별한 메서드입니다. 객체의 초기화를 담당합니다.
생성자의 특징
- 클래스 이름과 동일
- 반환 타입이 없음 (void도 쓰지 않음)
- 객체 생성 시
new키워드와 함께 호출
기본 생성자
매개변수가 없는 생성자입니다. 클래스에 생성자가 하나도 없으면 컴파일러가 자동으로 기본 생성자를 추가합니다.
public class Person {
String name;
int age;
// 기본 생성자
public Person() {
name = "이름 없음";
age = 0;
}
}
Person p = new Person(); // 기본 생성자 호출
System.out.println(p.name); // 이름 없음
System.out.println(p.age); // 0
매개변수가 있는 생성자
public class Person {
String name;
int age;
// 매개변수가 있는 생성자
public Person(String name, int age) {
this.name = name; // this: 현재 객체
this.age = age;
}
}
Person p = new Person("홍길동", 25);
System.out.println(p.name); // 홍길동
System.out.println(p.age); // 25
this 키워드
this는 현재 객체 자신을 가리킵니다. 매개변수와 인스턴스 변수의 이름이 같을 때 구분하기 위해 사용합니다.
public class Student {
String name;
int score;
public Student(String name, int score) {
this.name = name; // this.name: 인스턴스 변수
this.score = score; // name: 매개변수
}
}
생성자 오버로딩
생성자도 오버로딩할 수 있습니다. 다양한 방법으로 객체를 초기화할 수 있습니다.
public class Book {
String title;
String author;
int price;
// 기본 생성자
public Book() {
this.title = "제목 없음";
this.author = "저자 미상";
this.price = 0;
}
// 제목만 받는 생성자
public Book(String title) {
this.title = title;
this.author = "저자 미상";
this.price = 0;
}
// 모든 값을 받는 생성자
public Book(String title, String author, int price) {
this.title = title;
this.author = author;
this.price = price;
}
public void showInfo() {
System.out.println(title + " / " + author + " / " + price + "원");
}
}
Book book1 = new Book();
Book book2 = new Book("Java 입문");
Book book3 = new Book("Java 마스터", "홍길동", 25000);
book1.showInfo(); // 제목 없음 / 저자 미상 / 0원
book2.showInfo(); // Java 입문 / 저자 미상 / 0원
book3.showInfo(); // Java 마스터 / 홍길동 / 25000원
this()를 이용한 생성자 호출
한 생성자에서 다른 생성자를 호출할 때 this()를 사용합니다. 첫 줄에서만 사용 가능합니다.
public class Book {
String title;
String author;
int price;
public Book() {
this("제목 없음", "저자 미상", 0); // 다른 생성자 호출
}
public Book(String title) {
this(title, "저자 미상", 0);
}
public Book(String title, String author, int price) {
this.title = title;
this.author = author;
this.price = price;
}
}
상속
상속이란?
상속(Inheritance)은 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 만드는 것입니다.
- 부모 클래스 (Super Class): 상속을 해주는 클래스
- 자식 클래스 (Sub Class): 상속을 받는 클래스
extends 키워드
// 부모 클래스
public class Animal {
String name;
public void eat() {
System.out.println(name + "이(가) 먹습니다.");
}
public void sleep() {
System.out.println(name + "이(가) 잡니다.");
}
}
// 자식 클래스
public class Dog extends Animal {
// 자식 클래스만의 메서드
public void bark() {
System.out.println(name + "이(가) 멍멍 짖습니다.");
}
}
// 자식 클래스
public class Cat extends Animal {
public void meow() {
System.out.println(name + "이(가) 야옹 웁니다.");
}
}
public class AnimalTest {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "바둑이";
dog.eat(); // 바둑이이(가) 먹습니다.
dog.sleep(); // 바둑이이(가) 잡니다.
dog.bark(); // 바둑이이(가) 멍멍 짖습니다.
Cat cat = new Cat();
cat.name = "나비";
cat.eat(); // 나비이(가) 먹습니다.
cat.meow(); // 나비이(가) 야옹 웁니다.
}
}
상속의 장점
Animal
/ \
Dog Cat
- 코드 재사용: 공통 코드를 부모 클래스에 작성
- 유지보수 용이: 공통 기능 수정 시 부모 클래스만 수정
- 확장성: 새로운 자식 클래스 추가 용이
메서드 오버라이딩
오버라이딩(Overriding)은 부모 클래스의 메서드를 자식 클래스에서 재정의하는 것입니다.
public class Animal {
String name;
public void sound() {
System.out.println("동물 소리");
}
}
public class Dog extends Animal {
@Override // 오버라이딩 어노테이션 (권장)
public void sound() {
System.out.println("멍멍!");
}
}
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("야옹!");
}
}
Animal dog = new Dog();
Animal cat = new Cat();
dog.sound(); // 멍멍!
cat.sound(); // 야옹!
• 오버라이딩(Overriding): 부모의 메서드를 자식이 재정의 (같은 이름, 같은 매개변수)
• 오버로딩(Overloading): 같은 이름의 메서드를 여러 개 정의 (다른 매개변수)
super 키워드
super는 부모 클래스를 가리킵니다.
1. 부모의 변수나 메서드 접근:
public class Parent {
String name = "부모";
public void show() {
System.out.println("Parent의 show()");
}
}
public class Child extends Parent {
String name = "자식";
@Override
public void show() {
System.out.println("Child의 show()");
}
public void display() {
System.out.println(name); // 자식
System.out.println(super.name); // 부모
show(); // Child의 show()
super.show(); // Parent의 show()
}
}
2. 부모의 생성자 호출 (super()):
자식 클래스의 생성자에서 super()를 사용하여 부모 생성자를 호출합니다.
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Student extends Person {
String school;
public Student(String name, int age, String school) {
super(name, age); // 부모 생성자 호출 (첫 줄에서!)
this.school = school;
}
public void showInfo() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
System.out.println("학교: " + school);
}
}
Student student = new Student("홍길동", 20, "서울대학교");
student.showInfo();
출력:
이름: 홍길동
나이: 20
학교: 서울대학교
• super()는 생성자의 첫 줄에서만 호출 가능
• 명시하지 않으면 컴파일러가 자동으로 super() 삽입
• 부모에 기본 생성자가 없으면 반드시 super(매개변수) 호출 필요
종합 실습
문제 1 - 객체 생성 (기초)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Counter {
int count = 0;
void increase() { count++; }
}
public class Test {
public static void main(String[] args) {
Counter c = new Counter();
c.increase();
c.increase();
c.increase();
System.out.println(c.count);
}
}
문제 2 - 생성자 (기초)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Person {
String name;
Person(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person("홍길동");
System.out.println(p.name);
}
}
문제 3 - 메서드 오버로딩 (기초)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Calculator {
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
}
public class Test {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(1, 2, 3));
}
}
문제 4 - static 변수 (중급)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Item {
static int count = 0;
Item() { count++; }
}
public class Test {
public static void main(String[] args) {
Item i1 = new Item();
Item i2 = new Item();
Item i3 = new Item();
System.out.println(Item.count);
}
}
문제 5 - 상속 기본 (중급)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Animal {
void sound() { System.out.print("동물"); }
}
class Dog extends Animal {
void sound() { System.out.print("멍멍"); }
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.sound();
}
}
문제 6 - super 키워드 (중급)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Parent {
int num = 10;
}
class Child extends Parent {
int num = 20;
void show() {
System.out.println(super.num + this.num);
}
}
public class Test {
public static void main(String[] args) {
Child c = new Child();
c.show();
}
}
문제 7 - 생성자 체이닝 (고급)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class A {
A() { System.out.print("A"); }
}
class B extends A {
B() { System.out.print("B"); }
}
class C extends B {
C() { System.out.print("C"); }
}
public class Test {
public static void main(String[] args) {
C c = new C();
}
}
문제 8 - 복합 문제 (고급)
다음 Java 프로그램의 실행 결과는 무엇입니까?
class Shape {
int getArea() { return 0; }
}
class Rectangle extends Shape {
int width, height;
Rectangle(int w, int h) {
width = w;
height = h;
}
int getArea() { return width * height; }
}
public class Test {
public static void main(String[] args) {
Shape s = new Rectangle(5, 4);
System.out.println(s.getArea());
}
}
실전 프로그래밍
과제 1: 학생 관리 클래스
학생 정보를 관리하는 Student 클래스를 작성하세요.
요구사항:
- 필드: 이름(name), 학번(studentId), 점수(score)
- 생성자: 이름과 학번을 받는 생성자
- 메서드: setScore(점수 설정), getGrade(학점 반환)
- 학점 기준: 90이상 A, 80이상 B, 70이상 C, 60이상 D, 미만 F
예시 답안 보기
public class Student {
private String name;
private String studentId;
private int score;
public Student(String name, String studentId) {
this.name = name;
this.studentId = studentId;
this.score = 0;
}
public void setScore(int score) {
if (score >= 0 && score <= 100) {
this.score = score;
}
}
public String getGrade() {
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else if (score >= 60) return "D";
else return "F";
}
public void showInfo() {
System.out.println("이름: " + name);
System.out.println("학번: " + studentId);
System.out.println("점수: " + score);
System.out.println("학점: " + getGrade());
}
public static void main(String[] args) {
Student s = new Student("홍길동", "2024001");
s.setScore(85);
s.showInfo();
}
}
출력:
이름: 홍길동
학번: 2024001
점수: 85
학점: B
과제 2: 도형 상속 구조
Shape 클래스를 상속받아 Rectangle과 Circle 클래스를 작성하세요.
요구사항:
- Shape: 추상 클래스가 아닌 일반 클래스, getArea() 메서드 (0 반환)
- Rectangle: width, height 필드, getArea() 오버라이딩
- Circle: radius 필드, getArea() 오버라이딩 (π = 3.14)
- 각 클래스에 적절한 생성자 추가
예시 답안 보기
class Shape {
public double getArea() {
return 0;
}
}
class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public double getArea() {
return 3.14 * radius * radius;
}
}
public class ShapeTest {
public static void main(String[] args) {
Shape rect = new Rectangle(5, 4);
Shape circle = new Circle(3);
System.out.println("사각형 넓이: " + rect.getArea());
System.out.println("원 넓이: " + circle.getArea());
}
}
출력:
사각형 넓이: 20.0
원 넓이: 28.26
과제 3: 은행 계좌 시스템
BankAccount 클래스를 작성하고, SavingsAccount(저축 계좌)를 상속받아 구현하세요.
요구사항:
- BankAccount: 계좌번호, 잔액, 입금(deposit), 출금(withdraw) 메서드
- SavingsAccount: 이자율(interestRate) 추가, addInterest() 메서드
- 출금 시 잔액 부족하면 출금 불가
- super()를 사용하여 부모 생성자 호출
예시 답안 보기
class BankAccount {
protected String accountNumber;
protected int balance;
public BankAccount(String accountNumber, int initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public void deposit(int amount) {
if (amount > 0) {
balance += amount;
System.out.println(amount + "원 입금. 잔액: " + balance + "원");
}
}
public void withdraw(int amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
System.out.println(amount + "원 출금. 잔액: " + balance + "원");
} else {
System.out.println("출금 불가. 잔액 부족.");
}
}
public int getBalance() {
return balance;
}
}
class SavingsAccount extends BankAccount {
private double interestRate;
public SavingsAccount(String accountNumber, int initialBalance, double interestRate) {
super(accountNumber, initialBalance);
this.interestRate = interestRate;
}
public void addInterest() {
int interest = (int)(balance * interestRate);
balance += interest;
System.out.println("이자 " + interest + "원 추가. 잔액: " + balance + "원");
}
}
public class BankTest {
public static void main(String[] args) {
SavingsAccount account = new SavingsAccount("123-456", 100000, 0.05);
account.deposit(50000);
account.withdraw(30000);
account.addInterest();
System.out.println("최종 잔액: " + account.getBalance() + "원");
}
}
출력:
50000원 입금. 잔액: 150000원
30000원 출금. 잔액: 120000원
이자 6000원 추가. 잔액: 126000원
최종 잔액: 126000원
핵심 요약
이번 챕터에서 배운 내용
- 클래스와 객체
class Car { } // 클래스 (설계도) Car myCar = new Car(); // 객체 (인스턴스) - 접근 제어자
제어자 접근 범위 public모든 곳 protected같은 패키지 + 자식 클래스 (default) 같은 패키지 private같은 클래스 - 메서드 정의
public int add(int a, int b) { return a + b; } - 생성자
public Person(String name) { this.name = name; } - 상속
class Child extends Parent { // 부모의 속성과 메서드 상속 } - super 키워드
super.변수 // 부모의 변수 super.메서드() // 부모의 메서드 super() // 부모의 생성자 - 주요 포인트
- 클래스는 설계도, 객체는 실체
- 캡슐화: private + getter/setter
- 오버로딩: 같은 이름, 다른 매개변수
- 오버라이딩: 부모 메서드 재정의 (@Override)
- super()는 생성자 첫 줄에서만 호출
- 생성자 호출 순서: 부모 → 자식
다음 챕터 예고
다음 챕터에서는 추상 클래스와 인터페이스를 배웁니다.
- 추상 클래스 (abstract class)
- 추상 메서드
- 인터페이스 (interface)
- 인터페이스 구현 (implements)
- 다중 상속과 인터페이스
- 디폴트 메서드
클래스와 객체 학습 완료!
객체지향 프로그래밍의 핵심인 클래스, 객체, 상속을 배웠습니다.
이제 추상 클래스와 인터페이스를 배우면 더욱 유연한 설계가 가능합니다!