[25/07/08] JAVA 문법 2주차

김기수·2025년 7월 8일

클래스와 객체

  • 클래스 : 객체를 만들 때 필요한 청사진
  • 객체 : 실제로 존재하는 것
    객체를 만들때 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;
}
  • Setter
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("객체가 고양이가 아닙니다.");
}
profile
백엔드 개발자

0개의 댓글