본문 바로가기

(08.26) java 기초 - 객체형변환, 추상클래스, 인터페이스, 디자인패턴, 오브젝트

@starweb2025. 8. 26. 18:10

[ 3주차 - 0826 ] 

    금일 커리큘럼
        ├ 09:00 ~ 12:00 자바 프로그래밍 기초 (객체 형변환, 추상 클래스)
        └ 13:00 ~ 18:00 자바 프로그래밍 기초 (인터페이스, 디자인 패턴, 오브젝트, 개념정리)

1. 객체 형변환

  • 상속 관계에서 부모 타입 변수로 자식 객체를 참조하거나,
    자식 타입으로 명시적 형변환하는 것
  • 자바에서는 부모 타입 변수로 자식 객체를 참조할 수 있지만,
    자식 타입의 멤버를 사용하려면 명시적 형변환이 필요함

간단한 예시

Parent c = new Child();     // 묵시적 형변환 (자동)
((Child)c).method();        // 명시적 형변환 (자식타입으로 변경 후 메서드 실행)

객체 형변환 예시


class Parent {
    int num = 5;

    public int getNum() {
        return num;
    }
}

class Child extends Parent {
    int num = 10;

    @Override
    public int getNum() {
        return num;
    }

    public void print() {
        System.out.println(num);
    }
}

public class Exam {

    public static void test(Parent p) {
        System.out.println(p.num);
        System.out.println(p.getNum());

        // 명시적 형변환
        if (p instanceof Child) {
            ((Child)p).print();
        }
    }

    public static void main(String[] args) {

        Parent p = new Parent();
        System.out.println(p.num);          // 5
        System.out.println(p.getNum());     // 5

        Child c = new Child();
        System.out.println(c.num);          // 10
        System.out.println(c.getNum());     // 10
        c.print();                          // 10


        // 묵시적 형변환
        Parent pc = new Child();
        System.out.println(pc.num);         // 5
        System.out.println(pc.getNum());    // 10

        // 명시적 형변환
        ((Child)pc).print();                // 10


        System.out.println("-".repeat(5));
        test(p);                            // 5, 5
        System.out.println("-".repeat(5));
        test(c);                            // 5, 10, 10
        System.out.println("-".repeat(5));
        test(pc);                           // 5, 10, 10
    }
}

2. 추상 클래스 (abstract)

  • 추상 클래스는 상속을 통해 자식 클래스가 공통된 구조를 가질 수 있게 함
  • 추상 클래스abstract 키워드를 사용하여 선언함
    • public abstract class Animal {}
  • 특정 기능을 추상 메서드로 선언하여 자식 클래스에서 반드시 구현하도록 일반화 함
    • public abstract void sound();
    • 특정 기능에 대해 마음대로 네이밍 정의 못하게 하고 일관성 유지하는 특징이 있음
  • 추상 클래스는 객체 생성 안됨
    • Animal aa = new Animal(); // 불가

활용 예시

// 부모 추상 클래스
// Animal.java
public abstract class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }

    public abstract void info();  // 추상 메서드
    public abstract void sound(); // 추상 메서드
}
  • Animal{} 클래스는 추상 클래스이며, sound()info()추상 메서드
// 자식 클래스 - 1
// Dog.java
public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void info() {
        System.out.println("강아지\n이름: " + getName() + ", 나이: " + getAge());
    }
    @Override
    public void sound() {
        System.out.println("멍멍!");
    }
}
// 자식 클래스 - 2
// Cat.java
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void info() {
        System.out.println("고양이\n이름: " + getName() + ", 나이: " + getAge());
    }
    @Override
    public void sound() {
        System.out.println("야옹~");
    }
}
  • 부모 추상클래스의 abstract 메서드를 오버라이딩해서 구현 필수
// 사용
// import animal.Animal;
import animal.Dog;
import animal.Cat;

public class Exam {
    public static void main(String[] args) {
        Dog dog = new Dog("초코", 12);
        Cat cat = new Cat("춘삼", 11);

        dog.info();
        dog.sound();
        System.out.println("-".repeat(5));
        cat.info();
        cat.sound();
    }
}
# 실행 결과
고양이
이름: 춘삼, 나이: 11
야옹~
-----
강아지
이름: 초코, 나이: 12
멍멍!

3. 인터페이스 클래스 (interface)

  • 인터페이스는 껍데기 형식 - 구현 형태가 없는 것이 기본임
  • 내부 메서드는 추상클래스로 자동 설정
  • 네이밍 규칙은 파스칼 표기법(PascalCase) 따름
  • 기존 일반 상속은 1개만 되는 단일 상속이지만, 인터페이스 타입은 다중 상속 가능함
  • 선언 방식 (interface - implements)
    • 부모1 : public interface 부모1 {}
    • 부모2 : public interface 부모2 {}
    • 자식 : public class 자식 implements 부모1, 부모2 {}
  • 인터페이스 끼리 상속 방식 (interface - extends)
    • 인터1 : public interface 인터1 {}
    • 인터2 : public interface 인터2 {}
    • 인터3 : public interface 인터3 extends 인터1, 인터2 {}

활용 예시

// path : _interface/InterA.java
package _interface;
public interface InterA {
    void draw(); // 추상메서드 (자동)
    void erase();
}
// path : _interface/InterB.java
package _interface;
public interface InterB {
    void resize(double factor);
}
// path : _interface/Photo.java
package _interface;

public class Photo implements InterA, InterB {
    private String fileName;
    private int width, height;

    public Photo(String fileName, int width, int height) {
        this.fileName = fileName;
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        System.out.println(fileName + " 사진을 화면에 그립니다.");
    }

    @Override
    public void erase() {
        System.out.println(fileName + " 사진을 화면에서 지웁니다.");
    }

    @Override
    public void resize(double factor) {
        width = (int)(width * factor);
        height = (int)(height * factor);
        System.out.println("크기 조정: " + width + "x" + height);
    }
}
// path : ./InterExam.java
import _interface.InterA;
import _interface.InterB;
import _interface.Photo;

public class InterExam {
    public static void main(String[] args) {
        double fa = 2.5;

        // 인터페이스도 타입으로 사용 가능
        InterA fileA = new Photo("fileA.png", 200, 300);
        fileA.draw();
        fileA.erase();
        //  fileA.resize(fa);          // 불가
        ((Photo)fileA).resize(fa);     // 명시 변환 후 사용
        ((InterB)fileA).resize(fa);    // 명시 변환 후 사용

        System.out.println("-".repeat(5));

        InterB fileB = new Photo("fileB.png", 100, 200);
        ((InterA)fileB).draw();
        ((Photo)fileB).erase();
        fileB.resize(fa);

        System.out.println("-".repeat(5));

        Photo photo = new Photo("photo.png", 50, 50);
        photo.draw();
        photo.erase();
        photo.resize(fa);
    }
}
# 실행 결과
fileA.png 사진을 화면에 그립니다.
fileA.png 사진을 화면에서 지웁니다.
크기 조정: 500x750
크기 조정: 1250x1875
-----
fileB.png 사진을 화면에 그립니다.
fileB.png 사진을 화면에서 지웁니다.
크기 조정: 250x500
-----
photo.png 사진을 화면에 그립니다.
photo.png 사진을 화면에서 지웁니다.
크기 조정: 125x125

인터페이스의 default와 static 메소드

인터페이스에 기능메서드 추가되면 구현한클래스들 전부 수정해야 되는 단점이 있으나 default, static 형태로 추가 메서드 제공 가능하다. (JDK 8+)

  • 인터페이스의 default 메서드
    • 추가 오버라이딩이 필요한 기능메서드인 경우 사용
  • 인터페이스의 static 메서드
    • 구현한클래스(자식)에 없어도 사용할 수 있게 직접 호출 방식
    • static이라서 형 변환해서 사용 못하니 참고
      • InterC.infoVersion(); 가능
      • ((InterC)test2).infoVersion(); 불가능

TIP
static 메서드는 클래스(또는 인터페이스) 자체에 속하며, 객체(인스턴스)에 속하지 않음
static 메서드는 반드시 타입명(클래스명/인터페이스명).메서드명()으로 호출
static 메서드는 오버라이딩도 불가능하며, 항상 선언된 타입의 메서드가 호출됨

public interface InterC {
    void cMethod1();
    void cMethod2();
    default void cMethod3(); // 메서드 추가
    static void infoVersion() {
        System.out.println("버전 : v1.10");
    }
}

// other file 1...
public class Test1 implements InterC {
    @Override
    void cMethod1() {
        System.out.println("Test1 - cMethod1");
    }
    @Override
    void cMethod2() {
        System.out.println("Test1 - cMethod2");
    }
    // cMethod3 없어도 구현됨.
}

// other file 2...
public class Test2 implements InterC {
    @Override
    void cMethod1() {
        System.out.println("Test2 - cMethod1");
    }
    @Override
    void cMethod2() {
        System.out.println("Test2 - cMethod2");
    }
    @Override
    void cMethod3() {
        System.out.println("Test2 - cMethod3");
    }
}

// other file 3...
public class TestExam {
    public static void main(String[] args) {

        Test1 test1 = new Test1();
        test1.cMethod1();
        test1.cMethod2();

        Test2 test2 = new Test2();
        test2.cMethod3();

        InterC.infoVersion();
        // ((InterC)test2).infoVersion(); // 불가
    }
}

4. 디자인 패턴 (싱글,팩토리)

싱글톤 패턴

싱글톤 패턴은 getInstance()로 객체를 얻고, 항상 같은 인스턴스가 반환

// 싱글톤 클래스 정의
public class Singleton {
    // 1. 클래스 내부에 static으로 유일한 인스턴스 변수 선언
    private static Singleton instance;
    private int count = 0; // 필드 변수

    // 2. 생성자를 private으로 선언하여 외부에서 객체 생성 불가
    private Singleton() {
        // ...
    } 

    // 3.  public static 메서드로 인스턴스를 반환 (없으면 생성)
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void printMessage() {
        System.out.println("싱글톤 객체입니다!");
    }

    public void addCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

// 사용 예제
public class Main {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        s1.printMessage();

        // 두 객체가 같은지 확인
        System.out.println(s1 == s2); // true

        s1.addCount();
        s2.addCount();

        System.out.println(s1.getCount()); // 2
        System.out.println(s2.getCount()); // 2 (같은 객체)
    }
}

팩토리 메서드 패턴

팩토리 메서드 패턴은 객체 생성 로직을 별도의 팩토리 클래스에서 처리하여, 코드의 확장성과 유지보수를 높여줌.

// 제품 인터페이스
interface Animal {
    void sound();
}

// 구체적인 제품 클래스
class Dog implements Animal {
    public void sound() {
        System.out.println("멍멍!");
    }
}

class Cat implements Animal {
    public void sound() {
        System.out.println("야옹~");
    }
}

// 팩토리 클래스
class AnimalFactory {
    public static Animal createAnimal(String type) {
        if (type.equals("dog")) {
            return new Dog();
        } else if (type.equals("cat")) {
            return new Cat();
        }
        return null;
    }
}

// 사용 예제
public class Main {
    public static void main(String[] args) {
        Animal a1 = AnimalFactory.createAnimal("dog");
        Animal a2 = AnimalFactory.createAnimal("cat");

        a1.sound(); // 멍멍!
        a2.sound(); // 야옹~
    }
}

5. 오브젝트 클래스 (object)

  • 모든 클래스의 최상위 클래스
  • 모든 객체가 공통으로 가져야 할 기본 메소드 제공 (toString, equals 등...)
  • extends 없으면 오버라이딩 시 오브젝트 상속
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // toString() 오버라이딩
    @Override
    public String toString() {
        return "Person = {name='" + name + "', age=" + age + "}";
    }
}
public class ObjectTest {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동", 55);

        // toString() 테스트
        System.out.println(p1.toString());  // Person = {name='홍길동', age=55}
        System.out.println(p1.hashCode());  // Person@해시코드

        // toString 오버라이딩을 안했다면 객체.투스트링은 해시코드로 나타남.
    }
}

6. 개념 정리

접근 제한자

접근 제한자 같은 클래스 같은 패키지 자식 클래스 전체
private O X X X
(default) O O X X
protected O O O X
public O O O O

상속 금지 final 클래스

  • 상속을 금지 시키려면 클래스를 정의할 때 final키워드를 사용
    • public final class 클래스명 { ...... }

타입확인 ( instanceof )

  • 해당 타입이 어느 상속클래스 or 인터페이스 바라보고 있는지 확인하는 경우
Animal animal = new Dog();
if(animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.sound();
}

etc.

메서드 오버라이딩 금지 방식

  • 템플릿 메서드 패턴
  • 메서드 앞에 final 붙게되면 @Override로 해당 메서드 오버라이딩 할 수 없다.
public abstract class Game {
    // 템플릿 메소드
    // final -> 자식 클래스는 play() 해당 메서드 오버라이딩 금지됨.
    public final void play() {
        init();
        start();
        end();
    }

    // 추상 메소드들 (하위 클래스에서 구현)
    abstract void init();
    abstract void start();
    abstract void end();
}

public class Chess extends Game {
    @Override
    void init() {
        System.out.println("초기화");
    }

    @Override
    void start() {
        System.out.println("시작");
    }

    @Override
    void end() {
        System.out.println("종료");
    }
}

 


멋사 부트캠프 개인학습 git : https://github.com/star1431/backend

starweb
@starweb :: starweb 님의 블로그

starweb 님의 블로그 입니다.

공감하셨다면 구독도 환영합니다!

목차