클래스와 객체
- 클래스 : 객체를 만들 때 필요한 청사진
- 객체 : 실제로 존재하는 것
객체를 만들때 new라는 키워드를 사용한다.
이렇게 객체를 만드는 과정을 인스턴스화 라고 표현한다.
클래스의 구조
- 크게 속성, 생성자, 기능으로 나뉜다.
- 속성
프로퍼티(property), 필드(field)라고도 하며 변수로 표현한다.
- 생산자
객체가 생성될 때 초기 설정을 해주는 곳이다.
- 기능
기능은 메서드로 표현한다.
클래스와 관련된 기능을 작성해야 한다.
- getter, setter
클래스의 캡슐화를 위해 변수에 직접적으로 접근하는 대신 메서드를 통해 접근하는 방법입니다.
JVM 메모리 영역
JVM 메모리 구조
- Method Area(정적 영역)
프로그램 시작 시 정보가 저장된다.
클래스 정보가 올라가는 곳
클래스의 메서드 정보, static 변수 등이 저장된다.
모든 객체가 공유하는 공용 공간이다.

- Stack Area
메서드 내에서 정의하는 기본 자료형에 해당되는 지역변수의 데이터 값이 저장되는 공간
메서드가 호출 될 때 스택 프레임(메서드 그룹화)이 생기고 그 안에 메서드를 호출
메서드가 호출 될 때 메모리에 할당되고 종료되면 메모리에서 사라진다.

- Heap Area
new 키워드로 생성된 객체가 저장되는 곳이다.
객체의 실제 데이터가 저장되고 데이터의 주소는 stack area에 저장된다.
힙영역에 생성된 데이터를 참조하는 주소가 없으면 GC(가비지 컬렉터)가 제거함

래퍼클래스
- 기본 자료형을 객체로 감싸는 클래스

참조형
- 변수에 객체가 담기면 해당 변수를 참조형 변수라고 말합니다.
- 참조형 변수는 데이터가 저장된 메모리 주소를 가리킵니다.
Heap 메모리 주소
- 실제 값이 아닌 주소를 가르킵니다.
사용하는 이유
- 기본형은 객체처럼 속성, 기능을 가질 수 없다.
그냥 값 이기 때문
- 하지만 래퍼클래스로 기본형을 감싸 사용하면 객체처럼 사용 할 수 있다.
Integer num = 123; // 래퍼클래스
String str = num.toString(); // o
int a = 100; // 그냥 데이터 100
String str = a.toString(); // x
오토 박싱, 오토 언박싱
- 래퍼형과 기본형으로의 형변환은 굉장히 자주 일어나는데,
자바에서는 이 형변환 과정을 자동으로 지원해준다.
- 오토 박싱
기본형 -> 래퍼형으로 변환하는 과정
Integer num = 10; // 오토 박싱
Integer num = Integer.valueOf(10); // 위 코드의 내부적 자동 처리
- 오토 언박싱
래퍼형 -> 기본형으로 변환하는 과정
Integer num1 = 10;
int num = num1; // 오토 언박싱
int num = num1.intValue(); // 위 코드의 내부적 자동 처리
기본형과 래퍼형 성능 비교
- 래퍼형은 내부적으로 데이터를 감싸고 있기 때문에 연산시 불리하다.
연산시 객체에서 기본형 값을 꺼내는 추가 작업이 발생하기 때문이다.
빠른 작업이 필요한 경우 기본형을 직접 활용하는 것이 좋다.
Static
- 모든 객체가 함께 사용하는 변수나 메서드를 뜻한다.
static선언된 변수와 메서드는 한 번 만 생성되고 Method Area에 저장된다.
인스턴스 멤버(변수+메서드)
- 객체를 만들때 마다 생성되는 변수와 메서드를 뜻한다.
객체를 생성(인스턴스)한 후에만 사용할 수 있다.
객체를 생성한 후 접근할 수 있다.
Heap영역에 위치한다.
클래스 멤버(변수+메서드)
- 클래스 자체에 속하는 변수나 메서드를 뜻한다.
static키워드를 사용해서 선언한다.
객체를 생성하지 않아도 클래스명.(변수,메서드명)으로 접근할 수 있다.
클래스 메서드는 클래스 변수만 사용할 수 있고 인스턴스 변수는 사용할 수 없다.
Method영역에 위치한다.
final
- final 키워드는 공통적으로 무언가 제한한다는 의미를 가지며,
어떤 곳에 사용되냐에 따라 다른 역할을 수행한다.
- 변수에 사용 시 초기 값 이외에 변경 불가능하게 만든다.
(한번만 실행이 보장된 곳에서만 초기화가 가능
예를 들어 여러번 호출 될 수 있는 외부 메서드에서의 초기화는 불가능)
클래스에 사용 시 상속할 수 없게 만든다.
메서드에 사용 시 수정할 수 없게 만든다.(오버라이딩 금지)
const
- 상수는 변하지 않고 일정한 값을 갖는 함수다.
대문자로 표현하는게 관례
static final 키워드를 사용해 선언한다.
- static으로 선언하는 이유
보통 상수는 여러 곳에서 값을 공유해 쓰일 목적으로 활용되기 때문이다.
static없이 선언할 경우 인스턴스마다 중복 선언 될 수도있다.

불변객체
- final class는 참조 변경은 막지만 내부 상태 변경은 막지 않는다.
public class Circle {
final static double PI = 3.14159; // 상수
double radius; // ⚠️ final 로 선언되어 있지 않기 때문에 외부에서 변경 가능
Circle(double radius) {
this.radius = radius;
}
}
final Circle c1 = new Circle(2); // final class 선언
c1 = new Circle(3); // ❌ 참조 변경 불가능
c1.radius = 5 // ✅ 내부 상태 변경 가능
- 내부 상태 변경까지 막으려면 클래스 내부 변수에 final을 선언할 필요가 있다.
public final class Circle {
final static double PI = 3.14159; // 상수
final double radius; // ✅ final 로 선언해서 값이 변경되지 않도록 한다.
Circle(double radius) {
this.radius = radius;
}
}
- 불변 객체의 값이 변경이 필요한 경우
기존 객체의 상태는 직접 변경할 수 없기 떄문에 새로운 객체를 생성한다.
public final class Circle {
public static final double PI = 3.14159;
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
// ✅ 반지름이 다른 새로운 Circle 생성 (불변 객체 유지)
public Circle changeRadius(double newRadius) {
return new Circle(newRadius); // 생성자 호출: 기존 객체 변경 X, 새 객체 생성
}
}
인터페이스(표준화)
- 인터페이스는 설계 표준이다.
- 클래스가 따라야할 최소한의 공통 규칙을 정의하는 역할을 한다.
사용하는 이유
- 개발자마다 서로 다른 방식으로 메서드를 만들면 일관성이 깨질 수 있다.
그러므로 인터페이스를 활용해 최소한의 규격을 정의하고 세부 구현은 각 클래스에 맡기는 형식으로 구현한다.
적용
- 인터페이스는 모든 클래스가 지켜야 할 최소한의 규착을 정의한다.
클래스에서 implements 키워드로 인터페이스를 활용한다.
인터페이스를 구현한 클래스를 구현체라고 한다.
interface Car {
void drive();
void stop();
}
class LuxuryCar implements Car {
@Override
void drive() { // ✅ 인터페이스 규칙 준수
System.out.println("멋지게 이동합니다.");
}
@Override
void stop() { // ✅ 인터페이스 규칙 준수
System.out.println("멋지게 정지합니다.");
}
void charge() { // LuxuryCar만의 가능
System.out.println("차량을 충전합니다");
}
}
class SpeedCar implements Car {
@Override
void drive() { // ✅ 인터페이스 규칙 준수
System.out.println("빠르게 이동합니다.");
}
@Override
void stop() { // ✅ 인터페이스 규칙 준수
System.out.println("빠르게 정지합니다.");
}
void autoParking() { // SpeedCar만의 가능
System.out.println("자동 주차 기능을 실행합니다.");
}
}
public class Main {
public static void main(String[] args) {
LuxuryCar car1 = new LuxuryCar();
SpeedCar car2 = new SpeedCar();
// ✅ 각 차량의 공통 기능
car1.drive();
car1.stop();
car2.drive();
car2.stop();
// ✅각 차량의 고유 기능
car1.charge();
car2.autoParking();
}
}

다중구현
- implements 키워드로 다수의 인터페이스를 구현할 수 있다.
- 한 개의 클래스가 여러 인터페이스를 구현한 경우 다중 구현이라고 한다.
// 🚀 "동물의 기본 기능" 인터페이스
interface Animal {
void eat();
}
// ✈ "나는 기능" 인터페이스
interface Flyable {
void fly();
}
// ✅ 다중 구현
class Bird implements Animal, Flyable {
public void eat() {
System.out.println("새가 먹이를 먹습니다.");
}
public void fly() {
System.out.println("새가 하늘을 납니다.");
}
// Bird만의 기능
public void land() {
System.out.println("새가 착륙합니다.");
}
}

다중상속
- extends 키워드로 상속을 구현할 수 있다.
// 1. 기본 인터페이스: 동물의 기본 기능
interface Animal {
void eat();
}
// 2. 추가 인터페이스: 나는 기능
interface Flyable {
void fly();
}
// 3. ✅ 다중 상속새로운 인터페이스: 동물 + 나는 기능
interface FlyableAnimal extends Animal, Flyable {
void land(); // 추가 기능
}
// 4. 새 클래스 (FlyableAnimal을 구현)
class Bird implements FlyableAnimal {
public void eat() {
System.out.println("새가 먹이를 먹습니다.");
}
public void fly() {
System.out.println("새가 하늘을 납니다.");
}
public void land() {
System.out.println("새가 착륙합니다.");
}
}

인터페이스에 변수를 선언하는 경우
- public static final로 자동 선언된다.
- 인터페이스는 표준의 역할이므로 변수선언은 최소화하는 것이 좋다.
객체지향
- 객체지향의 4가지 특징
캡슐화, 상속, 추상화, 다형성
캡슐화
- 객체의 정보를 외부에서 직접 접근하지 못하게 보호하는 개념이다.
- 클래스 혹은 객체의 캡슐화는 접근 제어자를 통해 구현된다.
접근 제어자
- 클래스, 변수, 메서드, 생성자의 접근 범위를 제한하는 키워드이다.

데이터 접근 - Getter, Setter
- 캡슐화가 잘 적용된 클래스는 내부 데이터를 private로 보호하고 있다.
- 데이터 조회나 변경이 필요한 경우 안전한 접근방법이 필요하다.
그 역할을 수행하는 메서드가 Getter와 Setter이다.
- Getter
public String getValue() {
return this.value;
}
public void setValue(int value) {
this.value = value;
}
- 무분별한 세터가 무엇일까?
접근을 막아 놓고 다시 세터로 외부에 노출한다면, 접근제어를 하는 의미가 사라진다.
이런 경우 무조건 세터를 만드는 것이 아니라 올바른 데이터만 저장될 수 있도록 제한해야 한다.
public void setValue(int value) {
if(value == 1)
System.out.println("1이 입력되면 안됩니다");
else
this.value = value;
}
상속
- 클래스 관계를 부모(상위), 자식(하위)로 바라보는 개념이다.
- 이 구조를 통해 상속에서는 재사용성, 확장이 가능하다.
- extends 키워드를 사용해서 상속관계를 구현한다.
- 상속을 통해 코드 중복을 줄이고 유지보수성을 높힐 수 있다.
- 추상화, 다형성을 구현하는데에도 활용된다.
재사용성
- 상속의 장점은 부모 클래스의 내용을 물려받아 그대로 재사용 할 수 있다는 점이다.

super
- super는 자식클래스에서 부모클래스의 멤버에 접근할 때 사용하는 키워드다.
- 인스턴스 생성시 부모가 먼저 생성되고 자식이 생산되어야 하므로 super()은 항상 자식 생성자의 첫 줄에 위치해야 한다.
확장
- 부모클래스의 기능을 유지하면서 자식클래스에서 기능을 확장 할 수 있다.
재정의 - 오버라이딩(overriding)
- 오버라이딩을 통해 부모클래스의 기능을 재정의 할 수 있다.
- 오버라이드된 메서드에는 @override 키워드를 붙이는 것을 권장(없어도 동작한다.)
@override를 붙이면 컴파일러가 부모클래스에 동일한 메서드가 없다고 경고를 줘서 실수를 방지할 수 있다.
- 메서드 이름, 매개 변수, 반환 타입이 완전히 동일해야한다.
- 접근 제어자는 부모보다 더 강한 수준으로만 변경 가능하다.
class Child extends Parent {
@Override
void introduceFamily() { // ✅ 자식클래스에서 재정의
System.out.println("오버라이드");
}
}
추상클래스
- 공통 기능을 제공하면서 하위 클래스에 특정 메서드 구현을 강제하기 위해 사용된다.
- 객체를 생성할 목적이 아니라 설계도 역할을 할 때 사용한다.
- abstract 키워드를 클래스에 붙혀 선언한다.
- abstract 키워드로 메서드를 선언하면 자식클래스에서 강제로 구현해야한다.
- 추상클래스로 객체를 생성할 수 없다.
- 일반 클래스처럼 변수와 메서드를 가질 수 있다.
abstract class Animal {
private String name; // ✅ 변수선언가능
abstract void eat(); // ⚠️ 추상메서드: 상속 받은 자식은 강제 구현해야한다.
public void sleep() { // ✅ 자식클래스에서 재사용가능하다.
System.out.println("쿨쿨");
}
}
추상클래스와 인터페이스 차이점
- 상속이 계층적 구조를 선언하기 적합하다.
- 인터페이스는 표준을 제공하는 데 적합하다.
- 인터페이스는 인스턴스 변수를 선언할 수 없다.
- 계층적 구조를 표현하면서 공통 속성과 기능을 재사용할 때 추상클래스를 사용하는것이 적합하다.
추상화
- 불필요한 정보를 제거하고 본질적인 특징만 남기는 것을 뜻한다.
- 객체지향 프로그래밍에서는 추상화의 계층적 특징을 활용해서 유지보수성이 좋은 프로그램을 만들 수 있다.
- 추상화의 특징은 다형성에서도 활용된다.
- 예시) 고양이는 동물이고 동물은 생명체
생명체의 공동기능, 동물의 공동기능, 고양이의 기능만을 구현함으로 추상 계층 구조를 만들 수 있다.
다형성
- 하나의 타입으로 여러 객체를 다룰 수 있는 기술이다.
- 추상 계층이라는 특징을 활용해 다형성을 구현할 수 있다.
인터페이스를 활용한 다형성
public static void main(String[] args) {
// 다형성 활용
Animal animal = new Cat();
animal.exist(); // Cat에서 구현한 exist()가 실행된다.
animal.makeSound(); // Cat에서 구현한 makeSound()가 실행된다.
}

형변환(casting)
- 부모타입으로 자식타입을 다룰 수 있는 이유는 자동으로 형변환이 발생했기 때문이다.
자식 -> 부모 : 업캐스팅
부모 -> 자식 : 다운캐스팅
- 업캐스팅
Animal animal = new Cat();
- 업캐스팅의 주의사항
업캐스팅은 부모의 타입으로 데이터를 다룰 수 있지만 자식 클래스의 고유기능을 활용할 수 없다.
자식 클래스의 고유 기능을 사용하려면 다운캐스팅이 필요하다.
- 다운캐스팅
Animal animal = new Cat();
Cat cat = (Cat) animal;
- 다운캐스팅의 주의사항
잘못된 다운캐스팅은 컴파일단계에서 감지할 수 없다.
컴파일러는 다운캐스팅이 문법적으로 올바른지 여부만 검사해주기 때문에,
런타임시에 실제 어떤 객체가 변수에 할당되는지 검사해 주지 않는다.
Animal dog = new Dog();
// 문법적으로 잘못된건 아니라서 에러가 발생하지 않는다.
Cat cat1 = (Cat) dog; // ⚠️
cat1.scratch(); // ❌ 해당 라인이 실행할때만 에러 여부를 확인할 수 있다.
- 다운캐스팅을 사용할 때 항상 instanceof를 활용해 이를 방지한다.
instanceof는 객체가 특정 클래스나 인터페이스의 인스턴스인지 확인해 주는 역할을 한다.
주로 다운캐스팅을 하기 전에 타입을 검사해서 오류를 예방한다.
Animal animal2 = new Dog();
// ✅ 안전한 다운캐스팅(animal2 가 Cat 의 인스턴스 유형인지 확인합니다.)
if (animal2 instanceof Cat) {
Cat cat = (Cat) animal2;
cat.scratch();
} else {
System.out.println("객체가 고양이가 아닙니다.");
}