객체지향 이해하기
class 클래스 {
1. 속성 - 변수 선언으로 표현할 수 있다.
2. 생성자 - 조립 설명서
특징 : 클래스와 이름이 같다.
반환 타입이 존재하지 않는다.
여러 개가 존재할 수 있다.
3. 기능 (메서드 부분)
}
JVM

Method 영역
- Method 영역에 저장된 데이터는 프로그램 전체에서 공용으로 활용 가능
- static 으로 선언된 변수와 메서드들이 이 공간에 위치하게 됩
- 이 공간에 저장된 데이터는 공용으로 사용 가능
Heap 영역
- new 키워드로 생성된 객체는 Heap 영역에 저장 (객체 데이터)
- 프로그램 실행 흐름에서 Stack 영역의 변수에 Heap 영역의 메모리 주소값이 저장
Stack 영역
- 메서드가 호출될 때마다 Stack 영역에 메모리가 할당
- 하나의 접시(스택프레임)에 각 메서드의 지역변수가 저장
- 메서드가 시작되면 추가되고 메서드가 종료되면 사라지는 구조
- 특정 메서드가 실행되면 해당 메서드의 정보와 변수가 Stack 에 저장되고 메서드 실행이 끝나면 그 메모리는 자동으로 제거
- 메서드 내에 선언된 지역변수들이 저장되는 공간입니다. → 변수에 객체가 담기면 객체의 주소값이 저장
Static 이란?
- static 키워드는 모든 객체가 함께 사용하는 변수나 메서드를 만들때 사용됩니다.
- 객체(인스턴스)를 만들지 않아도 클래스 이름만으로 바로 사용할 수 있습니다.
- 모든 객체가 같은 값을 공유합니다.
- static 변수와 메서드는 '한 번만 생성'되고 Method Area(메서드영역) 에 저장됩니다.
- 자 여기서 궁금한 점은, 언제는 static void를 사용하고 언제는 void를 사용하냐이다.
| 객체 유무 | 메서드 차이 | 주요 쓰이는 곳 | 호출 방식 | |
| void | 객체를 생성한 후에만 쓸 수 있는 '인스턴스 메서스' | 반환값이 없는 메서드 | 객체가 동작할 때 필요한 기능 (속성) |
객체명.메서드명() |
| static void | 객체를 만들지 않고 호출 가능 | 클래스에 속하는 메서드 | main 메서드, 유틸리티 메서드 | 클래스명.메서드명() |
여기 표에서 '인스턴스 메서드'라고 했는데,
인스턴스 메서드란?
- 객체를 만들때 마다 생성되는 변수와 메서드
- 객체(인스턴스)를 생성한 후에만 사용할 수 있다.
- 각 객체가 개별적으로 값을 가진다. (공유되지 않음)
- 인스턴스는 Heap 영역에 위치
클래스 메서드란?
- 클래스가 로드될 때 한 번만 생성
- 모든 객체가 공유하는 변수
- Heap 이 아니라 Method Area 에 저장
- 객체를 만들지 않아도 클래스명.변수명으로 접근가능
final이란?
상수는 변하지 않고 항상 일정한 값을 갖는 수
- Java에서 상수는 대문자로 표현하는 것이 관례
- static 으로 선언된 변수는 프로그램 시작시 한 번만 초기화되고 모든 인스턴스에서 같은 값을 공유
여기서 상수는 절대 '변경'되어서는 안되기 때문에 static final을 사용하여 값이 변경되지 않도록 해줄 수 있다.
인터페이스(interface)란?
- 개발자마다 서로 다른 방식으로 메서드를 만든다면 일관성이 깨질 수 있다.
- 인터페이스를 활용해서 최소한의 규격을 정의
- 세부 구현은 각 클래스에 맡기고, 일관성을 유지하면서 클래스가 고유한 특색을 확장할 수 있도록 돕는다.
- 클래스에서 implements 키워드로 인터페이스를 활용할 수 있다.
- 인터페이스에는 다중구현과 다중상속이 있다.
사실 인터페이스를 실제 코드를 작성해보면서 이해하는게 빠르긴 하지만, 이미지를 통해 정리를 해보았다.


객체지향
1. 캡슐화(접근제어자)
| 접근 제어자 | 클래스 내부 | 패키지 내부 | 상속한 클래스 | 전체 공개 |
| public | O | O | O | O |
| protected | O | O | O | X |
| default | O | O | X | X |
| private | O | X | X | X |
- 접근제어자가 잘 적용된 클래스(private)는 데이터 조회나 변경이 필요한 경우에 게터(getter)와 세터(setter)를 사용해 접근한다.
게터(getter) 활용
public class Person {
private String secret;
public String getSecret() {
return this.secret; // ✅ 객체의 secret 속성 반환
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
p1.secret; // ❌ 직접 접근 불가능
String newSecret = p1.getSecret(); // ✅ 게터를 활용해 접근가능
}
}
세터(setter) 활용
public class Person {
private String secret;
public void setSecret(String secret) {
this.secret = secret; // secret 속성 설정 및 변경
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
p1.secret = "password"; // 직접접근, 변경 불가능
p1.setSecret("newPassword"); // 세터를 활용해 접근, 변경가능
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
하지만, 세터를 사용해 외부에서 설정을 해준다고 해도, 들어가서는 안되는 값을 모르고 설정해줄 수도 있다.
이럴 때는 조건문을 사용하여 방지할 수 있다.
public class DataStore {
private String store;
public void setStore(String data) {
if ("B".equals(data)) {
System.out.println("'B'는 입력할 수 없습니다!");
} else {
this.store = data;
}
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
고려해야할 속성들이 너무나도 많을 때는, 하나하나 다 세터를 해줄 수는 없는 노릇이다. 이런 경우에는,
여러 기능들을 하나의 메서드로 정의해주고 그 안에서 관리를 해주면 된다.
public class Robot {
private boolean leftLeg;
private boolean rightLeg;
private boolean leftArm;
...
public void walk(boolean power) {
System.out.println("왼쪽 다리!");
leftLeg = power;
System.out.println("오른쪽 다리");
rightLeg = true;
...
}
}
2. 상속
클래스는 부모 클래스(상위), 자식 클래스(하위)로 분류를 하여 생각해볼 수 있다.
이 구조를 통해 상속에서는 재사용, 확장이 가능하다. 이게 무슨 뜻이냐면 extends 키워드를 사용해서 상속관계를 구현하고,
부모의 속성과 기능을 자식이 물려받는다고 생각하면 된다.
public class Parent {
public String familyName = "스파르탄";
public int honor = 10;
public void introduceFamily() {
System.out.println("우리 " + this.familyName + " ...");
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
class Child extends Parent { // extends 키워드 활용
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
System.out.println(child.honor); // 부모의 속성을 물려받아 사용
System.out.println(child.familyName); // 부모의 속성을 물려받아 사용
child.introduceFamily(); // 부모의 메서드를 물려받아 사용
}
}
여기서 super라는 키워드가 등장하는데,
super는 부모클래스의 멤버(변수, 메서드)에 접근할 때 사용하는 키워드이다.
자식 클래스에서 부모의 변수나 메서드를 호출하고 싶을 때 사용한다. 이 때, 부모가 먼저 생성되어야 하므로 super()는 항상 생성자의 첫 줄에 위치해야한다.
오버라이딩
오버라이딩을 통해 부모클래스의 기능을 재정의할 수 있다. @Override
public class Parent {
// 기존 기능
public void introduceFamily() {
System.out.println("우리 " + familyName + " ...");
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
class Child extends Parent {
...
@Override
void introduceFamily() { // 자식클래스에서 재정의
System.out.println("오버라이드");
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.introduceFamily(); // 출력 "오버라이드"
}
}
추상 클래스
공통 기능을 제공하면서 하위 클래스에 특정 메서드 구현을 강제하기 위해 사용
abstract 키워드로 클래스를 선언하면 된다. 이 때, 추상클래스로는 객체를 생성할 수 없다. 또한, 자식 클래스에서 '강제로' 구현해야한다.
abstract class Animal {
private String name; // 변수선언가능
abstract void eat(); // 추상메서드: 상속 받은 자식은 강제 구현해야한다.
public void sleep() { // 자식클래스에서 재사용 가능
System.out.println("쿨쿨");
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class Cat extends Animal {
@Override
void eat() {
System.out.println("냠냠"); // 자식클래스에서 강제 구현해야한다.
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class Main {
public static void main(String[] args) {
Animal animal = new Animal(); // 추상클래스는 구현할 수 없다.
Cat cat = new Cat();
cat.eat(); // 강제 구현한 메서드 사용
cat.sleep(); // 부모클래스의 매서드 사용
}
}
3. 추상화
추상화란 불필요한 정보를 제거하고 본질적인 특징만 남기는 것을 의미
앞에 내용을 잠깐 언급해보면, 우리는 계층구조를 인터페이스 상속, 클래스 상속으로 구현을 해보았다.
| 키워드 | 의미 | 대상 | 다중 사용 가능 여부 | 예시 |
| extends | 상속 (기능 물려받기) |
클래스 -> 클래스 / 인터페이스 -> 인터페이스 |
클래스는 X, 인터페이스는 O | class Dog dtends Animal interface B extends A |
| emplements | 인터페이스 구현 (약속지키기) |
클래스 -> 인터페이스 | 가능 | class Dog implements able |
- 여기서 더 궁금했던 점은, 어떨 때는 클래스로 생성을 해주고 어떨 때는 인터페이스로 생성을 해주는지 궁금했다.
| 구분 | 목적 | 구현 | 상속 | 예시 |
| 클래스 | 실제 동작 | 구현 O | 하나만 가능 | User, Car, Animal 등 실제 객체 |
| 인터페이스 | 기능의 틀/약속 | 구현 X | 여러 개 구현 가능 | Comparable,Serializable 등 기능 규약 |
4. 다형성
다형성(Polymorphism)이란 하나의 타입으로 여러 객체를 다룰 수 있는 기술이다.
이에는 업캐스팅(UpCasting)과 다운캐스팅(DownCasting)이 존재한다.
업캐스팅의 예시를 들어보면, 업캐스팅은 부모의 타입으로 데이터를 다룰 수 있지만 자식 클래스의 '고유기능'을 활용할 수 없다.
이를 위해선 다운캐스팅을 이용해야하는데, 이를 통해 자식 클래스의 고유 메서드를 부모 클래스에서 사용할 수 있다.
이 때는 instanceof 키워드를 사용해주어야한다. 이는 객체가 특정 클래스나 인터페이스의 인스턴스인지 확인해주는 역할을 한다.