코딩/Java

Java 5 : 객체지향(OOP : Object-Oriented-Programming), 추상화, 인터페이스

americanoallday 2025. 2. 6. 15:35

아 오늘따라 왜 이렇게 하기 싫은지, 죽을 맛 ~~!!

 

함수(Function)

특정 작업을 수행하는 코드 블록

여러번 호출 가능

반환타입 메서드이름 (타입 변수명, 타입 변수명, ... ) // (선언부)
{
                  // 메서드가 호출되면 수행할 코드(구현부)
}

 

void 반환타입 : 

void 메서드는 값을 반환하지 않으므로 return이 필요 없지만, 강제 종료할 때 return;만 사용할 수도 있음

public class VoidReturnExample {
    public static void checkNumber(int num) {
        if (num < 0) {
            System.out.println("음수입니다.");
            return; // 여기서 메서드 종료 (아래 코드 실행 안 됨)
        }
        System.out.println("양수입니다.");
    }

    public static void main(String[] args) {
        checkNumber(-5); // 음수입니다.
        checkNumber(10); // 양수입니다.
    }
}

 

호출 스택(Call Stack)

메서드가 호출될 때 실행 순서를 관리하는 메모리 구조

후입선출(LIFO) 구조

메서드가 호출되면 호출 스택에 메모리 할당, 종료되면 해제

 

static 메서드(클래스 메서드)

static 키워드가 붙은 메서드

객체를 생성하지 않고 클래스 이름으로 호출 가능 (클래스명.메서드())

객체(인스턴스 변수)에 접근할 수 없음

class MathUtil {
    // static 메서드 (객체 없이 호출 가능)
    static int add(int a, int b) {
        return a + b;
    }
}

public class StaticMethodExample {
    public static void main(String[] args) {
        int sum = MathUtil.add(5, 10); // 클래스명으로 호출
        System.out.println("합계: " + sum);
    }
}

 

인스턴스 메서드(Instance Method)

static 키워드가 없는 메서드

반드시 객체를 생성한 후에 호출해야 함 (객체명.메서드())

객체(인스턴스 변수)에 접근 가능

class Car {
    String brand; // 인스턴스 변수

    Car(String brand) {
        this.brand = brand;
    }

    // 인스턴스 메서드 (객체 생성 후 호출 가능)
    void displayBrand() {
        System.out.println("이 차의 브랜드는 " + brand + "입니다.");
    }
}

public class InstanceMethodExample {
    public static void main(String[] args) {
        Car myCar = new Car("Tesla"); // 객체 생성
        myCar.displayBrand(); // 인스턴스 메서드 호출
    }
}

 

클래스(Class)

객체를 만들기 위한 설계도

속성(변수), 행동(메서드) 포함

 

클래스와 객체(Object)의 차이

클래스(Class) → 객체를 만들기 위한 “틀”

객체(Object) → 클래스를 바탕으로 생성된 “실제 데이터”

 

클래스 변수(Static 변수)

클래스 전체에 공유

 

인스턴스 변수

클래스 내부, static 없이 선언, 객체마다 따로 존재

 

지역 변수

메서드 내부에서 선언, 메서드가 끝나면 사라짐

public class VariableExample {
    static int classVar = 100; // 클래스 변수 (static)

    int instanceVar = 50; // 인스턴스 변수

    void display() {
        int localVar = 10; // 지역 변수
        System.out.println("클래스 변수: " + classVar);
        System.out.println("인스턴스 변수: " + instanceVar);
        System.out.println("지역 변수: " + localVar);
    }

    public static void main(String[] args) {
        VariableExample obj1 = new VariableExample();
        VariableExample obj2 = new VariableExample();

        obj1.instanceVar = 75; // obj1의 인스턴스 변수 값 변경
        obj2.instanceVar = 25; // obj2의 인스턴스 변수 값 변경

        obj1.display();
        obj2.display();
    }
}

 

매개변수 → 메서드가 호출될 때 전달받는 값 (인자)

더보기

매개변수(parameter) : 메서드 정의 시 선언하는 변수

인수(argument) : 실제 매개변수에 전달된 값

기본형 매개변수 (Primitive Type Parameter)

값 자체를 복사해서 전달 (Call by Value)

원본 변수에 영향을 주지 않음

 int, double, boolean 등

public class PrimitiveParamExample {
    public static void changeValue(int num) {
        num = 10; // 매개변수 값 변경
    }

    public static void main(String[] args) {
        int value = 5;
        changeValue(value); // 값 복사 (원본 변경 X)
        System.out.println("메서드 호출 후 value: " + value); // 5 (변화 없음)
    }
}

 

참조형 매개변수 (Reference Type Parameter)

**객체의 주소(참조값)**를 전달 (Call by Reference)

원본 객체에 영향을 줄 수 있음

 배열, 객체, String 등

public class ReferenceParamExample {
    public static void changeArray(int[] arr) {
        arr[0] = 100; // 배열의 첫 번째 요소 변경
    }

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        changeArray(numbers); // 참조값 전달 (원본 변경 O)
        System.out.println("메서드 호출 후 numbers[0]: " + numbers[0]); // 100
    }
}

 

오버로딩(Overloading)

오버로딩은 같은 기능을 수행하지만 다양한 입력을 받을 수 있도록 하는 기능

매개변수의 개수나 타입이 달라야 함

class MathUtil {
    // 정수 덧셈
    int add(int a, int b) {
        return a + b;
    }

    // 실수 덧셈 (매개변수 타입이 다름)
    double add(double a, double b) {
        return a + b;
    }

    // 세 개의 정수 덧셈 (매개변수 개수가 다름)
    int add(int a, int b, int c) {
        return a + b + c;
    }
}

public class OverloadingExample {
    public static void main(String[] args) {
        MathUtil math = new MathUtil();

        System.out.println(math.add(3, 5));         // 정수 덧셈 호출 (8)
        System.out.println(math.add(2.5, 3.5));     // 실수 덧셈 호출 (6.0)
        System.out.println(math.add(1, 2, 3));      // 세 개의 정수 덧셈 호출 (6)
    }
}

 

* 오버로딩과 main() 메서드

자바의 main() 메서드도 오버로딩 가능!

하지만 실행 시에는 반드시 main(String[] args) 버전이 호출됨!

public class MainOverloading {
    public static void main(String[] args) {
        System.out.println("메인 메서드 실행!");

        // 다른 main() 호출
        main(100);
        main("Hello");
    }

    public static void main(int num) {
        System.out.println("정수 메인: " + num);
    }

    public static void main(String message) {
        System.out.println("문자열 메인: " + message);
    }
}

 

생성자(Constructor)

클래스 이름과 동일한 메서드(리턴 타입이 없음)

객체 생성 시 자동으로 호출 됨

객체의 초기 상태를 설정하는 역할

class Person {
    String name;

    // 생성자 (객체 생성 시 자동 호출됨)
    Person(String name) {
        System.out.println("생성자가 호출되었습니다.");
        this.name = name;
    }
}

public class ConstructorExample {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동"); // 생성자 호출
        System.out.println("p1의 이름: " + p1.name); // p1의 이름: 홍길동
    }
}

 

* 클래스에 생성자를 직접 만들지 않으면, Java가 자동으로 기본 생성자(매개변수가 없는 생성자)를 제공 함.

class Person {
    String name;
}

public class DefaultConstructorExample {
    public static void main(String[] args) {
        Person p = new Person(); // 기본 생성자 자동 제공됨
        System.out.println("객체 생성 완료!");
    }
}

 

생성자 오버로딩

생성자를 여러 개 정의할 수도 있음 (매개변수 개수에 따라 다르게 동작)

class Car {
    String brand;
    int speed;

    // 기본 생성자
    Car() {
        this.brand = "기본 브랜드";
        this.speed = 0;
    }

    // 브랜드만 설정하는 생성자
    Car(String brand) {
        this.brand = brand;
        this.speed = 0;
    }

    // 브랜드와 속도를 설정하는 생성자
    Car(String brand, int speed) {
        this.brand = brand;
        this.speed = speed;
    }

    void displayInfo() {
        System.out.println("브랜드: " + brand + ", 속도: " + speed + "km/h");
    }
}

public class ConstructorOverloadingExample {
    public static void main(String[] args) {
        Car car1 = new Car();
        Car car2 = new Car("Tesla");
        Car car3 = new Car("BMW", 100);

        car1.displayInfo(); // 브랜드: 기본 브랜드, 속도: 0km/h
        car2.displayInfo(); // 브랜드: Tesla, 속도: 0km/h
        car3.displayInfo(); // 브랜드: BMW, 속도: 100km/h
    }
}

 

this → 현재 객체를 가리키는 참조 변수

* static 메서드 내에서 this 사용 불가

class Person {
    String name;

    // 생성자 (객체 생성 시 호출됨)
    Person(String name) {
        this.name = name; // this를 사용하여 멤버 변수(name)와 지역 변수(name)를 구분
    }

    void introduce() {
        System.out.println("제 이름은 " + this.name + "입니다.");
    }
}

public class ThisExample {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동");
        p1.introduce(); // 제 이름은 홍길동입니다.
    }
}

 

this() → 같은 클래스의 다른 생성자를 호출할 때 사용

* this()는 생성자의 첫 번째 줄에서만 사용 가능

* this()를 여러 번 호출할 수 없음 (순환 호출 방지) : 한 생성자에서 한 번만 호출 가능

class Example {
    Example() {
        System.out.println("기본 생성자");
        this(10); // ❌ 오류 발생 (this()는 첫 번째 줄에서만 호출 가능)
    }

    Example(int num) {
        System.out.println("숫자: " + num);
    }
}
class Example {
    Example() {
        this(10);  // ✅ 첫 번째 호출
        this(20);  // ❌ 두 번째 호출 (오류 발생)
    }

    Example(int num) {
        System.out.println("숫자: " + num);
    }
}

 

변수 초기화

지역 변수(Local Variable) → 수동으로 초기화해야 함

자동 초기화되지 않으며, 사용 전에 반드시 초기화해야 함

더보기

✔ 지역 변수는 “스택(Stack) 메모리”에 저장됨

✔ 스택 메모리는 함수 호출 시 할당되고, 종료되면 해제됨 (매우 빠르게 재사용됨)

✔ Java는 성능을 고려해 매번 초기화를 하지 않고, 개발자가 직접 초기화하도록 요구함

✔ 초기화하지 않은 지역 변수에는 이전 값(쓰레기 값)이 남아 있을 수도 있음 (C언어에서는 이를 “Garbage Value”라고 부름)

 

📌 즉, 지역 변수는 빠르게 생성되고 삭제되므로, 자동 초기화하면 성능이 떨어질 수 있어서 Java는 개발자에게 초기화를 강제 함.

public class LocalVariableExample {
    public static void main(String[] args) {
        int number; // 지역 변수 선언 (초기화 안 함)
        System.out.println(number); // ❌ 오류 발생 (초기화 필요)
    }
}

 

멤버 변수(클래스 변수, 인스턴스 변수) → 자동으로 초기화됨

더보기

✔ “힙(Heap) 메모리” 또는 “메서드(Method) 영역”에 저장됨

✔ 기본값(Default Value)으로 자동 초기화됨

 

클래스 상속 (extends) → 기존 클래스를 확장하여 새로운 클래스를 만드는 것

부모 클래스(슈퍼클래스)의 변수와 메서드를 자식 클래스(서브클래스)에서 사용할 수 있음

// 부모 클래스 (SuperClass)
class Animal {
    String name;

    void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 (SubClass)
class Dog extends Animal {
    void bark() {
        System.out.println("멍멍!");
    }
}

public class InheritanceExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "바둑이";
        dog.makeSound(); // 부모 클래스의 메서드 호출
        dog.bark(); // 자식 클래스의 메서드 호출
    }
}

 

super 키워드 → 부모 클래스의 멤버(변수, 메서드)에 접근

부모 클래스와 동일한 변수나 메서드가 있을 경우, 부모 클래스의 것을 호출할 때 유용

class Animal {
    String name = "동물";

    void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

class Dog extends Animal {
    String name = "강아지"; // 같은 변수 이름

    void displayNames() {
        System.out.println("자식 클래스 name: " + name); //강아지
        System.out.println("부모 클래스 name: " + super.name); // 부모 변수 접근, 동물
    }

    void makeSound() {
        super.makeSound(); // 부모 메서드 호출
        System.out.println("멍멍!");
    }
}

public class SuperExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.displayNames();
        dog.makeSound();
    }
}

 

super() 메서드 →자식 클래스의 생성자에서 부모 클래스의 생성자를 호출할 때 사용

class Animal {
    Animal(String type) {
        System.out.println(type + "이(가) 생성되었습니다."); // 강아지가 생성되었습니다.
    }
}

class Dog extends Animal {
    Dog() {
        super("강아지"); // 부모 생성자 호출
        System.out.println("Dog 객체 생성 완료!");
    }
}

public class SuperConstructorExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

 

패키지 (package) → 클래스를 논리적으로 묶어 관리

자바에서는 폴더 경로(도메인)를 이용해 같은 클래스명이여도 다른 폴더에 존재하면 중복해서 사용 가능함.

import 문 → 다른 패키지의 클래스를 가져와 사용

* import 패키지명.*; → 해당 패키지의 모든 클래스를 포함하지만, 하위 패키지는 포함되지 않음, 패키지는 직접 import 추가해야 함.

// animals 패키지 안에 Dog 클래스 정의
package animals;

public class Dog {
    public void bark() {
        System.out.println("멍멍!");
    }
}
// main 패키지에서 Dog 클래스 사용
package main;

import animals.Dog; // 다른 패키지의 클래스 가져오기

public class PackageExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.bark();
    }
}

 

static importstatic 멤버(변수, 메서드)를 클래스 이름 없이 바로 사용 가능

import static java.lang.Math.*; // Math 클래스의 모든 static 멤버 임포트

public class StaticImportExample {
    public static void main(String[] args) {
        System.out.println(sqrt(25)); // Math.sqrt() 대신 sqrt() 사용 가능
        System.out.println(PI);       // Math.PI 대신 PI 사용 가능
    }
}

 

제어자 (modifier) → 클래스, 변수, 메서드의 속성을 변경하는 키워드

 

접근 제어자 (public, default, protected, private) → 접근 범위를 제어

접근 제어자 같은 클래스 같은 패키지 자식 클래스 외부 클래스
public
protected
default
private

* Java에서는 “파일 이름과 동일한 클래스만 public으로 선언 가능”하다는 규칙이 있음

 

기타 제어자 (static, final) → 메모리 사용 방식 및 수정 가능 여부 제어

static → 클래스 단위에서 공유되는 변수 & 메서드

final → 변경 불가능 (상수, 메서드에 붙이면 오버라이딩 금지, 클래스에 붙이면 상속 금지)

 

“추상적(abstract)” → 직접 사용될 수 없고, 상속을 통해 구현해야 함

 

추상 클래스(abstract class)

abstract 키워드가 붙은 클래스는 “추상 클래스”

추상 클래스는 직접 객체를 생성할 수 없음 (new로 인스턴스화 불가)

일반 메서드도 포함할 수 있지만, abstract 메서드를 포함할 수도 있음

반드시 “자식 클래스”가 추상 클래스를 상속받고, 모든 추상 메서드를 구현해야 함

 

장점 :

1. 중복 기능 제거 → 부모 클래스에 공통 기능을 작성하여 코드 중복 방지

2. 구현 강제성을 통한 기능 보장 → 자식 클래스가 반드시 필요한 메서드를 구현하도록 강제

3. 일관된 설계 가능 → 규격화된 설계를 제공하여 유지보수 및 확장성 증가

 

// 추상 클래스
abstract class Animal {
    String name;

    // 일반 메서드 (구현된 메서드)
    void eat() {
        System.out.println(name + "이(가) 먹고 있습니다."); // 바둑이이(가) 먹고 있습니다.
    }

    // 추상 메서드 (구현이 없는 메서드 → 상속받은 클래스에서 구현 필수)
    abstract void makeSound();
}

// 구체적인 클래스 (추상 클래스를 상속받아 구현)
class Dog extends Animal {
    // 추상 메서드 반드시 구현해야 함
    @Override
    void makeSound() {
        System.out.println("멍멍!");
    }
}

public class AbstractClassExample {
    public static void main(String[] args) {
        // Animal animal = new Animal(); ❌ 오류 (추상 클래스는 직접 객체 생성 불가)
        
        Dog dog = new Dog();
        dog.name = "바둑이";
        dog.eat(); // 일반 메서드 사용 가능
        dog.makeSound(); // 추상 메서드 구현된 것 호출
    }
}

 

추상 메서드(abstract mathod)

구현 없이 선언만 된 메서드 ({ } 없음)

abstract 클래스 안에서만 선언 가능

추상 메서드가 있는 클래스는 반드시 abstract로 선언해야 함

상속받은 클래스에서 반드시 구현해야 함 (@Override 필수)

abstract class Shape {
    // 추상 메서드 (각 도형마다 다르게 구현해야 함)
    abstract double calculateArea();
}

// 구체적인 클래스 (추상 클래스 상속 후 구현)
class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    // 추상 메서드 구현
    @Override
    double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class AbstractMethodExample {
    public static void main(String[] args) {
        // Shape shape = new Shape(); ❌ 오류 (추상 클래스는 직접 객체 생성 불가)
        
        Circle circle = new Circle(5);
        System.out.println("원의 넓이: " + circle.calculateArea());
    }
}

 

📌 abstract 클래스는 “공통 기능”을 상속받을 때, interface는 “설계도” 역할을 할 때 사용

 

인터페이스(interface)

클래스가 따라야 할 "설계도"

클래스가 implements 키워드를 사용하여 인터페이스를 구현해야 함

"다중 상속"이 가능(여러 개의 인터페이스를 동시에 구현할 수 있음)

추상 클래스와 비슷하지만, 더 엄격한 규칙을 가짐.

클래스가 반드시 구현해야 할 메서드를 정의하는 설계도

 

✔ 모든 메서드는 기본적으로 abstract (구현부 {} 없음) → 구현 필수

✔ 변수는 public static final (상수) → 값 변경 불가능 (public static final, public abstract 제어자 생략 가능)

✔ implements 키워드로 인터페이스를 구현해야 함

✔ 다중 구현 가능 (class A implements 인터페이스1, 인터페이스2) (abstract는 단일 상속 방식)

// 인터페이스 정의 (설계도 역할)
interface Animal {
    void makeSound(); // 추상 메서드 (자동으로 public abstract)
}

// 인터페이스 구현 (implements 사용)
class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍!");
    }
}

public class InterfaceExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();
    }
}
더보기

✔ 자바 8부터 interface에서도 메서드 구현 가능 (default, static 메서드)

✔ 기존 인터페이스와의 호환성을 유지하면서 새로운 기능을 추가할 수 있도록 함

 

default 메서드 구현

interface Animal {
    void makeSound(); // 추상 메서드

    // default 메서드 (기본 구현 제공 가능)
    default void sleep() {
        System.out.println("Zzz...");
    }
}

class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍!");
    }
}

public class DefaultMethodExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();
        dog.sleep(); // 인터페이스의 기본 메서드 호출 가능
    }
}

 

static 메서드 구현

interface Animal {
    void makeSound();

    static void info() {
        System.out.println("모든 동물은 소리를 낼 수 있습니다.");
    }
}

public class StaticMethodExample {
    public static void main(String[] args) {
        Animal.info(); // 인터페이스 이름으로 호출 가능
    }
}

인터페이스 다중 상속 예제

interface Animal {
    void makeSound();
}

interface Pet {
    void play();
}

class Dog implements Animal, Pet {
    @Override
    public void makeSound() {
        System.out.println("멍멍!");
    }

    @Override
    public void play() {
        System.out.println("강아지가 장난을 칩니다.");
    }
}

public class MultipleInterfaceExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();
        dog.play();
    }
}

 

 

죽음의 다이아몬드 현상 (Deadly Diamond of Death)

다중 상속에서 동일한 메서드명이 겹쳐서 어떤 메서드를 실행해야 할지 모호해지는 문제

C++ 예제

#include <iostream>
using namespace std;

class A {
public:
    void show() { cout << "A 클래스" << endl; }
};

class B : public A { };
class C : public A { };

// 다중 상속 (B와 C를 동시에 상속)
class D : public B, public C { };

int main() {
    D obj;
    obj.show(); // ❌ 모호성 오류 발생 (B와 C 중 어느 show()를 실행?)
    return 0;
}

 

자바에서는 interface를 통해 다중 구현이 가능하지만, default 메서드가 겹치면 해결해야 함.

super 키워드를 사용하여 명확하게 부모 인터페이스를 지정 가능

interface A {
    default void show() {
        System.out.println("A 인터페이스");
    }
}

interface B {
    default void show() {
        System.out.println("B 인터페이스");
    }
}

class C implements A, B {
    // 두 인터페이스에 동일한 메서드가 있어서 직접 해결해야 함
    @Override
    public void show() {
        A.super.show(); // 또는 B.super.show() 선택 가능
    }
}

public class InterfaceDiamondExample {
    public static void main(String[] args) {
        C obj = new C();
        obj.show(); // A 인터페이스
    }
}

'코딩 > Java' 카테고리의 다른 글

default 메서드란  (0) 2025.04.03
Java 6 : Map, Hash, HashTable  (1) 2025.02.07
Java 4 : 조건문, 반목문, 배열  (0) 2025.02.05
Java 3 : 연산자  (0) 2025.02.05
Java 2 : 변수  (1) 2025.02.04