객체지향 프로그래밍(OOP)
- 문제 혹은 로직을 객체 단위로 나눠서 작성하는 방법 (절차지향 프로그래밍과 반대 개념이 아니라 접근 방식의 차이가 있음)
- 객체들이 서로 유기적으로 상호작용하는 프로그래밍 이론
JAVA, C#...
ex) 자동차(객체) = 데이터(바퀴, 핸들, 차체 등) + 기능(길찾기, 주행보조 등)
특징
- 캡슐화
데이터의 구조와 기능을 하나의 캡슐형태로 만들어 외부에서는 형태를 알 수 없도록 하는 방법
- 추상화
객체의 공통적인 필드와 기능을 하나의 형태로 정의하는 설계 방법
- 상속
하위 클래스에서는 상속 받은 상위 클래스의 기능을 사용할 수 있고, 하위 클래스 자체에 새로운 기능을 추가할 수 있는 것
- 다형성
객체가 상속을 통해 기능을 확장하거나 변경하여 다른 형태로 재구성되는 것(오버로딩/오버라이딩)
장점
- 코드 재사용성 증가
- 생산성 향상
- 유지보수가 쉬움
단점
- 개발 속도가 느림
- 실행 속도가 느림(객체 단위로 구성되어 있기 때문)
- 코드의 난이도 상승
클래스
- 객체를 정의하는 설계도
- 객체 변수, 메소드로 이루어짐
- 사용자 정의 타입
사용자가 직접 설계하고, 데이터와 기능이 조합되고, 메모리에 생성되기 때문이다.
- 데이터와 기능의 조합
- 객체를 생성하는 용도
클래스를 메모리에 생성시켜야 객체라는 형태로 사용할 수 있다.
💡
클래스 자체는 객체를 생성하기 위한 설계도나 템플릿으로 생각할 수 있다.
이 설계도를 바탕으로 실제로 작동하는 객체(인스턴스)를 만들기 위해서는 메모리에 그 객체를 실제로 생성해야 한다.
[과정]
클래스 정의: 클래스는 객체의 속성(변수)과 행동(메소드)을 정의한다. 이 단계에서는 실제 메모리 할당이 일어나지 않는다. 클래스는 단지 객체를 생성하는 데 필요한 정보를 담고 있을 뿐이다.
객체 생성: 프로그램이 실행될 때, 클래스를 기반으로 객체(인스턴스)가 생성된다. 이때 실제 메모리 할당이 일어나며, 이 메모리에는 클래스에 정의된 속성들의 실제 값이 저장된다.
객체 사용: 생성된 객체는 메모리에 존재하며, 프로그램에서 이 객체를 사용하여 다양한 작업을 수행할 수 있다. 객체의 메소드를 호출하거나, 객체의 변수를 읽고 쓰는 등의 작업을 할 수 있다.
간단히 말해서, 클래스는 '어떤 것'을 만들기 위한 계획이나 설계도이며, 이 클래스를 바탕으로 생성된 객체는 메모리에 실제로 존재하는 '어떤 것'의 구체적인 예이다. 객체가 메모리에 생성될 때, 클래스에 정의된 속성과 메소드가 실제로 사용 가능한 형태가 된다.
객체, 인스턴스
객체(Object)
- 실체
- 클래스에 정의된 내용에 맞춰 메모리에 생성된 형태
- 정의한 클래스 내용에 따라 속성과 기능이 다름
인스턴스(Instance)
- 클래스와 객체의 관계
- 클래스로부터 어떤 객체를 선언(인스턴스화)
- 어떤 객체는 어떤 클래스의 인스턴스
클래스 변수와 인스턴스 변수
변수 초기화
- 변수를 선언하고 최초로 값을 지정하는 것
- 멤버 변수(클래스 변수, 인스턴스 변수)는 초기화 생략 가능
- 지역 변수는 반드시 초기화 해줘야 함.
💡
지역 변수는 메소드 내에서 선언되며, 메소드가 호출될 때 생성되고, 메소드가 종료되면 소멸한다. 이러한 지역 변수는 자동으로 초기화되지 않는다. 프로그래밍 언어에 따라 다를 수 있지만, 대부분의 언어에서 지역 변수는 기본값이 없으며, 사용 전에 명시적으로 초기화해야 한다. 초기화하지 않은 지역 변수를 사용하려고 하면 컴파일러 오류가 발생할 수 있다.
지역 변수는 그 범위가 제한적이므로 사용 전에 항상 초기화하는 것이 좋다. 반면, 클래스의 멤버 변수(인스턴스 변수, 클래스 변수)는 일반적으로 기본값으로 자동 초기화되지만, 특정 값을 지정해야 하는 경우 아래와 같은 방법을 사용하여 초기화할 수 있다.
1. 명시적 초기화
int number = 10; // 명시적 초기화
2. 초기화 블록
class MyClass {
int instanceVar;
static int staticVar;
// 인스턴스 초기화 블록
{
instanceVar = 20;
}
// 정적 초기화 블록
static {
staticVar = 30;
}
}
3. 생성자
class MyClass {
int number;
MyClass() {
number = 40; // 생성자를 통한 초기화, 주로 인스턴스 변수들의 초기화 시 사용
}
}
생성자
- 객체가 생성될 때 자동으로 호출됨
- new 키워드로 객체를 생성할 때, 제일 먼저 실행되는 메소드
- 기본 생성자 외에 여러 개의 생성자를 가질 수 있음
- 생성자 규칙: 클래스명과 이름 맞추기, 반환 값은 없지만 void 형으로 적으면 안됨
기본 생성자
- 매개변수가 없고, 어떤 실행문도 없는 생성자
- 모든 클래스에는 반드시 하나 이상의 생성자가 정의
- Java compiler가 어떤 생성자도 없을 때 기본적으로 추가해줌
- 어떤 생성자가 있으면, compiler가 기본 생성자는 별도로 추가하지 않음
매개변수가 있는 생성자
- 메서드처럼 매개변수를 인자로 받아 인스턴스 초기화에 값을 사용할 수 있는 생성자
- 인스턴스 생성 후, set 메서드를 호출해주는 것보다 간결한 코드
this, this()
this
- 객체 자신
- 인스턴스 자신을 가리키는 참조변수
- 인스턴스의 주소가 저장, 숨겨진 채로 존재
- static 메서드에서는 사용 불가
💡
1. static 메서드의 특성: static 메서드는 클래스 레벨에서 정의되며, 특정 인스턴스에 속하지 않는다. 이는 static 메서드가 객체의 생성과 상관없이 호출될 수 있다는 것을 의미한다. 즉, 클래스가 메모리에 로드될 때 static 메서드가 사용 가능해진다.
2. 인스턴스와의 독립성: this는 특정 인스턴스의 주소를 나타내므로, 인스턴스가 생성될 때마다 각기 다른 this 참조가 생성된다. 반면, static 메서드는 인스턴스의 생성 여부와 무관하게 동일하게 유지된다. 따라서 static 메서드는 어떤 특정 인스턴스에 속하지 않으므로 this 참조를 가질 수 없다.
3. 접근 가능성의 제한: static 메서드는 인스턴스 멤버에 직접 접근할 수 없다. this를 사용하면 인스턴스 변수나 메서드에 접근하는 것이 가능해지기 때문에, this의 사용은 static 메서드의 정의와 상 충돌한다. static 메서드는 클래스에 속하는 멤버에만 접근할 수 있으며, 인스턴스에 속하는 멤버에는 접근할 수 없다.
결론적으로, this 키워드는 인스턴스 자신을 참조하는데, static 메서드는 인스턴스가 아닌 클래스 레벨에서 작동하기 때문에 this를 사용할 수 없다. static 메서드는 인스턴스 생성과 무관하게 동일하게 작동해야 하므로, 인스턴스에 종속적인 this 참조는 static 메서드의 맥락에서 의미가 없다.
this()
- 생성자
- 같은 클래스 내부의 다른 생성자를 호출할 때 사용
- 생성자 첫 문장에서만 사용 가능
//예시: 직원 정보 관리
// Employee 클래스를 정의하고, 직원 정보를 저장하고 검색하는 EmployeeManager 클래스를 사용
public class Employee {
private int id;
private String name;
private double salary;
public Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// Getter와 Setter 메소드
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
import java.util.HashMap;
import java.util.Map;
public class EmployeeManager {
private Map<Integer, Employee> employees = new HashMap<>();
public void addEmployee(Employee employee) {
employees.put(employee.getId(), employee);
}
public Employee getEmployee(int id) {
return employees.get(id);
}
public void printAllEmployees() {
for (Employee employee : employees.values()) {
System.out.println(employee);
}
}
}
public class Main {
public static void main(String[] args) {
EmployeeManager manager = new EmployeeManager();
manager.addEmployee(new Employee(1, "Alice", 50000));
manager.addEmployee(new Employee(2, "Bob", 60000));
manager.addEmployee(new Employee(3, "Charlie", 70000));
manager.printAllEmployees();
Employee employee = manager.getEmployee(2);
System.out.println("검색한 직원: " + employee);
}
}
Employee 클래스는 직원 정보를 저장하고, EmployeeManager 클래스는 직원 정보를 관리한다. EmployeeManager는 HashMap을 사용하여 직원 정보를 저장하고 검색하는 메소드를 제공한다. Main 클래스에서는 EmployeeManager를 사용하여 직원 정보를 추가, 출력, 검색하는 예를 보여준다.
메서드(Method)
- 어떤 기능을 수행하기 위한 명령문들의 집합체
- 구성은 크게 두 부분으로 나뉨: 선언부, 구현부
선언부
- 접근제어자, static/final keyword, 메소드 이름, 매개변수 선언, 반환타입으로 구성
구현부
- 지역변수, 수행될 실행문, return 문으로 구성
- void일 경우 return문이 없음
매개변수의 종류
- 기본형 매개변수
기본 데이터형으로 넘겨 받는 매개변수, 읽기만 가능
- 참조형 매개변수
인스턴스의 주소를 넘겨 받는 매개변수, 읽고 변경 가능
반환 값의 종류
메서드 종료 시, return이 되는 대상에 따라 나뉨
- 기본형 반환 값
기본형을 반환하는 것은 기본형 데이터를 반환
- 참조형 반환 값
참조형을 반환하는 것은 객체의 주소를 반환
JVM의 구조
- application이 동작할 때 사용되는 메모리 공간은 runtime data area
- runtime data area는 다시 5개로 나뉨
Method area: 클래스 정보와 클래스 변수 등, 모든 쓰레드가 공유하는 메모리 영역
Stack: 메서드의 작업 공간
Heap: 인스턴스가 생성되는 공간
호출 스택
- 메소드의 작업에 필요한 메모리 공간 제공
- 메소드가 작업을 수행하는 동안 지역변수들과 연산의 중간 결과 등을 저장하는데 사용
- 메소드가 작업을 마치면 할당되었던 메모리는 반환
- 호출 스택 제일 상위에 위치하는 메소드가 현재 실행 중인 메소드, 나머지는 대기
- 객체를 생성하지 않고 호출하기 위해서는 static keyword
Static/인스턴스 메서드
- Static 메서드
객체 생성 없이 호출할 수 있는 메서드(Class 이름.method 이름())
메서드 내 인스턴스 변수 사용 불가
-인스턴스 메서드
객체 생성 후, 호출할 수 있는 메서드(참조변수.method 이름())
메서드 내 인스턴스 변수 사용 가능
오버로딩(Overloading)
- 한 클래스 내에서 같은 이름의 메소드를 여러 개 정의
- 오버로딩 조건
1. 메소드의 이름이 같아야 함
2. 메소드의 개수 또는 타입이 달라야 함(리턴타입의 차이로는 오버로딩 되지 않음)
오버로딩과 가변인자
- 메소드의 매개변수를 동적으로 지정할 수 있는 기능 (>=JDK 1.5)
가변인자의 선언: 데이터타입...변수명
가변인자는 메소드 매개변수의 마지막에 선언해줘야 함
가변인자를 선언한 메소드는 되도록 오버로딩 하지 않는 것이 좋음
접근제어자
- 클래스의 변수나 메소드의 접근에 제한을 두는 키워드
제어자
- 변수, 메서드, 클래스 등에 대한 접근이나 어떤 기능적인 부분에 대해 지정해주는 것
- 하나의 대상(변수, 메서드, 클래스) 에 대해 여러 제어자를 조합하여 사용할 수 있으며, 접근제어자는 한가지만 가능
- 각 대상의 선언부에 함께 표시
- 접근 제어자
public, protected, default, private
- 그 외 제어자
static, final, abstract, native, transient, synchronized, volatile, strictfp
- 접근제어자 종류
1. private: 해당 클래스에서만 접근 가능
2. public: 어디서든 접근 가능
3. default: 해당 패키지 내에서만 접근 가능
4. protected: 해당 패키지 및 상속받은 클래스에서 접근 가능
Static
- 변수나 메소드의 특성을 바꾸는 키워드
- Static 특징
1. 메모리에 한번만 할당됨( static 변수는 클래스가 메모리에 로드될 때 생성되고, 프로그램이 종료될 때까지 존재)
2. 즉, Static 변수나 메소드는 공유되는 특성을 가짐
3. static 멤버는 인스턴스에 속하지 않으므로, this 키워드를 사용하여 접근할 수 없음
4. static 메소드 내에서는 static이 아닌 인스턴스 멤버에 직접 접근할 수 없음
5. 상수: static final 키워드를 사용하여 클래스 레벨의 상수를 정의
6. 유틸리티 클래스: 인스턴스를 생성할 필요 없이 메소드를 사용하는 클래스 (예: java.lang.Math, java.util.Arrays 등).
- Static 클래스 변수
해당 클래스의 각 객체들이 값을 공유
class Counter {
static int count = 0;
public Counter() {
count++;
}
public static int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
new Counter();
new Counter();
new Counter();
// static 변수이므로 클래스 이름으로 직접 접근 가능
System.out.println(Counter.count); // 출력: 3
// static 메소드를 사용해도 됩니다.
System.out.println(Counter.getCount()); // 출력: 3
}
}
- Static 클래스 메소드
객체를 생성하지 않아도 호출 가능
class MathUtil {
public static int add(int a, int b) {
return a + b;
}
public static int subtract(int a, int b) {
return a - b;
}
}
public class Main {
public static void main(String[] args) {
// 인스턴스 생성 없이 static 메소드 호출
System.out.println(MathUtil.add(5, 3)); // 출력: 8
System.out.println(MathUtil.subtract(5, 3)); // 출력: 2
}
}
public class Calculator {
// static 변수 (클래스 레벨의 변수)
public static final double PI = 3.14159265359;
// 오버로딩된 메소드 (매개변수의 타입이 다름)
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
// static 메소드 (객체 생성 없이 호출 가능)
public static double circumference(double radius) {
return 2 * PI * radius;
}
// private 메소드 (클래스 내부에서만 사용 가능)
private void privateMethod() {
System.out.println("This is a private method.");
}
// protected 메소드 (동일 패키지 또는 하위 클래스에서 접근 가능)
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
// public 메소드 (어디서든 접근 가능)
public void publicMethod() {
System.out.println("This is a public method.");
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
// 오버로딩된 메소드 사용
System.out.println(calculator.add(5, 3)); // 출력: 8
System.out.println(calculator.add(5.5, 3.3)); // 출력: 8.8
// static 변수와 메소드 사용
System.out.println(Calculator.PI); // 출력: 3.14159265359
System.out.println(Calculator.circumference(5)); // 출력: 31.4159265359
// 접근 제어자에 따른 메소드 사용
calculator.publicMethod(); // 출력: This is a public method.
calculator.protectedMethod(); // 출력: This is a protected method.
// 컴파일 에러: private 메소드는 외부에서 접근 불가
// calculator.privateMethod();
}
}